pax_global_header00006660000000000000000000000064146261153520014517gustar00rootroot0000000000000052 comment=2308f6247a80eeea97cccb343b0a5f56fdd793d5 google-certificate-transparency-go-2308f62/000077500000000000000000000000001462611535200206415ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/.github/000077500000000000000000000000001462611535200222015ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/.github/dependabot.yml000066400000000000000000000013041462611535200250270ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: / schedule: interval: daily - package-ecosystem: github-actions directory: / schedule: interval: daily - package-ecosystem: docker directory: /integration schedule: interval: daily - package-ecosystem: docker directory: /internal/witness/cmd/feeder schedule: interval: daily - package-ecosystem: docker directory: /internal/witness/cmd/witness schedule: interval: daily - package-ecosystem: docker directory: /trillian/examples/deployment/docker/ctfe schedule: interval: daily - package-ecosystem: docker directory: /trillian/examples/deployment/docker/envsubst schedule: interval: daily google-certificate-transparency-go-2308f62/.github/workflows/000077500000000000000000000000001462611535200242365ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/.github/workflows/codeql.yml000066400000000000000000000057341462611535200262410ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] schedule: - cron: '24 17 * * 1' # Declare default permissions as read only. permissions: read-all jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: category: "/language:${{matrix.language}}" google-certificate-transparency-go-2308f62/.github/workflows/golangci-lint.yml000066400000000000000000000010451462611535200275100ustar00rootroot00000000000000name: golangci-lint on: push: pull_request: permissions: contents: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: "1.21" - name: golangci-lint uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: v1.55.1 args: --timeout=5m google-certificate-transparency-go-2308f62/.github/workflows/govulncheck.yml000066400000000000000000000006421462611535200272730ustar00rootroot00000000000000name: govulncheck on: push: branches: - master pull_request: branches: - master permissions: contents: read jobs: govulncheck_job: runs-on: ubuntu-latest name: Run govulncheck steps: - id: govulncheck uses: golang/govulncheck-action@3a32958c2706f7048305d5a2e53633d7e37e97d0 # v1.0.2 with: go-version-input: 1.21.10 go-package: ./... google-certificate-transparency-go-2308f62/.github/workflows/scorecard.yml000066400000000000000000000056551462611535200267410ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '34 20 * * 1' push: branches: [ "master" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecards on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: sarif_file: results.sarif google-certificate-transparency-go-2308f62/.gitignore000066400000000000000000000004511462611535200226310ustar00rootroot00000000000000*.iml *.swo *.swp *.tfstate *.tfstate.backup *~ /.idea /certcheck /chainfix /coverage.txt /createtree /crlcheck /ctclient /ct_server /ct_hammer /data /dumpscts /findlog /goshawk /gosmin /gossip_server /preloader /scanlog /sctcheck /sctscan /trillian_log_server /trillian_log_signer /trillian.json google-certificate-transparency-go-2308f62/.golangci.yaml000066400000000000000000000015111462611535200233640ustar00rootroot00000000000000run: deadline: 90s skip-dirs: - (^|/)x509($|/) - (^|/)x509util($|/) - (^|/)asn1($|/) linters-settings: gocyclo: min-complexity: 25 depguard: list-type: blacklist packages: - ^golang.org/x/net/context$ - github.com/gogo/protobuf/proto - encoding/asn1 - crypto/x509 issues: exclude-use-default: false exclude-rules: # The following grpc linters are excluded because grpc.Dial, grpc.DialContext and grpc.WithBlock will be supported throughout 1.x. - linters: [staticcheck] text: 'SA1019: grpc.Dial is deprecated: use NewClient instead' - linters: [staticcheck] text: 'SA1019: grpc.DialContext is deprecated: use NewClient instead' - linters: [staticcheck] text: 'SA1019: grpc.WithBlock is deprecated: this DialOption is not supported by NewClient' google-certificate-transparency-go-2308f62/AUTHORS000066400000000000000000000016661462611535200217220ustar00rootroot00000000000000# This is the official list of benchmark authors for copyright purposes. # This file is distinct from the CONTRIBUTORS files. # See the latter for an explanation. # # Names should be added to this file as: # Name or Organization # The email address is not required for organizations. # # Please keep the list sorted. Alex Cohn Comodo CA Limited Ed Maste Elisha Silas Fiaz Hossain Google LLC Internet Security Research Group Jeff Trawick Katriel Cohn-Gordon Laël Cellier Mark Schloesser NORDUnet A/S Nicholas Galbreath Oliver Weidner PrimeKey Solutions AB Ruslan Kovalov Venafi, Inc. Vladimir Rutsky Ximin Luo google-certificate-transparency-go-2308f62/CHANGELOG.md000066400000000000000000002322121462611535200224540ustar00rootroot00000000000000# CERTIFICATE-TRANSPARENCY-GO Changelog ## HEAD ## v1.2.1 ### Fixes * Fix Go potential bugs and maintainability by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1496 ### Dependency update * Bump google.golang.org/grpc from 1.63.2 to 1.64.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1482 ## v1.2.0 ### CTFE Storage Saving: Extra Data Issuance Chain Deduplication To reduce CT/Trillian database storage by deduplication of the entire issuance chain (intermediate certificate(s) and root certificate) that is currently stored in the Trillian merkle tree leaf ExtraData field. Storage cost should be reduced by at least 33% for new CT logs with this feature enabled. Currently only MySQL/MariaDB is supported to store the issuance chain in the CTFE database. Existing logs are not affected by this change. Log operators can choose to opt-in this change for new CT logs by adding new CTFE configs in the [LogMultiConfig](trillian/ctfe/configpb/config.proto) and importing the [database schema](trillian/ctfe/storage/mysql/schema.sql). See [example](trillian/examples/deployment/docker/ctfe/ct_server.cfg). - `ctfe_storage_connection_string` - `extra_data_issuance_chain_storage_backend` An optional LRU cache can be enabled by providing the following flags. - `cache_type` - `cache_size` - `cache_ttl` This change is tested in Cloud Build tests using the `mysql:8.4` Docker image as of the time of writing. * Add issuance chain storage interface by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1430 * Add issuance chain cache interface by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1431 * Add CTFE extra data storage saving configs to config.proto by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1432 * Add new types `PrecertChainEntryHash` and `CertificateChainHash` for TLS marshal/unmarshal in storage saving by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1435 * Add IssuanceChainCache LRU implementation by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1454 * Add issuance chain service by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1452 * Add CTFE extra data storage saving configs validation by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1456 * Add IssuanceChainStorage MySQL implementation by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1462 * Fix errcheck lint in mysql test by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1464 * CTFE Extra Data Issuance Chain Deduplication by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1477 * Fix incorrect deployment doc and server config by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1494 ### Submission proxy: Root compatibility checking * Adds the ability for a CT client to disable root compatibile checking by @aaomidi in https://github.com/google/certificate-transparency-go/pull/1258 ### Fixes * Return 429 Too Many Requests for gRPC error code `ResourceExhausted` from Trillian by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1401 * Safeguard against redirects on PUT request by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1418 * Fix CT client upload to be safe against no-op POSTs by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1424 ### Misc * Prefix errors.New variables with the word "Err" by @aaomidi in https://github.com/google/certificate-transparency-go/pull/1399 * Remove lint exceptions and fix remaining issues by @silaselisha in https://github.com/google/certificate-transparency-go/pull/1438 * Fix invalid Go toolchain version by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1471 * Regenerate proto files by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1489 ### Dependency update * Bump distroless/base-debian12 from `5eae9ef` to `28a7f1f` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1388 * Bump github/codeql-action from 3.24.6 to 3.24.7 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1389 * Bump actions/checkout from 4.1.1 to 4.1.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1390 * Bump golang from `6699d28` to `7f9c058` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1391 * Bump golang from `6699d28` to `7f9c058` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1392 * Bump golang from `6699d28` to `7a392a2` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1393 * Bump golang from `6699d28` to `7a392a2` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1394 * Bump golang from `7a392a2` to `d996c64` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1395 * Bump golang from `7f9c058` to `d996c64` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1396 * Bump golang from `7a392a2` to `d996c64` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1397 * Bump golang from `7f9c058` to `d996c64` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1398 * Bump github/codeql-action from 3.24.7 to 3.24.8 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1400 * Bump github/codeql-action from 3.24.8 to 3.24.9 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1402 * Bump go.etcd.io/etcd/v3 from 3.5.12 to 3.5.13 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1405 * Bump distroless/base-debian12 from `28a7f1f` to `611d30d` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1406 * Bump golang from 1.22.1-bookworm to 1.22.2-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1407 * Bump golang.org/x/net from 0.22.0 to 0.23.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1408 * update govulncheck go version from 1.21.8 to 1.21.9 by @phbnf in https://github.com/google/certificate-transparency-go/pull/1412 * Bump golang from 1.22.1-bookworm to 1.22.2-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1409 * Bump golang from 1.22.1-bookworm to 1.22.2-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1410 * Bump golang.org/x/crypto from 0.21.0 to 0.22.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1414 * Bump golang from 1.22.1-bookworm to 1.22.2-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1411 * Bump github/codeql-action from 3.24.9 to 3.24.10 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1415 * Bump golang.org/x/net from 0.23.0 to 0.24.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1416 * Bump google.golang.org/grpc from 1.62.1 to 1.63.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1417 * Bump github.com/fullstorydev/grpcurl from 1.8.9 to 1.9.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1419 * Bump golang from `48b942a` to `3451eec` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1421 * Bump golang from `48b942a` to `3451eec` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1423 * Bump golang from `48b942a` to `3451eec` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1420 * Bump golang from `3451eec` to `b03f3ba` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1426 * Bump golang from `3451eec` to `b03f3ba` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1425 * Bump golang from `48b942a` to `3451eec` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1422 * Bump golang from `3451eec` to `b03f3ba` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1427 * Bump golang from `3451eec` to `b03f3ba` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1428 * Bump github/codeql-action from 3.24.10 to 3.25.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1433 * Bump github/codeql-action from 3.25.0 to 3.25.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1434 * Bump actions/upload-artifact from 4.3.1 to 4.3.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1436 * Bump actions/checkout from 4.1.2 to 4.1.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1437 * Bump actions/upload-artifact from 4.3.2 to 4.3.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1440 * Bump github/codeql-action from 3.25.1 to 3.25.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1441 * Bump golang from `b03f3ba` to `d0902ba` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1444 * Bump golang from `b03f3ba` to `d0902ba` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1443 * Bump github.com/rs/cors from 1.10.1 to 1.11.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1442 * Bump golang from `b03f3ba` to `d0902ba` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1447 * Bump actions/checkout from 4.1.3 to 4.1.4 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1446 * Bump github/codeql-action from 3.25.2 to 3.25.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1449 * Bump golangci/golangci-lint-action from 4.0.0 to 5.0.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1448 * Bump golang from `b03f3ba` to `d0902ba` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1445 * Bump golangci/golangci-lint-action from 5.0.0 to 5.1.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1451 * Bump distroless/base-debian12 from `611d30d` to `d8d01e2` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1450 * Bump google.golang.org/protobuf from 1.33.1-0.20240408130810-98873a205002 to 1.34.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1453 * Bump actions/setup-go from 5.0.0 to 5.0.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1455 * Bump golang.org/x/net from 0.24.0 to 0.25.0 and golang.org/x/crypto from v0.22.0 to v0.23.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1457 * Bump google.golang.org/protobuf from 1.34.0 to 1.34.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1458 * Bump distroless/base-debian12 from `d8d01e2` to `786007f` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1461 * Bump golangci/golangci-lint-action from 5.1.0 to 5.3.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1460 * Bump `go-version-input` to 1.21.10 in govulncheck.yml by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1472 * Bump golangci/golangci-lint-action from 5.3.0 to 6.0.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1473 * Bump actions/checkout from 4.1.4 to 4.1.5 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1469 * Bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1465 * Bump golang from 1.22.2-bookworm to 1.22.3-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1466 * Bump golang from 1.22.2-bookworm to 1.22.3-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1463 * Bump golang from 1.22.2-bookworm to 1.22.3-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1470 * Bump golang from 1.22.2-bookworm to 1.22.3-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1467 * Bump github/codeql-action from 3.25.3 to 3.25.4 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1474 * Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1475 * Bump ossf/scorecard-action from 2.3.1 to 2.3.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1476 * Bump github/codeql-action from 3.25.4 to 3.25.5 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1478 * Bump golang from `6d71b7c` to `ef27a3c` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1480 * Bump golang from `6d71b7c` to `ef27a3c` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1481 * Bump golang from `6d71b7c` to `ef27a3c` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1479 * Bump golang from `6d71b7c` to `ef27a3c` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1483 * Bump golang from `ef27a3c` to `5c56bd4` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1484 * Bump golang from `ef27a3c` to `5c56bd4` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1485 * Bump golang from `ef27a3c` to `5c56bd4` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1486 * Bump actions/checkout from 4.1.5 to 4.1.6 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1487 * Bump golang from `ef27a3c` to `5c56bd4` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1488 * Bump github/codeql-action from 3.25.5 to 3.25.6 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1490 * Bump alpine from `c5b1261` to `58d02b4` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1491 * Bump alpine from `58d02b4` to `77726ef` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1493 ## v1.1.8 * Recommended Go version for development: 1.21 * Using a different version can lead to presubmits failing due to unexpected diffs. ### Add support for AIX * crypto/x509: add AIX operating system by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1277 ### Monitoring * Distribution metric to monitor the start of get-entries requests by @phbnf in https://github.com/google/certificate-transparency-go/pull/1364 ### Fixes * Use the appropriate HTTP response code for backend timeouts by @robstradling in https://github.com/google/certificate-transparency-go/pull/1313 ### Misc * Move golangci-lint from Cloud Build to GitHub Action by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1230 * Set golangci-lint GH action timeout to 5m by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1231 * Added Slack channel details by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1246 * Improve fuzzing by @AdamKorcz in https://github.com/google/certificate-transparency-go/pull/1345 ### Dependency update * Bump golang from `20f9ab5` to `5ee1296` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1216 * Bump golang from `20f9ab5` to `5ee1296` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1217 * Bump golang from `20f9ab5` to `5ee1296` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1218 * Bump k8s.io/klog/v2 from 2.100.1 to 2.110.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1219 * Bump golang from `20f9ab5` to `5ee1296` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1220 * Bump golang from `5ee1296` to `5bafbbb` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1221 * Bump golang from `5ee1296` to `5bafbbb` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1222 * Bump golang from `5ee1296` to `5bafbbb` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1223 * Bump golang from `5ee1296` to `5bafbbb` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1224 * Update the minimal image to gcr.io/distroless/base-debian12 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1148 * Bump jq from 1.6 to 1.7 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1225 * Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1226 * Bump golang.org/x/time from 0.3.0 to 0.4.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1227 * Bump github.com/mattn/go-sqlite3 from 1.14.17 to 1.14.18 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1228 * Bump github.com/gorilla/mux from 1.8.0 to 1.8.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1229 * Bump golang from 1.21.3-bookworm to 1.21.4-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1232 * Bump golang from 1.21.3-bookworm to 1.21.4-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1233 * Bump golang from 1.21.3-bookworm to 1.21.4-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1234 * Bump golang from 1.21.3-bookworm to 1.21.4-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1235 * Bump go-version-input from 1.20.10 to 1.20.11 in govulncheck.yml by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1238 * Bump golang.org/x/net from 0.17.0 to 0.18.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1236 * Bump github/codeql-action from 2.22.5 to 2.22.6 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1240 * Bump github/codeql-action from 2.22.6 to 2.22.7 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1241 * Bump golang from `85aacbe` to `dadce81` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1243 * Bump golang from `85aacbe` to `dadce81` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1242 * Bump golang from `85aacbe` to `dadce81` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1244 * Bump golang from `85aacbe` to `dadce81` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1245 * Bump golang from `dadce81` to `52362e2` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1247 * Bump golang from `dadce81` to `52362e2` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1248 * Bump golang from `dadce81` to `52362e2` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1249 * Bump golang from `dadce81` to `52362e2` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1250 * Bump github/codeql-action from 2.22.7 to 2.22.8 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1251 * Bump golang.org/x/net from 0.18.0 to 0.19.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1252 * Bump golang.org/x/time from 0.4.0 to 0.5.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1254 * Bump alpine from `eece025` to `34871e7` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1256 * Bump alpine from `eece025` to `34871e7` in /trillian/examples/deployment/docker/envsubst by @dependabot in https://github.com/google/certificate-transparency-go/pull/1257 * Bump go-version-input from 1.20.11 to 1.20.12 in govulncheck.yml by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1264 * Bump actions/setup-go from 4.1.0 to 5.0.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1261 * Bump golang from 1.21.4-bookworm to 1.21.5-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1259 * Bump golang from 1.21.4-bookworm to 1.21.5-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1263 * Bump golang from 1.21.4-bookworm to 1.21.5-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1262 * Bump golang from 1.21.4-bookworm to 1.21.5-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1260 * Bump go.etcd.io/etcd/v3 from 3.5.10 to 3.5.11 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1266 * Bump github/codeql-action from 2.22.8 to 2.22.9 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1269 * Bump alpine from `34871e7` to `51b6726` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1270 * Bump alpine from 3.18 to 3.19 in /trillian/examples/deployment/docker/envsubst by @dependabot in https://github.com/google/certificate-transparency-go/pull/1271 * Bump golang from `a6b787c` to `2d3b13c` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1272 * Bump golang from `a6b787c` to `2d3b13c` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1273 * Bump golang from `a6b787c` to `2d3b13c` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1274 * Bump golang from `a6b787c` to `2d3b13c` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1275 * Bump github/codeql-action from 2.22.9 to 2.22.10 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1278 * Bump google.golang.org/grpc from 1.59.0 to 1.60.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1279 * Bump github/codeql-action from 2.22.10 to 3.22.11 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1280 * Bump distroless/base-debian12 from `1dfdb5e` to `8a0bb63` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1281 * Bump github.com/google/trillian from 1.5.3 to 1.5.4-0.20240110091238-00ca9abe023d by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1297 * Bump actions/upload-artifact from 3.1.3 to 4.0.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1282 * Bump github/codeql-action from 3.22.11 to 3.23.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1295 * Bump github.com/mattn/go-sqlite3 from 1.14.18 to 1.14.19 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1283 * Bump golang from 1.21.5-bookworm to 1.21.6-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1300 * Bump distroless/base-debian12 from `8a0bb63` to `0a93daa` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1284 * Bump golang from 1.21.5-bookworm to 1.21.6-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1299 * Bump golang from 1.21.5-bookworm to 1.21.6-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1298 * Bump golang from 1.21.5-bookworm to 1.21.6-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1301 * Bump golang from `688ad7f` to `1e8ea75` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1306 * Bump golang from `688ad7f` to `1e8ea75` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1305 * Use trillian release instead of pinned commit by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1304 * Bump actions/upload-artifact from 4.0.0 to 4.1.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1310 * Bump golang from `1e8ea75` to `cbee5d2` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1312 * Bump golang from `688ad7f` to `cbee5d2` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1308 * Bump golang from `1e8ea75` to `cbee5d2` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1311 * Bump golang.org/x/net from 0.19.0 to 0.20.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1302 * Bump golang from `b651ed8` to `cbee5d2` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1309 * Bump golang from `cbee5d2` to `c4b696f` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1314 * Bump golang from `cbee5d2` to `c4b696f` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1315 * Bump github/codeql-action from 3.23.0 to 3.23.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1317 * Bump golang from `cbee5d2` to `c4b696f` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1316 * Bump golang from `cbee5d2` to `c4b696f` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1318 * Bump k8s.io/klog/v2 from 2.120.0 to 2.120.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1319 * Bump actions/upload-artifact from 4.1.0 to 4.2.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1320 * Bump actions/upload-artifact from 4.2.0 to 4.3.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1321 * Bump golang from `c4b696f` to `d8c365d` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1326 * Bump golang from `c4b696f` to `d8c365d` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1323 * Bump google.golang.org/grpc from 1.60.1 to 1.61.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1324 * Bump golang from `c4b696f` to `d8c365d` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1322 * Bump golang from `c4b696f` to `d8c365d` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1325 * Bump github.com/mattn/go-sqlite3 from 1.14.19 to 1.14.20 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1327 * Bump github/codeql-action from 3.23.1 to 3.23.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1328 * Bump alpine from `51b6726` to `c5b1261` in /trillian/examples/deployment/docker/envsubst by @dependabot in https://github.com/google/certificate-transparency-go/pull/1330 * Bump alpine from `51b6726` to `c5b1261` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1329 * Bump go.etcd.io/etcd/v3 from 3.5.11 to 3.5.12 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1332 * Bump github.com/mattn/go-sqlite3 from 1.14.20 to 1.14.21 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1333 * Bump golang from `d8c365d` to `69bfed3` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1335 * Bump golang from `d8c365d` to `69bfed3` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1338 * Bump golang from `d8c365d` to `69bfed3` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1337 * Bump golang from `d8c365d` to `69bfed3` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1336 * Bump golang from `69bfed3` to `3efef61` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1339 * Bump github.com/mattn/go-sqlite3 from 1.14.21 to 1.14.22 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1344 * Bump golang from `69bfed3` to `3efef61` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1341 * Bump golang from `69bfed3` to `3efef61` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1343 * Bump distroless/base-debian12 from `0a93daa` to `f47fa3d` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1340 * Bump golang from `69bfed3` to `3efef61` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1342 * Bump github/codeql-action from 3.23.2 to 3.24.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1346 * Bump actions/upload-artifact from 4.3.0 to 4.3.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1347 * Bump golang from 1.21.6-bookworm to 1.22.0-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1350 * Bump golang from 1.21.6-bookworm to 1.22.0-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1348 * Bump golang from 1.21.6-bookworm to 1.22.0-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1349 * Bump golang from 1.21.6-bookworm to 1.22.0-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1351 * Bump golang.org/x/crypto from 0.18.0 to 0.19.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1353 * Bump golangci/golangci-lint-action from 3.7.0 to 4.0.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1354 * Bump golang.org/x/net from 0.20.0 to 0.21.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1352 * Bump distroless/base-debian12 from `f47fa3d` to `2102ce1` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1355 * Bump github/codeql-action from 3.24.0 to 3.24.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1357 * Bump golang from `874c267` to `5a3e169` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1356 * Bump golang from `874c267` to `5a3e169` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1358 * Bump golang from `874c267` to `5a3e169` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1359 * Bump golang from `874c267` to `5a3e169` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1360 * Bump github/codeql-action from 3.24.1 to 3.24.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1366 * Bump golang from `5a3e169` to `925fe3f` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1363 * Bump google.golang.org/grpc from 1.61.0 to 1.61.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1362 * Bump golang from `5a3e169` to `925fe3f` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1365 * Bump golang from `5a3e169` to `925fe3f` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1361 * Bump golang from `5a3e169` to `925fe3f` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1367 * Bump golang/govulncheck-action from 1.0.1 to 1.0.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1368 * Bump github/codeql-action from 3.24.3 to 3.24.5 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1371 * Bump google.golang.org/grpc from 1.61.1 to 1.62.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1369 * Bump distroless/base-debian12 from `2102ce1` to `5eae9ef` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1372 * Bump distroless/base-debian12 from `5eae9ef` to `f9b0e86` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1375 * Bump golang.org/x/crypto from 0.19.0 to 0.20.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1374 * Bump github.com/prometheus/client_golang from 1.18.0 to 1.19.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1373 * Bump github/codeql-action from 3.24.5 to 3.24.6 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1377 * Bump distroless/base-debian12 from `f9b0e86` to `5eae9ef` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1376 * Bump golang.org/x/net from 0.21.0 to 0.22.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1378 * Bump Go from 1.20 to 1.21 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1386 * Bump google.golang.org/grpc from 1.62.0 to 1.62.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1380 * Bump golang from 1.22.0-bookworm to 1.22.1-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1382 * Bump golang from 1.22.0-bookworm to 1.22.1-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1385 * Bump golang from 1.22.0-bookworm to 1.22.1-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1384 * Bump golang from 1.22.0-bookworm to 1.22.1-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1383 ## v1.1.7 * Recommended Go version for development: 1.20 * This is the version used by the Cloud Build presubmits. Using a different version can lead to presubmits failing due to unexpected diffs. * Bump golangci-lint from 1.51.1 to 1.55.1 (developers should update to this version). ### Add support for WASI port * Add build tags for wasip1 GOOS by @flavio in https://github.com/google/certificate-transparency-go/pull/1089 ### Add support for IBM Z operating system z/OS * Add build tags for zOS by @onlywork1984 in https://github.com/google/certificate-transparency-go/pull/1088 ### Log List * Add support for "is_all_logs" field in loglist3 by @phbnf in https://github.com/google/certificate-transparency-go/pull/1095 ### Documentation * Improve Dockerized Test Deployment documentation by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1179 ### Misc * Escape forward slashes in certificate Subject names when used as user quota id strings by @robstradling in https://github.com/google/certificate-transparency-go/pull/1059 * Search whole chain looking for issuer match by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1112 * Use proper check per @AGWA instead of buggy check introduced in #1112 by @mhutchinson in https://github.com/google/certificate-transparency-go/pull/1114 * Build the ctfe/ct_server binary without depending on glibc by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1119 * Migrate CTFE Ingress manifest to support GKE version 1.23 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1086 * Remove Dependabot ignore configuration by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1097 * Add "github-actions" and "docker" Dependabot config by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1101 * Add top level permission in CodeQL workflow by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1102 * Pin Docker image dependencies by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1110 * Remove GO111MODULE from Dockerfile and Cloud Build yaml files by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1113 * Add docker Dependabot config by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1126 * Export is_mirror = 0.0 for non mirror instead of nothing by @phbnf in https://github.com/google/certificate-transparency-go/pull/1133 * Add govulncheck GitHub action by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1145 * Spelling by @jsoref in https://github.com/google/certificate-transparency-go/pull/1144 ### Dependency update * Bump Go from 1.19 to 1.20 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1146 * Bump golangci-lint from 1.51.1 to 1.55.1 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1214 * Bump go.etcd.io/etcd/v3 from 3.5.8 to 3.5.9 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1083 * Bump golang.org/x/crypto from 0.8.0 to 0.9.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/108 * Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1092 * Bump golang.org/x/net from 0.10.0 to 0.11.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1094 * Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1098 * Bump google.golang.org/protobuf from 1.30.0 to 1.31.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1099 * Bump golang.org/x/net from 0.11.0 to 0.12.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1108 * Bump actions/checkout from 3.1.0 to 3.5.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1103 * Bump github/codeql-action from 2.1.27 to 2.20.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1104 * Bump ossf/scorecard-action from 2.0.6 to 2.2.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1105 * Bump actions/upload-artifact from 3.1.0 to 3.1.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1106 * Bump github/codeql-action from 2.20.3 to 2.20.4 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1115 * Bump github/codeql-action from 2.20.4 to 2.21.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1117 * Bump golang.org/x/net from 0.12.0 to 0.14.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1124 * Bump github/codeql-action from 2.21.0 to 2.21.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1121 * Bump github/codeql-action from 2.21.2 to 2.21.4 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1125 * Bump golang from `fd9306e` to `eb3f9ac` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1127 * Bump alpine from 3.8 to 3.18 in /trillian/examples/deployment/docker/envsubst by @dependabot in https://github.com/google/certificate-transparency-go/pull/1129 * Bump golang from `fd9306e` to `eb3f9ac` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1128 * Bump alpine from `82d1e9d` to `7144f7b` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1130 * Bump golang from `fd9306e` to `eb3f9ac` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1131 * Bump golang from 1.19-alpine to 1.21-alpine in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1132 * Bump actions/checkout from 3.5.3 to 3.6.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1134 * Bump github/codeql-action from 2.21.4 to 2.21.5 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1135 * Bump distroless/base from `73deaaf` to `46c5b9b` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1136 * Bump actions/checkout from 3.6.0 to 4.0.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1137 * Bump golang.org/x/net from 0.14.0 to 0.15.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1139 * Bump github.com/rs/cors from 1.9.0 to 1.10.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1140 * Bump actions/upload-artifact from 3.1.2 to 3.1.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1141 * Bump golang from `445f340` to `96634e5` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1142 * Bump github/codeql-action from 2.21.5 to 2.21.6 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1149 * Bump Docker golang base images to 1.21.1 by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1147 * Bump github/codeql-action from 2.21.6 to 2.21.7 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1150 * Bump github/codeql-action from 2.21.7 to 2.21.8 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1152 * Bump golang from `d3114db` to `a0b3bc4` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1155 * Bump golang from `d3114db` to `a0b3bc4` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1157 * Bump golang from `d3114db` to `a0b3bc4` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1156 * Bump golang from `d3114db` to `a0b3bc4` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1158 * Bump golang from `e06b3a4` to `114b9cc` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1159 * Bump golang from `a0b3bc4` to `114b9cc` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1160 * Bump golang from `a0b3bc4` to `114b9cc` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1161 * Bump actions/checkout from 4.0.0 to 4.1.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1162 * Bump golang from `114b9cc` to `9c7ea4a` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1163 * Bump golang from `114b9cc` to `9c7ea4a` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1166 * Bump golang from `114b9cc` to `9c7ea4a` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1165 * Bump golang from `114b9cc` to `9c7ea4a` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1164 * Bump github/codeql-action from 2.21.8 to 2.21.9 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1169 * Bump golang from `9c7ea4a` to `61f84bc` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1168 * Bump github.com/prometheus/client_golang from 1.16.0 to 1.17.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1172 * Bump golang from `9c7ea4a` to `61f84bc` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1170 * Bump github.com/rs/cors from 1.10.0 to 1.10.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1176 * Bump alpine from `7144f7b` to `eece025` in /trillian/examples/deployment/docker/envsubst by @dependabot in https://github.com/google/certificate-transparency-go/pull/1174 * Bump alpine from `7144f7b` to `eece025` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1175 * Bump golang from `9c7ea4a` to `61f84bc` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1171 * Bump golang from `9c7ea4a` to `61f84bc` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1173 * Bump distroless/base from `46c5b9b` to `a35b652` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1177 * Bump golang.org/x/crypto from 0.13.0 to 0.14.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1178 * Bump github/codeql-action from 2.21.9 to 2.22.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1180 * Bump golang from 1.21.1-bookworm to 1.21.2-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1181 * Bump golang.org/x/net from 0.15.0 to 0.16.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1184 * Bump golang from 1.21.1-bookworm to 1.21.2-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1182 * Bump golang from 1.21.1-bookworm to 1.21.2-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1185 * Bump golang from 1.21.1-bookworm to 1.21.2-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1183 * Bump github/codeql-action from 2.22.0 to 2.22.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1186 * Bump distroless/base from `a35b652` to `b31a6e0` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1188 * Bump ossf/scorecard-action from 2.2.0 to 2.3.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1187 * Bump github.com/google/go-cmp from 0.5.9 to 0.6.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1189 * Bump golang.org/x/net from 0.16.0 to 0.17.0 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1190 * Bump go-version-input from 1.20.8 to 1.20.10 in govulncheck by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1195 * Bump golang from 1.21.2-bookworm to 1.21.3-bookworm in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1193 * Bump golang from 1.21.2-bookworm to 1.21.3-bookworm in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1191 * Bump golang from 1.21.2-bookworm to 1.21.3-bookworm in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1194 * Bump golang from 1.21.2-bookworm to 1.21.3-bookworm in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1192 * Bump golang from `a94b089` to `8f9a1ec` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1196 * Bump github/codeql-action from 2.22.1 to 2.22.2 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1197 * Bump golang from `a94b089` to `5cc7ddc` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1200 * Bump golang from `a94b089` to `5cc7ddc` in /internal/witness/cmd/witness by @dependabot in https://github.com/google/certificate-transparency-go/pull/1199 * Bump github/codeql-action from 2.22.2 to 2.22.3 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1202 * Bump golang from `5cc7ddc` to `20f9ab5` in /integration by @dependabot in https://github.com/google/certificate-transparency-go/pull/1203 * Bump golang from `a94b089` to `20f9ab5` in /trillian/examples/deployment/docker/ctfe by @dependabot in https://github.com/google/certificate-transparency-go/pull/1198 * Bump golang from `8f9a1ec` to `20f9ab5` in /internal/witness/cmd/feeder by @dependabot in https://github.com/google/certificate-transparency-go/pull/1201 * Bump actions/checkout from 4.1.0 to 4.1.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1204 * Bump github/codeql-action from 2.22.3 to 2.22.4 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1206 * Bump ossf/scorecard-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1207 * Bump github/codeql-action from 2.22.4 to 2.22.5 by @dependabot in https://github.com/google/certificate-transparency-go/pull/1209 * Bump multiple Go module dependencies by @roger2hk in https://github.com/google/certificate-transparency-go/pull/1213 ## v1.1.6 ### Dependency update * Bump Trillian to v1.5.2 * Bump Prometheus to v0.43.1 ## v1.1.5 ### Public/Private Key Consistency * #1044: If a public key has been configured for a log, check that it is consistent with the private key. * #1046: Ensure that no two logs in the CTFE configuration use the same private key. ### Cleanup * Remove v2 log list package files. ### Misc * Updated golangci-lint to v1.51.1 (developers should update to this version). * Bump Go version from 1.17 to 1.19. ## v1.1.4 [Published 2022-10-21](https://github.com/google/certificate-transparency-go/releases/tag/v1.1.4) ### Cleanup * Remove log list v1 package and its dependencies. ### Migrillian * #960: Skip consistency check when root is size zero. ### Misc * Update Trillian to [0a389c4](https://github.com/google/trillian/commit/0a389c4bb8d97fb3be8f55d7e5b428cf4304986f) * Migrate loglist dependency from v1 to v3 in ctclient cmd. * Migrate loglist dependency from v1 to v3 in ctutil/loginfo.go * Migrate loglist dependency from v1 to v3 in ctutil/sctscan.go * Migrate loglist dependency from v1 to v3 in trillian/integration/ct_hammer/main.go * Downgrade 429 errors to verbosity 2 ## v1.1.3 [Published 2022-05-14](https://github.com/google/certificate-transparency-go/releases/tag/v1.1.3) ### Integration * Breaking change to API for `integration.HammerCTLog`: * Added `ctx` as first argument, and terminate loop if it becomes cancelled ### JSONClient * PostAndParseWithRetry now does backoff-and-retry upon receiving HTTP 429. ### Cleanup * `WithBalancerName` is deprecated and removed, using the recommended way. * `ctfe.PEMCertPool` type has been moved to `x509util.PEMCertPool` to reduce dependencies (#903). ### Misc * updated golangci-lint to v1.46.1 (developers should update to this version) * update `google.golang.org/grpc` to v1.46.0 * `ctclient` tool now uses Cobra for better CLI experience (#901). * #800: Remove dependency from `ratelimit`. * #927: Add read-only mode to CTFE config. ## v1.1.2 [Published 2021-09-21](https://github.com/google/certificate-transparency-go/releases/tag/v1.1.2) ### CTFE * Removed the `-by_range` flag. ### Updated dependencies * Trillian from v1.3.11 to v1.4.0 * protobuf to v2 ## v1.1.1 [Published 2020-10-06](https://github.com/google/certificate-transparency-go/releases/tag/v1.1.1) ### Tools #### CT Hammer Added a flag (--strict_sth_consistency_size) which when set to true enforces the current behaviour of only request consistency proofs between tree sizes for which the hammer has seen valid STHs. When setting this flag to false, if no two usable STHs are available the hammer will attempt to request a consistency proof between the latest STH it's seen and a random smaller (but > 0) tree size. ### CTFE #### Caching The CTFE now includes a Cache-Control header in responses containing purely immutable data, e.g. those for get-entries and get-proof-by-hash. This allows clients and proxies to cache these responses for up to 24 hours. #### EKU Filtering > :warning: **It is not yet recommended to enable this option in a production CT Log!** CTFE now supports filtering logging submissions by leaf certificate EKU. This is enabled by adding an extKeyUsage list to a log's stanza in the config file. The format is a list of strings corresponding to the supported golang x509 EKUs: |Config string | Extended Key Usage | |----------------------------|----------------------------------------| |`Any` | ExtKeyUsageAny | |`ServerAuth` | ExtKeyUsageServerAuth | |`ClientAuth` | ExtKeyUsageClientAuth | |`CodeSigning` | ExtKeyUsageCodeSigning | |`EmailProtection` | ExtKeyUsageEmailProtection | |`IPSECEndSystem` | ExtKeyUsageIPSECEndSystem | |`IPSECTunnel` | ExtKeyUsageIPSECTunnel | |`IPSECUser` | ExtKeyUsageIPSECUser | |`TimeStamping` | ExtKeyUsageTimeStamping | |`OCSPSigning` | ExtKeyUsageOCSPSigning | |`MicrosoftServerGatedCrypto`| ExtKeyUsageMicrosoftServerGatedCrypto | |`NetscapeServerGatedCrypto` | ExtKeyUsageNetscapeServerGatedCrypto | When an extKeyUsage list is specified, the CT Log will reject logging submissions for leaf certificates that do not contain an EKU present in this list. When enabled, EKU filtering is only performed at the leaf level (i.e. there is no 'nested' EKU filtering performed). If no list is specified, or the list contains an `Any` entry, no EKU filtering will be performed. #### GetEntries Calls to `get-entries` which are at (or above) the maximum permitted number of entries whose `start` parameter does not fall on a multiple of the maximum permitted number of entries, will have their responses truncated such that subsequent requests will align with this boundary. This is intended to coerce callers of `get-entries` into all using the same `start` and `end` parameters and thereby increase the cacheability of these requests. e.g.:
Old behaviour:
             1         2         3
             0         0         0
Entries>-----|---------|---------|----...
Client A -------|---------|----------|...
Client B --|--------|---------|-------...
           ^        ^         ^
           `--------`---------`---- requests

With coercion (max batch = 10 entries):
             1         2         3
             0         0         0
Entries>-----|---------|---------|----...
Client A ----X---------|---------|...
Client B --|-X---------|---------|-------...
             ^
             `-- Requests truncated
This behaviour can be disabled by setting the `--align_getentries` flag to false. #### Flags The `ct_server` binary changed the default of these flags: - `by_range` - Now defaults to `true` The `ct_server` binary added the following flags: - `align_getentries` - See GetEntries section above for details Added `backend` flag to `migrillian`, which now replaces the deprecated "backend" feature of Migrillian configs. #### FixedBackendResolver Replaced This was previously used in situations where a comma separated list of backends was provided in the `rpcBackend` flag rather than a single value. It has been replaced by equivalent functionality using a newer gRPC API. However this support was only intended for use in integration tests. In production we recommend the use of etcd or a gRPC load balancer. ### LogList Log list tools updated to use the correct v2 URL (from v2_beta previously). ### Libraries #### x509 fork Merged upstream Go 1.13 and Go 1.14 changes (with the exception of https://github.com/golang/go/commit/14521198679e, to allow old certs using a malformed root still to be logged). #### asn1 fork Merged upstream Go 1.14 changes. #### ctutil Added VerifySCTWithVerifier() to verify SCTs using a given ct.SignatureVerifier. ### Configuration Files Configuration files that previously had to be text-encoded Protobuf messages can now alternatively be binary-encoded instead. ### JSONClient - `PostAndParseWithRetry` error logging now includes log URI in messages. ### Minimal Gossip Example All the code for this, except for the x509ext package, has been moved over to the [trillian-examples](https://github.com/google/trillian-examples) repository. This keeps the code together and removes a circular dependency between the two repositories. The package layout and structure remains the same so updating should just mean changing any relevant import paths. ### Dependencies A circular dependency on the [monologue](https://github.com/google/monologue) repository has been removed. A circular dependency on the [trillian-examples](https://github.com/google/trillian-examples) repository has been removed. The version of trillian in use has been updated to 1.3.11. This has required various other dependency updates including gRPC and protobuf. This code now uses the v2 proto API. The Travis tests now expect the 3.11.4 version of protoc. The version of etcd in use has been switched to the one from `go.etcd.io`. Most of the above changes are to align versions more closely with the ones used in the trillian repository. ## v1.1.0 Published 2019-11-14 15:00:00 +0000 UTC ### CTFE The `reject_expired` and `reject_unexpired` configuration fields for the CTFE have been changed so that their behaviour reflects their name: - `reject_expired` only rejects expired certificates (i.e. it now allows not-yet-valid certificates). - `reject_unexpired` only allows expired certificates (i.e. it now rejects not-yet-valid certificates). A `reject_extensions` configuration field for the CTFE was added, this allows submissions to be rejected if they contain an extension with any of the specified OIDs. A `frozen_sth` configuration field for the CTFE was added. This STH will be served permanently. It must be signed by the log's private key. A `/healthz` URL has been added which responds with HTTP 200 OK and the string "ok" when the server is up. #### Flags The `ct_server` binary has these new flags: - `mask_internal_errors` - Removes error strings from HTTP 500 responses (Internal Server Error) Removed default values for `--metrics_endpoint` and `--log_rpc_server` flags. This makes it easier to get the documented "unset" behaviour. #### Metrics The CTFE exports these new metrics: - `is_mirror` - set to 1 for mirror logs (copies of logs hosted elsewhere) - `frozen_sth_timestamp` - time of the frozen Signed Tree Head in milliseconds since the epoch #### Kubernetes Updated prometheus-to-sd to v0.5.2. A dedicated node pool is no longer required by the Kubernetes manifests. ### Log Lists A new package has been created for parsing, searching and creating JSON log lists compatible with the [v2 schema](http://www.gstatic.com/ct/log_list/v2_beta/log_list_schema.json): `github.com/google/certificate-transparency-go/loglist2`. ### Docker Images Our Docker images have been updated to use Go 1.11 and [Distroless base images](https://github.com/GoogleContainerTools/distroless). The CTFE Docker image now sets `ENTRYPOINT`. ### Utilities / Libraries #### jsonclient The `jsonclient` package now copes with empty HTTP responses. The user-agent header it sends can now be specified. #### x509 and asn1 forks Merged upstream changes from Go 1.12 into the `asn1` and `x509` packages. Added a "lax" tag to `asn1` that applies recursively and makes some checks more relaxed: - parsePrintableString() copes with invalid PrintableString contents, e.g. use of tagPrintableString when the string data is really ISO8859-1. - checkInteger() allows integers that are not minimally encoded (and so are not correct DER). - OIDs are allowed to be empty. The following `x509` functions will now return `x509.NonFatalErrors` if ASN.1 parsing fails in strict mode but succeeds in lax mode. Previously, they only attempted strict mode parsing. - `x509.ParseTBSCertificate()` - `x509.ParseCertificate()` - `x509.ParseCertificates()` The `x509` package will now treat a negative RSA modulus as a non-fatal error. The `x509` package now supports RSASES-OAEP and Ed25519 keys. #### ctclient The `ctclient` tool now defaults to using [all_logs_list.json](https://www.gstatic.com/ct/log_list/all_logs_list.json) instead of [log_list.json](https://www.gstatic.com/ct/log_list/log_list.json). This can be overridden using the `--log_list` flag. It can now perform inclusion checks on pre-certificates. It has these new commands: - `bisect` - Finds a log entry given a timestamp. It has these new flags: - `--chain` - Displays the entire certificate chain - `--dns_server` - The DNS server to direct queries to (system resolver by default) - `--skip_https_verify` - Skips verification of the HTTPS connection - `--timestamp` - Timestamp to use for `bisect` and `inclusion` commands (for `inclusion`, only if --leaf_hash is not used) It now accepts hex or base64-encoded strings for the `--tree_hash`, `--prev_hash` and `--leaf_hash` flags. #### certcheck The `certcheck` tool has these new flags: - `--check_time` - Check current validity of certificate (replaces `--timecheck`) - `--check_name` - Check validity of certificate name - `--check_eku` - Check validity of EKU nesting - `--check_path_len` - Check validity of path length constraint - `--check_name_constraint` - Check name constraints - `--check_unknown_critical_exts` - Check for unknown critical extensions (replaces `--ignore_unknown_critical_exts`) - `--strict` - Set non-zero exit code for non-fatal errors in parsing #### sctcheck The `sctcheck` tool has these new flags: - `--check_inclusion` - Checks that the SCT was honoured (i.e. the corresponding certificate was included in the issuing CT log) #### ct_hammer The `ct_hammer` tool has these new flags: - `--duplicate_chance` - Allows setting the probability of the hammer sending a duplicate submission. ## v1.0.21 - CTFE Logging / Path Options. Mirroring. RPKI. Non Fatal X.509 error improvements Published 2018-08-20 10:11:04 +0000 UTC ### CTFE `CTFE` no longer prints certificate chains as long byte strings in messages when handler errors occur. This was obscuring the reason for the failure and wasn't particularly useful. `CTFE` now has a global log URL path prefix flag and a configuration proto for a log specific path. The latter should help for various migration strategies if existing C++ server logs are going to be converted to run on the new code. ### Mirroring More progress has been made on log mirroring. We believe that it's now at the point where testing can begin. ### Utilities / Libraries The `certcheck` and `ct_hammer` utilities have received more enhancements. `x509` and `x509util` now support Subject Information Access and additional extensions for [RPKI / RFC 3779](https://www.ietf.org/rfc/rfc3779.txt). `scanner` / `fixchain` and some other command line utilities now have better handling of non-fatal errors. Commit [3629d6846518309d22c16fee15d1007262a459d2](https://api.github.com/repos/google/certificate-transparency-go/commits/3629d6846518309d22c16fee15d1007262a459d2) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.21) ## v1.0.20 - Minimal Gossip / Go 1.11 Fix / Utility Improvements Published 2018-07-05 09:21:34 +0000 UTC Enhancements have been made to various utilities including `scanner`, `sctcheck`, `loglist` and `x509util`. The `allow_verification_with_non_compliant_keys` flag has been removed from `signatures.go`. An implementation of Gossip has been added. See the `gossip/minimal` package for more information. An X.509 compatibility issue for Go 1.11 has been fixed. This should be backwards compatible with 1.10. Commit [37a384cd035e722ea46e55029093e26687138edf](https://api.github.com/repos/google/certificate-transparency-go/commits/37a384cd035e722ea46e55029093e26687138edf) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.20) ## v1.0.19 - CTFE User Quota Published 2018-06-01 13:51:52 +0000 UTC CTFE now supports Trillian Log's explicit quota API; quota can be requested based on the remote user's IP, as well as per-issuing certificate in submitted chains. Commit [8736a411b4ff214ea20687e46c2b67d66ebd83fc](https://api.github.com/repos/google/certificate-transparency-go/commits/8736a411b4ff214ea20687e46c2b67d66ebd83fc) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.19) ## v1.0.18 - Adding Migration Tool / Client Additions / K8 Config Published 2018-06-01 14:28:20 +0000 UTC Work on a log migration tool (Migrillian) is in progress. This is not yet ready for production use but will provide features for mirroring and migrating logs. The `RequestLog` API allows for logging of SCTs when they are issued by CTFE. The CT Go client now supports `GetEntryAndProof`. Utilities have been switched over to use the `glog` package. Commit [77abf2dac5410a62c04ac1c662c6d0fa54afc2dc](https://api.github.com/repos/google/certificate-transparency-go/commits/77abf2dac5410a62c04ac1c662c6d0fa54afc2dc) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.18) ## v1.0.17 - Merkle verification / Tracing / Demo script / CORS Published 2018-06-01 14:25:16 +0000 UTC Now uses Merkle Tree verification from Trillian. The CT server now supports CORS. Request tracing added using OpenCensus. For GCE / K8 it just requires the flag to be enabled to export traces to Stackdriver. Other environments may differ. A demo script was added that goes through setting up a simple deployment suitable for development / demo purposes. This may be useful for those new to the project. Commit [3c3d22ce946447d047a03228ebb4a41e3e4eb15b](https://api.github.com/repos/google/certificate-transparency-go/commits/3c3d22ce946447d047a03228ebb4a41e3e4eb15b) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.17) ## v1.0.16 - Lifecycle test / Go 1.10.1 Published 2018-06-01 14:22:23 +0000 UTC An integration test was added that goes through a create / drain queue / freeze lifecycle for a log. Changes to `x509` were merged from Go 1.10.1. Commit [a72423d09b410b80673fd1135ba1022d04bac6cd](https://api.github.com/repos/google/certificate-transparency-go/commits/a72423d09b410b80673fd1135ba1022d04bac6cd) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.16) ## v1.0.15 - More control of verification, grpclb, stackdriver metrics Published 2018-06-01 14:20:32 +0000 UTC Facilities were added to the `x509` package to control whether verification checks are applied. Log server requests are now balanced using `gRPClb`. For Kubernetes, metrics can be published to Stackdriver monitoring. Commit [684d6eee6092774e54d301ccad0ed61bc8d010c1](https://api.github.com/repos/google/certificate-transparency-go/commits/684d6eee6092774e54d301ccad0ed61bc8d010c1) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.15) ## v1.0.14 - SQLite Removed, LeafHashForLeaf Published 2018-06-01 14:15:37 +0000 UTC Support for SQLite was removed. This motivation was ongoing test flakiness caused by multi-user access. This database may work for an embedded scenario but is not suitable for use in a server environment. A `LeafHashForLeaf` client API was added and is now used by the CT client and integration tests. Commit [698cd6a661196db4b2e71437422178ffe8705006](https://api.github.com/repos/google/certificate-transparency-go/commits/698cd6a661196db4b2e71437422178ffe8705006) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.14) ## v1.0.13 - Crypto changes, util updates, sync with trillian repo, loglist verification Published 2018-06-01 14:15:21 +0000 UTC Some of our custom crypto package that were wrapping calls to the standard package have been removed and the base features used directly. Updates were made to GCE ingress and health checks. The log list utility can verify signatures. Commit [480c3654a70c5383b9543ec784203030aedbd3a5](https://api.github.com/repos/google/certificate-transparency-go/commits/480c3654a70c5383b9543ec784203030aedbd3a5) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.13) ## v1.0.12 - Client / util updates & CTFE fixes Published 2018-06-01 14:13:42 +0000 UTC The CT client can now use a JSON loglist to find logs. CTFE had a fix applied for preissued precerts. A DNS client was added and CT client was extended to support DNS retrieval. Commit [74c06c95e0b304a050a1c33764c8a01d653a16e3](https://api.github.com/repos/google/certificate-transparency-go/commits/74c06c95e0b304a050a1c33764c8a01d653a16e3) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.12) ## v1.0.11 - Kubernetes CI / Integration fixes Published 2018-06-01 14:12:18 +0000 UTC Updates to Kubernetes configs, mostly related to running a CI instance. Commit [0856acca7e0ab7f082ae83a1fbb5d21160962efc](https://api.github.com/repos/google/certificate-transparency-go/commits/0856acca7e0ab7f082ae83a1fbb5d21160962efc) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.11) ## v1.0.10 - More scanner, x509, utility and client fixes. CTFE updates Published 2018-06-01 14:09:47 +0000 UTC The CT client was using the wrong protobuffer library package. To guard against this in future a check has been added to our lint config. The `x509` and `asn1` packages have had upstream fixes applied from Go 1.10rc1. Commit [1bec4527572c443752ad4f2830bef88be0533236](https://api.github.com/repos/google/certificate-transparency-go/commits/1bec4527572c443752ad4f2830bef88be0533236) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.10) ## v1.0.9 - Scanner, x509, utility and client fixes Published 2018-06-01 14:11:13 +0000 UTC The `scanner` utility now displays throughput stats. Build instructions and README files were updated. The `certcheck` utility can be told to ignore unknown critical X.509 extensions. Commit [c06833528d04a94eed0c775104d1107bab9ae17c](https://api.github.com/repos/google/certificate-transparency-go/commits/c06833528d04a94eed0c775104d1107bab9ae17c) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.9) ## v1.0.8 - Client fixes, align with trillian repo Published 2018-06-01 14:06:44 +0000 UTC Commit [e8b02c60f294b503dbb67de0868143f5d4935e56](https://api.github.com/repos/google/certificate-transparency-go/commits/e8b02c60f294b503dbb67de0868143f5d4935e56) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.8) ## v1.0.7 - CTFE fixes Published 2018-06-01 14:06:13 +0000 UTC An issue was fixed with CTFE signature caching. In an unlikely set of circumstances this could lead to log mis-operation. While the chances of this are small, we recommend that versions prior to this one are not deployed. Commit [52c0590bd3b4b80c5497005b0f47e10557425eeb](https://api.github.com/repos/google/certificate-transparency-go/commits/52c0590bd3b4b80c5497005b0f47e10557425eeb) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.7) ## v1.0.6 - crlcheck improvements / other fixes Published 2018-06-01 14:04:22 +0000 UTC The `crlcheck` utility has had several fixes and enhancements. Additionally the `hammer` now supports temporal logs. Commit [3955e4a00c42e83ff17ce25003976159c5d0f0f9](https://api.github.com/repos/google/certificate-transparency-go/commits/3955e4a00c42e83ff17ce25003976159c5d0f0f9) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.6) ## v1.0.5 - X509 and asn1 fixes Published 2018-06-01 14:02:58 +0000 UTC This release is mostly fixes to the `x509` and `asn1` packages. Some command line utilities were also updated. Commit [ae40d07cce12f1227c6e658e61c9dddb7646f97b](https://api.github.com/repos/google/certificate-transparency-go/commits/ae40d07cce12f1227c6e658e61c9dddb7646f97b) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.5) ## v1.0.4 - Multi log backend configs Published 2018-06-01 14:02:07 +0000 UTC Support was added to allow CTFE to use multiple backends, each serving a distinct set of logs. It allows for e.g. regional backend deployment with common frontend servers. Commit [62023ed90b41fa40854957b5dec7d9d73594723f](https://api.github.com/repos/google/certificate-transparency-go/commits/62023ed90b41fa40854957b5dec7d9d73594723f) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.4) ## v1.0.3 - Hammer updates, use standard context Published 2018-06-01 14:01:11 +0000 UTC After the Go 1.9 migration references to anything other than the standard `context` package have been removed. This is the only one that should be used from now on. Commit [b28beed8b9aceacc705e0ff4a11d435a310e3d97](https://api.github.com/repos/google/certificate-transparency-go/commits/b28beed8b9aceacc705e0ff4a11d435a310e3d97) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.3) ## v1.0.2 - Go 1.9 Published 2018-06-01 14:00:00 +0000 UTC Go 1.9 is now required to build the code. Commit [3aed33d672ee43f04b1e8a00b25ca3e2e2e74309](https://api.github.com/repos/google/certificate-transparency-go/commits/3aed33d672ee43f04b1e8a00b25ca3e2e2e74309) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.2) ## v1.0.1 - Hammer and client improvements Published 2018-06-01 13:59:29 +0000 UTC Commit [c28796cc21776667fb05d6300e32d9517be96515](https://api.github.com/repos/google/certificate-transparency-go/commits/c28796cc21776667fb05d6300e32d9517be96515) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0.1) ## v1.0 - First Trillian CT Release Published 2018-06-01 13:59:00 +0000 UTC This is the point that corresponds to the 1.0 release in the trillian repo. Commit [abb79e468b6f3bbd48d1ab0c9e68febf80d52c4d](https://api.github.com/repos/google/certificate-transparency-go/commits/abb79e468b6f3bbd48d1ab0c9e68febf80d52c4d) Download [zip](https://api.github.com/repos/google/certificate-transparency-go/zipball/v1.0) google-certificate-transparency-go-2308f62/CODEOWNERS000066400000000000000000000000431462611535200222310ustar00rootroot00000000000000* @google/certificate-transparency google-certificate-transparency-go-2308f62/CONTRIBUTING.md000066400000000000000000000046651462611535200231050ustar00rootroot00000000000000# How to contribute # We'd love to accept your patches and contributions to this project. There are a just a few small guidelines you need to follow. ## Contributor License Agreement ## Contributions to any Google project must be accompanied by a Contributor License Agreement. This is not a copyright **assignment**, it simply gives Google permission to use and redistribute your contributions as part of the project. * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA][]. * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA][]. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. [individual CLA]: https://developers.google.com/open-source/cla/individual [corporate CLA]: https://developers.google.com/open-source/cla/corporate Once your CLA is submitted (or if you already submitted one for another Google project), make a commit adding yourself to the [AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part of your first [pull request][]. [AUTHORS]: AUTHORS [CONTRIBUTORS]: CONTRIBUTORS ## Submitting a patch ## 1. It's generally best to start by opening a new issue describing the bug or feature you're intending to fix. Even if you think it's relatively minor, it's helpful to know what people are working on. Mention in the initial issue that you are planning to work on that bug or feature so that it can be assigned to you. 1. Follow the normal process of [forking][] the project, and setup a new branch to work in. It's important that each group of changes be done in separate branches in order to ensure that a pull request only includes the commits related to that bug or feature. 1. Do your best to have [well-formed commit messages][] for each change. This provides consistency throughout the project, and ensures that commit messages are able to be formatted properly by various git tools. 1. Finally, push the commits to your fork and submit a [pull request][]. [forking]: https://help.github.com/articles/fork-a-repo [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [pull request]: https://help.github.com/articles/creating-a-pull-request google-certificate-transparency-go-2308f62/CONTRIBUTORS000066400000000000000000000050121462611535200225170ustar00rootroot00000000000000# People who have agreed to one of the CLAs and can contribute patches. # The AUTHORS file lists the copyright holders; this file # lists people. For example, Google employees are listed here # but not in AUTHORS, because Google holds the copyright. # # Names should be added to this file only after verifying that # the individual or the individual's organization has agreed to # the appropriate Contributor License Agreement, found here: # # https://developers.google.com/open-source/cla/individual # https://developers.google.com/open-source/cla/corporate # # The agreement for individuals can be filled out on the web. # # When adding J Random Contributor's name to this file, # either J's name or J's organization's name should be # added to the AUTHORS file, depending on whether the # individual or corporate CLA was used. # # Names should be added to this file as: # Name # # Please keep the list sorted. Adam Eijdenberg Al Cutter Alex Cohn Ben Laurie Chris Kennelly David Drysdale Deyan Bektchiev Ed Maste Elisha Silas Emilia Kasper Eran Messeri Fiaz Hossain Gary Belvin Jeff Trawick Joe Tsai Kat Joyce Katriel Cohn-Gordon Kiril Nikolov Konrad Kraszewski Laël Cellier Linus Nordberg Mark Schloesser Nicholas Galbreath Oliver Weidner Pascal Leroy Paul Hadfield Paul Lietar Pavel Kalinnikov Pierre Phaneuf Rob Percival Rob Stradling Roger Ng Roland Shoemaker Ruslan Kovalov Samuel Lidén Borell Tatiana Merkulova Vladimir Rutsky Ximin Luo google-certificate-transparency-go-2308f62/LICENSE000066400000000000000000000261361462611535200216560ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. google-certificate-transparency-go-2308f62/PULL_REQUEST_TEMPLATE.md000066400000000000000000000011731462611535200244440ustar00rootroot00000000000000 ### Checklist - [ ] I have updated the [CHANGELOG](CHANGELOG.md). - Adjust the draft version number according to [semantic versioning](https://semver.org/) rules. - [ ] I have updated [documentation](docs/) accordingly. google-certificate-transparency-go-2308f62/README.md000066400000000000000000000126071462611535200221260ustar00rootroot00000000000000# Certificate Transparency: Go Code [![Go Report Card](https://goreportcard.com/badge/github.com/google/certificate-transparency-go)](https://goreportcard.com/report/github.com/google/certificate-transparency-go) [![GoDoc](https://godoc.org/github.com/google/certificate-transparency-go?status.svg)](https://godoc.org/github.com/google/certificate-transparency-go) ![CodeQL workflow](https://github.com/google/certificate-transparency-go/actions/workflows/codeql.yml/badge.svg) This repository holds Go code related to [Certificate Transparency](https://www.certificate-transparency.org/) (CT). The repository requires Go version 1.21. - [Repository Structure](#repository-structure) - [Trillian CT Personality](#trillian-ct-personality) - [Working on the Code](#working-on-the-code) - [Running Codebase Checks](#running-codebase-checks) - [Rebuilding Generated Code](#rebuilding-generated-code) ## Support - Slack: https://transparency-dev.slack.com/ ([invitation](https://join.slack.com/t/transparency-dev/shared_invite/zt-27pkqo21d-okUFhur7YZ0rFoJVIOPznQ)) ## Repository Structure The main parts of the repository are: - Encoding libraries: - `asn1/` and `x509/` are forks of the upstream Go `encoding/asn1` and `crypto/x509` libraries. We maintain separate forks of these packages because CT is intended to act as an observatory of certificates across the ecosystem; as such, we need to be able to process somewhat-malformed certificates that the stricter upstream code would (correctly) reject. Our `x509` fork also includes code for working with the [pre-certificates defined in RFC 6962](https://tools.ietf.org/html/rfc6962#section-3.1). - `tls` holds a library for processing TLS-encoded data as described in [RFC 5246](https://tools.ietf.org/html/rfc5246). - `x509util/` provides additional utilities for dealing with `x509.Certificate`s. - CT client libraries: - The top-level `ct` package (in `.`) holds types and utilities for working with CT data structures defined in [RFC 6962](https://tools.ietf.org/html/rfc6962). - `client/` and `jsonclient/` hold libraries that allow access to CT Logs via HTTP entrypoints described in [section 4 of RFC 6962](https://tools.ietf.org/html/rfc6962#section-4). - `dnsclient/` has a library that allows access to CT Logs over [DNS](https://github.com/google/certificate-transparency-rfcs/blob/master/dns/draft-ct-over-dns.md). - `scanner/` holds a library for scanning the entire contents of an existing CT Log. - CT Personality for [Trillian](https://github.com/google/trillian): - `trillian/` holds code that allows a Certificate Transparency Log to be run using a Trillian Log as its back-end -- see [below](#trillian-ct-personality). - Command line tools: - `./client/ctclient` allows interaction with a CT Log. - `./ctutil/sctcheck` allows SCTs (signed certificate timestamps) from a CT Log to be verified. - `./scanner/scanlog` allows an existing CT Log to be scanned for certificates of interest; please be polite when running this tool against a Log. - `./x509util/certcheck` allows display and verification of certificates - `./x509util/crlcheck` allows display and verification of certificate revocation lists (CRLs). - Other libraries related to CT: - `ctutil/` holds utility functions for validating and verifying CT data structures. - `loglist3/` has a library for reading [v3 JSON lists of CT Logs](https://groups.google.com/a/chromium.org/g/ct-policy/c/IdbrdAcDQto/m/i5KPyzYwBAAJ). ## Trillian CT Personality The `trillian/` subdirectory holds code and scripts for running a CT Log based on the [Trillian](https://github.com/google/trillian) general transparency Log, and is [documented separately](trillian/README.md). ## Working on the Code Developers who want to make changes to the codebase need some additional dependencies and tools, described in the following sections. ### Running Codebase Checks The [`scripts/presubmit.sh`](scripts/presubmit.sh) script runs various tools and tests over the codebase; please ensure this script passes before sending pull requests for review. ```bash # Install golangci-lint go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.1 # Run code generation, build, test and linters ./scripts/presubmit.sh # Run build, test and linters but skip code generation ./scripts/presubmit.sh --no-generate # Or just run the linters alone: golangci-lint run ``` ### Rebuilding Generated Code Some of the CT Go code is autogenerated from other files: - [Protocol buffer](https://developers.google.com/protocol-buffers/) message definitions are converted to `.pb.go` implementations. - A mock implementation of the Trillian gRPC API (in `trillian/mockclient`) is created with [GoMock](https://github.com/golang/mock). Re-generating mock or protobuffer files is only needed if you're changing the original files; if you do, you'll need to install the prerequisites: - tools written in `go` can be installed with a single run of `go install` (courtesy of [`tools.go`](./tools/tools.go) and `go.mod`). - `protoc` tool: you'll need [version 3.20.1](https://github.com/protocolbuffers/protobuf/releases/tag/v3.20.1) installed, and `PATH` updated to include its `bin/` directory. With tools installed, run the following: ```bash go generate -x ./... # hunts for //go:generate comments and runs them ``` google-certificate-transparency-go-2308f62/asn1/000077500000000000000000000000001462611535200215035ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/asn1/README.md000066400000000000000000000003721462611535200227640ustar00rootroot00000000000000# Important Notice This is a fork of the `encoding/asn1` Go package. The original source can be found on [GitHub](https://github.com/golang/go). Be careful about making local modifications to this code as it will make maintenance harder in future. google-certificate-transparency-go-2308f62/asn1/asn1.go000066400000000000000000001055471462611535200227100ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package asn1 implements parsing of DER-encoded ASN.1 data structures, // as defined in ITU-T Rec X.690. // // See also “A Layman's Guide to a Subset of ASN.1, BER, and DER,” // http://luca.ntop.org/Teaching/Appunti/asn1.html. // // This is a fork of the Go standard library ASN.1 implementation // (encoding/asn1), with the aim of relaxing checks for various things // that are common errors present in many X.509 certificates in the // wild. // // Main differences: // - Extra "lax" tag that recursively applies and relaxes some strict // checks: // - parsePrintableString() copes with invalid PrintableString contents, // e.g. use of tagPrintableString when the string data is really // ISO8859-1. // - checkInteger() allows integers that are not minimally encoded (and // so are not correct DER). // - parseObjectIdentifier() allows zero-length OIDs. // - Better diagnostics on which particular field causes errors. package asn1 // ASN.1 is a syntax for specifying abstract objects and BER, DER, PER, XER etc // are different encoding formats for those objects. Here, we'll be dealing // with DER, the Distinguished Encoding Rules. DER is used in X.509 because // it's fast to parse and, unlike BER, has a unique encoding for every object. // When calculating hashes over objects, it's important that the resulting // bytes be the same at both ends and DER removes this margin of error. // // ASN.1 is very complex and this package doesn't attempt to implement // everything by any means. import ( "errors" "fmt" "math" "math/big" "reflect" "strconv" "time" "unicode/utf16" "unicode/utf8" ) // A StructuralError suggests that the ASN.1 data is valid, but the Go type // which is receiving it doesn't match. type StructuralError struct { Msg string Field string } func (e StructuralError) Error() string { var prefix string if e.Field != "" { prefix = e.Field + ": " } return "asn1: structure error: " + prefix + e.Msg } // A SyntaxError suggests that the ASN.1 data is invalid. type SyntaxError struct { Msg string Field string } func (e SyntaxError) Error() string { var prefix string if e.Field != "" { prefix = e.Field + ": " } return "asn1: syntax error: " + prefix + e.Msg } // We start by dealing with each of the primitive types in turn. // BOOLEAN func parseBool(bytes []byte, fieldName string) (ret bool, err error) { if len(bytes) != 1 { err = SyntaxError{"invalid boolean", fieldName} return } // DER demands that "If the encoding represents the boolean value TRUE, // its single contents octet shall have all eight bits set to one." // Thus only 0 and 255 are valid encoded values. switch bytes[0] { case 0: ret = false case 0xff: ret = true default: err = SyntaxError{"invalid boolean", fieldName} } return } // INTEGER // checkInteger returns nil if the given bytes are a valid DER-encoded // INTEGER and an error otherwise. func checkInteger(bytes []byte, lax bool, fieldName string) error { if len(bytes) == 0 { return StructuralError{"empty integer", fieldName} } if len(bytes) == 1 { return nil } if lax { return nil } if (bytes[0] == 0 && bytes[1]&0x80 == 0) || (bytes[0] == 0xff && bytes[1]&0x80 == 0x80) { return StructuralError{"integer not minimally-encoded", fieldName} } return nil } // parseInt64 treats the given bytes as a big-endian, signed integer and // returns the result. func parseInt64(bytes []byte, lax bool, fieldName string) (ret int64, err error) { err = checkInteger(bytes, lax, fieldName) if err != nil { return } if len(bytes) > 8 { // We'll overflow an int64 in this case. err = StructuralError{"integer too large", fieldName} return } for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { ret <<= 8 ret |= int64(bytes[bytesRead]) } // Shift up and down in order to sign extend the result. ret <<= 64 - uint8(len(bytes))*8 ret >>= 64 - uint8(len(bytes))*8 return } // parseInt treats the given bytes as a big-endian, signed integer and returns // the result. func parseInt32(bytes []byte, lax bool, fieldName string) (int32, error) { if err := checkInteger(bytes, lax, fieldName); err != nil { return 0, err } ret64, err := parseInt64(bytes, lax, fieldName) if err != nil { return 0, err } if ret64 != int64(int32(ret64)) { return 0, StructuralError{"integer too large", fieldName} } return int32(ret64), nil } var bigOne = big.NewInt(1) // parseBigInt treats the given bytes as a big-endian, signed integer and returns // the result. func parseBigInt(bytes []byte, lax bool, fieldName string) (*big.Int, error) { if err := checkInteger(bytes, lax, fieldName); err != nil { return nil, err } ret := new(big.Int) if len(bytes) > 0 && bytes[0]&0x80 == 0x80 { // This is a negative number. notBytes := make([]byte, len(bytes)) for i := range notBytes { notBytes[i] = ^bytes[i] } ret.SetBytes(notBytes) ret.Add(ret, bigOne) ret.Neg(ret) return ret, nil } ret.SetBytes(bytes) return ret, nil } // BIT STRING // BitString is the structure to use when you want an ASN.1 BIT STRING type. A // bit string is padded up to the nearest byte in memory and the number of // valid bits is recorded. Padding bits will be zero. type BitString struct { Bytes []byte // bits packed into bytes. BitLength int // length in bits. } // At returns the bit at the given index. If the index is out of range it // returns false. func (b BitString) At(i int) int { if i < 0 || i >= b.BitLength { return 0 } x := i / 8 y := 7 - uint(i%8) return int(b.Bytes[x]>>y) & 1 } // RightAlign returns a slice where the padding bits are at the beginning. The // slice may share memory with the BitString. func (b BitString) RightAlign() []byte { shift := uint(8 - (b.BitLength % 8)) if shift == 8 || len(b.Bytes) == 0 { return b.Bytes } a := make([]byte, len(b.Bytes)) a[0] = b.Bytes[0] >> shift for i := 1; i < len(b.Bytes); i++ { a[i] = b.Bytes[i-1] << (8 - shift) a[i] |= b.Bytes[i] >> shift } return a } // parseBitString parses an ASN.1 bit string from the given byte slice and returns it. func parseBitString(bytes []byte, fieldName string) (ret BitString, err error) { if len(bytes) == 0 { err = SyntaxError{"zero length BIT STRING", fieldName} return } paddingBits := int(bytes[0]) if paddingBits > 7 || len(bytes) == 1 && paddingBits > 0 || bytes[len(bytes)-1]&((1< 0 { s += "." } s += strconv.Itoa(v) } return s } // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and // returns it. An object identifier is a sequence of variable length integers // that are assigned in a hierarchy. func parseObjectIdentifier(bytes []byte, lax bool, fieldName string) (s ObjectIdentifier, err error) { if len(bytes) == 0 { if lax { return ObjectIdentifier{}, nil } err = SyntaxError{"zero length OBJECT IDENTIFIER", fieldName} return } // In the worst case, we get two elements from the first byte (which is // encoded differently) and then every varint is a single byte long. s = make([]int, len(bytes)+1) // The first varint is 40*value1 + value2: // According to this packing, value1 can take the values 0, 1 and 2 only. // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2, // then there are no restrictions on value2. v, offset, err := parseBase128Int(bytes, 0, fieldName) if err != nil { return } if v < 80 { s[0] = v / 40 s[1] = v % 40 } else { s[0] = 2 s[1] = v - 80 } i := 2 for ; offset < len(bytes); i++ { v, offset, err = parseBase128Int(bytes, offset, fieldName) if err != nil { return } s[i] = v } s = s[0:i] return } // ENUMERATED // An Enumerated is represented as a plain int. type Enumerated int // FLAG // A Flag accepts any data and is set to true if present. type Flag bool // parseBase128Int parses a base-128 encoded int from the given offset in the // given byte slice. It returns the value and the new offset. func parseBase128Int(bytes []byte, initOffset int, fieldName string) (ret, offset int, err error) { offset = initOffset var ret64 int64 for shifted := 0; offset < len(bytes); shifted++ { // 5 * 7 bits per byte == 35 bits of data // Thus the representation is either non-minimal or too large for an int32 if shifted == 5 { err = StructuralError{"base 128 integer too large", fieldName} return } ret64 <<= 7 b := bytes[offset] ret64 |= int64(b & 0x7f) offset++ if b&0x80 == 0 { ret = int(ret64) // Ensure that the returned value fits in an int on all platforms if ret64 > math.MaxInt32 { err = StructuralError{"base 128 integer too large", fieldName} } return } } err = SyntaxError{"truncated base 128 integer", fieldName} return } // UTCTime func parseUTCTime(bytes []byte) (ret time.Time, err error) { s := string(bytes) formatStr := "0601021504Z0700" ret, err = time.Parse(formatStr, s) if err != nil { formatStr = "060102150405Z0700" ret, err = time.Parse(formatStr, s) } if err != nil { return } if serialized := ret.Format(formatStr); serialized != s { err = fmt.Errorf("asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %q", s, serialized) return } if ret.Year() >= 2050 { // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 ret = ret.AddDate(-100, 0, 0) } return } // parseGeneralizedTime parses the GeneralizedTime from the given byte slice // and returns the resulting time. func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) { const formatStr = "20060102150405Z0700" s := string(bytes) if ret, err = time.Parse(formatStr, s); err != nil { return } if serialized := ret.Format(formatStr); serialized != s { err = fmt.Errorf("asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %q", s, serialized) } return } // NumericString // parseNumericString parses an ASN.1 NumericString from the given byte array // and returns it. func parseNumericString(bytes []byte, fieldName string) (ret string, err error) { for _, b := range bytes { if !isNumeric(b) { return "", SyntaxError{"NumericString contains invalid character", fieldName} } } return string(bytes), nil } // isNumeric reports whether the given b is in the ASN.1 NumericString set. func isNumeric(b byte) bool { return '0' <= b && b <= '9' || b == ' ' } // PrintableString // parsePrintableString parses an ASN.1 PrintableString from the given byte // array and returns it. func parsePrintableString(bytes []byte, lax bool, fieldName string) (ret string, err error) { for _, b := range bytes { if !isPrintable(b, allowAsterisk, allowAmpersand) { if !lax { err = SyntaxError{"PrintableString contains invalid character", fieldName} } else { // Might be an ISO8859-1 string stuffed in, check if it // would be valid and assume that's what's happened if so, // otherwise try T.61, failing that give up and just assign // the bytes switch { case couldBeISO8859_1(bytes): ret, err = iso8859_1ToUTF8(bytes), nil case couldBeT61(bytes): ret, err = parseT61String(bytes) default: err = SyntaxError{"PrintableString contains invalid character, couldn't determine correct String type", fieldName} } } return } } ret = string(bytes) return } type asteriskFlag bool type ampersandFlag bool const ( allowAsterisk asteriskFlag = true rejectAsterisk asteriskFlag = false allowAmpersand ampersandFlag = true rejectAmpersand ampersandFlag = false ) // isPrintable reports whether the given b is in the ASN.1 PrintableString set. // If asterisk is allowAsterisk then '*' is also allowed, reflecting existing // practice. If ampersand is allowAmpersand then '&' is allowed as well. func isPrintable(b byte, asterisk asteriskFlag, ampersand ampersandFlag) bool { return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || '0' <= b && b <= '9' || '\'' <= b && b <= ')' || '+' <= b && b <= '/' || b == ' ' || b == ':' || b == '=' || b == '?' || // This is technically not allowed in a PrintableString. // However, x509 certificates with wildcard strings don't // always use the correct string type so we permit it. (bool(asterisk) && b == '*') || // This is not technically allowed either. However, not // only is it relatively common, but there are also a // handful of CA certificates that contain it. At least // one of which will not expire until 2027. (bool(ampersand) && b == '&') } // IA5String // parseIA5String parses an ASN.1 IA5String (ASCII string) from the given // byte slice and returns it. func parseIA5String(bytes []byte, fieldName string) (ret string, err error) { for _, b := range bytes { if b >= utf8.RuneSelf { err = SyntaxError{"IA5String contains invalid character", fieldName} return } } ret = string(bytes) return } // T61String // parseT61String parses an ASN.1 T61String (8-bit clean string) from the given // byte slice and returns it. func parseT61String(bytes []byte) (ret string, err error) { return string(bytes), nil } // UTF8String // parseUTF8String parses an ASN.1 UTF8String (raw UTF-8) from the given byte // array and returns it. func parseUTF8String(bytes []byte) (ret string, err error) { if !utf8.Valid(bytes) { return "", errors.New("asn1: invalid UTF-8 string") } return string(bytes), nil } // BMPString // parseBMPString parses an ASN.1 BMPString (Basic Multilingual Plane of // ISO/IEC/ITU 10646-1) from the given byte slice and returns it. func parseBMPString(bmpString []byte) (string, error) { if len(bmpString)%2 != 0 { return "", errors.New("pkcs12: odd-length BMP string") } // Strip terminator if present. if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { bmpString = bmpString[:l-2] } s := make([]uint16, 0, len(bmpString)/2) for len(bmpString) > 0 { s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) bmpString = bmpString[2:] } return string(utf16.Decode(s)), nil } // A RawValue represents an undecoded ASN.1 object. type RawValue struct { Class, Tag int IsCompound bool Bytes []byte FullBytes []byte // includes the tag and length } // RawContent is used to signal that the undecoded, DER data needs to be // preserved for a struct. To use it, the first field of the struct must have // this type. It's an error for any of the other fields to have this type. type RawContent []byte // Tagging // parseTagAndLength parses an ASN.1 tag and length pair from the given offset // into a byte slice. It returns the parsed data and the new offset. SET and // SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we // don't distinguish between ordered and unordered objects in this code. func parseTagAndLength(bytes []byte, initOffset int, fieldName string) (ret tagAndLength, offset int, err error) { offset = initOffset // parseTagAndLength should not be called without at least a single // byte to read. Thus this check is for robustness: if offset >= len(bytes) { err = errors.New("asn1: internal error in parseTagAndLength") return } b := bytes[offset] offset++ ret.class = int(b >> 6) ret.isCompound = b&0x20 == 0x20 ret.tag = int(b & 0x1f) // If the bottom five bits are set, then the tag number is actually base 128 // encoded afterwards if ret.tag == 0x1f { ret.tag, offset, err = parseBase128Int(bytes, offset, fieldName) if err != nil { return } // Tags should be encoded in minimal form. if ret.tag < 0x1f { err = SyntaxError{"non-minimal tag", fieldName} return } } if offset >= len(bytes) { err = SyntaxError{"truncated tag or length", fieldName} return } b = bytes[offset] offset++ if b&0x80 == 0 { // The length is encoded in the bottom 7 bits. ret.length = int(b & 0x7f) } else { // Bottom 7 bits give the number of length bytes to follow. numBytes := int(b & 0x7f) if numBytes == 0 { err = SyntaxError{"indefinite length found (not DER)", fieldName} return } ret.length = 0 for i := 0; i < numBytes; i++ { if offset >= len(bytes) { err = SyntaxError{"truncated tag or length", fieldName} return } b = bytes[offset] offset++ if ret.length >= 1<<23 { // We can't shift ret.length up without // overflowing. err = StructuralError{"length too large", fieldName} return } ret.length <<= 8 ret.length |= int(b) if ret.length == 0 { // DER requires that lengths be minimal. err = StructuralError{"superfluous leading zeros in length", fieldName} return } } // Short lengths must be encoded in short form. if ret.length < 0x80 { err = StructuralError{"non-minimal length", fieldName} return } } return } // parseSequenceOf is used for SEQUENCE OF and SET OF values. It tries to parse // a number of ASN.1 values from the given byte slice and returns them as a // slice of Go values of the given type. func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type, lax bool, fieldName string) (ret reflect.Value, err error) { matchAny, expectedTag, compoundType, ok := getUniversalType(elemType) if !ok { err = StructuralError{"unknown Go type for slice", fieldName} return } // First we iterate over the input and count the number of elements, // checking that the types are correct in each case. numElements := 0 for offset := 0; offset < len(bytes); { var t tagAndLength t, offset, err = parseTagAndLength(bytes, offset, fieldName) if err != nil { return } switch t.tag { case TagIA5String, TagGeneralString, TagT61String, TagUTF8String, TagNumericString, TagBMPString: // We pretend that various other string types are // PRINTABLE STRINGs so that a sequence of them can be // parsed into a []string. t.tag = TagPrintableString case TagGeneralizedTime, TagUTCTime: // Likewise, both time types are treated the same. t.tag = TagUTCTime } if !matchAny && (t.class != ClassUniversal || t.isCompound != compoundType || t.tag != expectedTag) { err = StructuralError{fmt.Sprintf("sequence tag mismatch (got:%+v, want:0/%d/%t)", t, expectedTag, compoundType), fieldName} return } if invalidLength(offset, t.length, len(bytes)) { err = SyntaxError{"truncated sequence", fieldName} return } offset += t.length numElements++ } ret = reflect.MakeSlice(sliceType, numElements, numElements) params := fieldParameters{lax: lax} offset := 0 for i := 0; i < numElements; i++ { offset, err = parseField(ret.Index(i), bytes, offset, params) if err != nil { return } } return } var ( bitStringType = reflect.TypeOf(BitString{}) objectIdentifierType = reflect.TypeOf(ObjectIdentifier{}) enumeratedType = reflect.TypeOf(Enumerated(0)) flagType = reflect.TypeOf(Flag(false)) timeType = reflect.TypeOf(time.Time{}) rawValueType = reflect.TypeOf(RawValue{}) rawContentsType = reflect.TypeOf(RawContent(nil)) bigIntType = reflect.TypeOf(new(big.Int)) ) // invalidLength reports whether offset + length > sliceLength, or if the // addition would overflow. func invalidLength(offset, length, sliceLength int) bool { return offset+length < offset || offset+length > sliceLength } // Tests whether the data in |bytes| would be a valid ISO8859-1 string. // Clearly, a sequence of bytes comprised solely of valid ISO8859-1 // codepoints does not imply that the encoding MUST be ISO8859-1, rather that // you would not encounter an error trying to interpret the data as such. func couldBeISO8859_1(bytes []byte) bool { for _, b := range bytes { if b < 0x20 || (b >= 0x7F && b < 0xA0) { return false } } return true } // Checks whether the data in |bytes| would be a valid T.61 string. // Clearly, a sequence of bytes comprised solely of valid T.61 // codepoints does not imply that the encoding MUST be T.61, rather that // you would not encounter an error trying to interpret the data as such. func couldBeT61(bytes []byte) bool { for _, b := range bytes { switch b { case 0x00: // Since we're guessing at (incorrect) encodings for a // PrintableString, we'll err on the side of caution and disallow // strings with a NUL in them, don't want to re-create a PayPal NUL // situation in monitors. fallthrough case 0x23, 0x24, 0x5C, 0x5E, 0x60, 0x7B, 0x7D, 0x7E, 0xA5, 0xA6, 0xAC, 0xAD, 0xAE, 0xAF, 0xB9, 0xBA, 0xC0, 0xC9, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDE, 0xDF, 0xE5, 0xFF: // These are all invalid code points in T.61, so it can't be a T.61 string. return false } } return true } // Converts the data in |bytes| to the equivalent UTF-8 string. func iso8859_1ToUTF8(bytes []byte) string { buf := make([]rune, len(bytes)) for i, b := range bytes { buf[i] = rune(b) } return string(buf) } // parseField is the main parsing function. Given a byte slice and an offset // into the array, it will try to parse a suitable ASN.1 value out and store it // in the given Value. func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParameters) (offset int, err error) { offset = initOffset fieldType := v.Type() // If we have run out of data, it may be that there are optional elements at the end. if offset == len(bytes) { if !setDefaultValue(v, params) { err = SyntaxError{"sequence truncated", params.name} } return } // Deal with the ANY type. if ifaceType := fieldType; ifaceType.Kind() == reflect.Interface && ifaceType.NumMethod() == 0 { var t tagAndLength t, offset, err = parseTagAndLength(bytes, offset, params.name) if err != nil { return } if invalidLength(offset, t.length, len(bytes)) { err = SyntaxError{"data truncated", params.name} return } var result interface{} if !t.isCompound && t.class == ClassUniversal { innerBytes := bytes[offset : offset+t.length] switch t.tag { case TagPrintableString: result, err = parsePrintableString(innerBytes, params.lax, params.name) case TagNumericString: result, err = parseNumericString(innerBytes, params.name) case TagIA5String: result, err = parseIA5String(innerBytes, params.name) case TagT61String: result, err = parseT61String(innerBytes) case TagUTF8String: result, err = parseUTF8String(innerBytes) case TagInteger: result, err = parseInt64(innerBytes, params.lax, params.name) case TagBitString: result, err = parseBitString(innerBytes, params.name) case TagOID: result, err = parseObjectIdentifier(innerBytes, params.lax, params.name) case TagUTCTime: result, err = parseUTCTime(innerBytes) case TagGeneralizedTime: result, err = parseGeneralizedTime(innerBytes) case TagOctetString: result = innerBytes case TagBMPString: result, err = parseBMPString(innerBytes) default: // If we don't know how to handle the type, we just leave Value as nil. } } offset += t.length if err != nil { return } if result != nil { v.Set(reflect.ValueOf(result)) } return } t, offset, err := parseTagAndLength(bytes, offset, params.name) if err != nil { return } if params.explicit { expectedClass := ClassContextSpecific if params.application { expectedClass = ClassApplication } if offset == len(bytes) { err = StructuralError{"explicit tag has no child", params.name} return } if t.class == expectedClass && t.tag == *params.tag && (t.length == 0 || t.isCompound) { if fieldType == rawValueType { // The inner element should not be parsed for RawValues. } else if t.length > 0 { t, offset, err = parseTagAndLength(bytes, offset, params.name) if err != nil { return } } else { if fieldType != flagType { err = StructuralError{"zero length explicit tag was not an asn1.Flag", params.name} return } v.SetBool(true) return } } else { // The tags didn't match, it might be an optional element. ok := setDefaultValue(v, params) if ok { offset = initOffset } else { err = StructuralError{"explicitly tagged member didn't match", params.name} } return } } matchAny, universalTag, compoundType, ok1 := getUniversalType(fieldType) if !ok1 { err = StructuralError{fmt.Sprintf("unknown Go type: %v", fieldType), params.name} return } // Special case for strings: all the ASN.1 string types map to the Go // type string. getUniversalType returns the tag for PrintableString // when it sees a string, so if we see a different string type on the // wire, we change the universal type to match. if universalTag == TagPrintableString { if t.class == ClassUniversal { switch t.tag { case TagIA5String, TagGeneralString, TagT61String, TagUTF8String, TagNumericString, TagBMPString: universalTag = t.tag } } else if params.stringType != 0 { universalTag = params.stringType } } // Special case for time: UTCTime and GeneralizedTime both map to the // Go type time.Time. if universalTag == TagUTCTime && t.tag == TagGeneralizedTime && t.class == ClassUniversal { universalTag = TagGeneralizedTime } if params.set { universalTag = TagSet } matchAnyClassAndTag := matchAny expectedClass := ClassUniversal expectedTag := universalTag if !params.explicit && params.tag != nil { expectedClass = ClassContextSpecific expectedTag = *params.tag matchAnyClassAndTag = false } if !params.explicit && params.application && params.tag != nil { expectedClass = ClassApplication expectedTag = *params.tag matchAnyClassAndTag = false } if !params.explicit && params.private && params.tag != nil { expectedClass = ClassPrivate expectedTag = *params.tag matchAnyClassAndTag = false } // We have unwrapped any explicit tagging at this point. if !matchAnyClassAndTag && (t.class != expectedClass || t.tag != expectedTag) || (!matchAny && t.isCompound != compoundType) { // Tags don't match. Again, it could be an optional element. ok := setDefaultValue(v, params) if ok { offset = initOffset } else { err = StructuralError{fmt.Sprintf("tags don't match (%d vs %+v) %+v %s @%d", expectedTag, t, params, fieldType.Name(), offset), params.name} } return } if invalidLength(offset, t.length, len(bytes)) { err = SyntaxError{"data truncated", params.name} return } innerBytes := bytes[offset : offset+t.length] offset += t.length // We deal with the structures defined in this package first. switch fieldType { case rawValueType: result := RawValue{t.class, t.tag, t.isCompound, innerBytes, bytes[initOffset:offset]} v.Set(reflect.ValueOf(result)) return case objectIdentifierType: newSlice, err1 := parseObjectIdentifier(innerBytes, params.lax, params.name) v.Set(reflect.MakeSlice(v.Type(), len(newSlice), len(newSlice))) if err1 == nil { reflect.Copy(v, reflect.ValueOf(newSlice)) } err = err1 return case bitStringType: bs, err1 := parseBitString(innerBytes, params.name) if err1 == nil { v.Set(reflect.ValueOf(bs)) } err = err1 return case timeType: var time time.Time var err1 error if universalTag == TagUTCTime { time, err1 = parseUTCTime(innerBytes) } else { time, err1 = parseGeneralizedTime(innerBytes) } if err1 == nil { v.Set(reflect.ValueOf(time)) } err = err1 return case enumeratedType: parsedInt, err1 := parseInt32(innerBytes, params.lax, params.name) if err1 == nil { v.SetInt(int64(parsedInt)) } err = err1 return case flagType: v.SetBool(true) return case bigIntType: parsedInt, err1 := parseBigInt(innerBytes, params.lax, params.name) if err1 == nil { v.Set(reflect.ValueOf(parsedInt)) } err = err1 return } switch val := v; val.Kind() { case reflect.Bool: parsedBool, err1 := parseBool(innerBytes, params.name) if err1 == nil { val.SetBool(parsedBool) } err = err1 return case reflect.Int, reflect.Int32, reflect.Int64: if val.Type().Size() == 4 { parsedInt, err1 := parseInt32(innerBytes, params.lax, params.name) if err1 == nil { val.SetInt(int64(parsedInt)) } err = err1 } else { parsedInt, err1 := parseInt64(innerBytes, params.lax, params.name) if err1 == nil { val.SetInt(parsedInt) } err = err1 } return // TODO(dfc) Add support for the remaining integer types case reflect.Struct: structType := fieldType for i := 0; i < structType.NumField(); i++ { if structType.Field(i).PkgPath != "" { err = StructuralError{"struct contains unexported fields", structType.Field(i).Name} return } } if structType.NumField() > 0 && structType.Field(0).Type == rawContentsType { bytes := bytes[initOffset:offset] val.Field(0).Set(reflect.ValueOf(RawContent(bytes))) } innerOffset := 0 for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) if i == 0 && field.Type == rawContentsType { continue } innerParams := parseFieldParameters(field.Tag.Get("asn1")) innerParams.name = field.Name innerParams.lax = params.lax innerOffset, err = parseField(val.Field(i), innerBytes, innerOffset, innerParams) if err != nil { return } } // We allow extra bytes at the end of the SEQUENCE because // adding elements to the end has been used in X.509 as the // version numbers have increased. return case reflect.Slice: sliceType := fieldType if sliceType.Elem().Kind() == reflect.Uint8 { val.Set(reflect.MakeSlice(sliceType, len(innerBytes), len(innerBytes))) reflect.Copy(val, reflect.ValueOf(innerBytes)) return } newSlice, err1 := parseSequenceOf(innerBytes, sliceType, sliceType.Elem(), params.lax, params.name) if err1 == nil { val.Set(newSlice) } err = err1 return case reflect.String: var v string switch universalTag { case TagPrintableString: v, err = parsePrintableString(innerBytes, params.lax, params.name) case TagNumericString: v, err = parseNumericString(innerBytes, params.name) case TagIA5String: v, err = parseIA5String(innerBytes, params.name) case TagT61String: v, err = parseT61String(innerBytes) case TagUTF8String: v, err = parseUTF8String(innerBytes) case TagGeneralString: // GeneralString is specified in ISO-2022/ECMA-35, // A brief review suggests that it includes structures // that allow the encoding to change midstring and // such. We give up and pass it as an 8-bit string. v, err = parseT61String(innerBytes) case TagBMPString: v, err = parseBMPString(innerBytes) default: err = SyntaxError{fmt.Sprintf("internal error: unknown string type %d", universalTag), params.name} } if err == nil { val.SetString(v) } return } err = StructuralError{"unsupported: " + v.Type().String(), params.name} return } // canHaveDefaultValue reports whether k is a Kind that we will set a default // value for. (A signed integer, essentially.) func canHaveDefaultValue(k reflect.Kind) bool { switch k { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return true } return false } // setDefaultValue is used to install a default value, from a tag string, into // a Value. It is successful if the field was optional, even if a default value // wasn't provided or it failed to install it into the Value. func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { if !params.optional { return } ok = true if params.defaultValue == nil { return } if canHaveDefaultValue(v.Kind()) { v.SetInt(*params.defaultValue) } return } // Unmarshal parses the DER-encoded ASN.1 data structure b // and uses the reflect package to fill in an arbitrary value pointed at by val. // Because Unmarshal uses the reflect package, the structs // being written to must use upper case field names. // // An ASN.1 INTEGER can be written to an int, int32, int64, // or *big.Int (from the math/big package). // If the encoded value does not fit in the Go type, // Unmarshal returns a parse error. // // An ASN.1 BIT STRING can be written to a BitString. // // An ASN.1 OCTET STRING can be written to a []byte. // // An ASN.1 OBJECT IDENTIFIER can be written to an // ObjectIdentifier. // // An ASN.1 ENUMERATED can be written to an Enumerated. // // An ASN.1 UTCTIME or GENERALIZEDTIME can be written to a time.Time. // // An ASN.1 PrintableString, IA5String, or NumericString can be written to a string. // // Any of the above ASN.1 values can be written to an interface{}. // The value stored in the interface has the corresponding Go type. // For integers, that type is int64. // // An ASN.1 SEQUENCE OF x or SET OF x can be written // to a slice if an x can be written to the slice's element type. // // An ASN.1 SEQUENCE or SET can be written to a struct // if each of the elements in the sequence can be // written to the corresponding element in the struct. // // The following tags on struct fields have special meaning to Unmarshal: // // application specifies that an APPLICATION tag is used // private specifies that a PRIVATE tag is used // default:x sets the default value for optional integer fields (only used if optional is also present) // explicit specifies that an additional, explicit tag wraps the implicit one // optional marks the field as ASN.1 OPTIONAL // set causes a SET, rather than a SEQUENCE type to be expected // tag:x specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC // lax relax strict encoding checks for this field, and for any fields within it // // If the type of the first field of a structure is RawContent then the raw // ASN1 contents of the struct will be stored in it. // // If the type name of a slice element ends with "SET" then it's treated as if // the "set" tag was set on it. This can be used with nested slices where a // struct tag cannot be given. // // Other ASN.1 types are not supported; if it encounters them, // Unmarshal returns a parse error. func Unmarshal(b []byte, val interface{}) (rest []byte, err error) { return UnmarshalWithParams(b, val, "") } // UnmarshalWithParams allows field parameters to be specified for the // top-level element. The form of the params is the same as the field tags. func UnmarshalWithParams(b []byte, val interface{}, params string) (rest []byte, err error) { v := reflect.ValueOf(val).Elem() offset, err := parseField(v, b, 0, parseFieldParameters(params)) if err != nil { return nil, err } return b[offset:], nil } google-certificate-transparency-go-2308f62/asn1/asn1_test.go000066400000000000000000001363661462611535200237520ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package asn1 import ( "bytes" "encoding/hex" "fmt" "math" "math/big" "reflect" "strings" "testing" "time" ) type boolTest struct { in []byte ok bool out bool } var boolTestData = []boolTest{ {[]byte{0x00}, true, false}, {[]byte{0xff}, true, true}, {[]byte{0x00, 0x00}, false, false}, {[]byte{0xff, 0xff}, false, false}, {[]byte{0x01}, false, false}, } func TestParseBool(t *testing.T) { for i, test := range boolTestData { ret, err := parseBool(test.in, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if test.ok && ret != test.out { t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out) } } } type int64Test struct { in []byte ok bool okLax bool out int64 } var int64TestData = []int64Test{ {[]byte{0x00}, true, true, 0}, {[]byte{0x7f}, true, true, 127}, {[]byte{0x00, 0x80}, true, true, 128}, {[]byte{0x01, 0x00}, true, true, 256}, {[]byte{0x80}, true, true, -128}, {[]byte{0xff, 0x7f}, true, true, -129}, {[]byte{0xff}, true, true, -1}, {[]byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, true, true, -9223372036854775808}, {[]byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, false, false, 0}, {[]byte{}, false, false, 0}, {[]byte{0x00, 0x7f}, false, true, 127}, {[]byte{0xff, 0xf0}, false, true, -16}, } func TestParseInt64(t *testing.T) { for i, test := range int64TestData { ret, err := parseInt64(test.in, false, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if test.ok && ret != test.out { t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out) } ret, err = parseInt64(test.in, true, "fieldname") if (err == nil) != test.okLax { t.Errorf("#%d: Incorrect lax error result (did fail? %v, expected: %v)", i, err == nil, test.okLax) } if test.okLax && ret != test.out { t.Errorf("#%d: Bad lax result: %v (expected %v)", i, ret, test.out) } } } type int32Test struct { in []byte ok bool okLax bool out int32 } var int32TestData = []int32Test{ {[]byte{0x00}, true, true, 0}, {[]byte{0x7f}, true, true, 127}, {[]byte{0x00, 0x80}, true, true, 128}, {[]byte{0x01, 0x00}, true, true, 256}, {[]byte{0x80}, true, true, -128}, {[]byte{0xff, 0x7f}, true, true, -129}, {[]byte{0xff}, true, true, -1}, {[]byte{0x80, 0x00, 0x00, 0x00}, true, true, -2147483648}, {[]byte{0x80, 0x00, 0x00, 0x00, 0x00}, false, false, 0}, {[]byte{}, false, false, 0}, {[]byte{0x00, 0x7f}, false, true, 127}, {[]byte{0xff, 0xf0}, false, true, -16}, } func TestParseInt32(t *testing.T) { for i, test := range int32TestData { ret, err := parseInt32(test.in, false, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if test.ok && int32(ret) != test.out { t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out) } ret, err = parseInt32(test.in, true, "fieldname") if (err == nil) != test.okLax { t.Errorf("#%d: Incorrect lax error result (did fail? %v, expected: %v)", i, err == nil, test.okLax) } if test.okLax && int32(ret) != test.out { t.Errorf("#%d: Bad lax result: %v (expected %v)", i, ret, test.out) } } } var bigIntTests = []struct { in []byte ok bool okLax bool base10 string }{ {[]byte{0xff}, true, true, "-1"}, {[]byte{0x00}, true, true, "0"}, {[]byte{0x01}, true, true, "1"}, {[]byte{0x00, 0xff}, true, true, "255"}, {[]byte{0xff, 0x00}, true, true, "-256"}, {[]byte{0x01, 0x00}, true, true, "256"}, {[]byte{}, false, false, ""}, {[]byte{0x00, 0x7f}, false, true, "127"}, {[]byte{0xff, 0xf0}, false, true, "-16"}, } func TestParseBigInt(t *testing.T) { for i, test := range bigIntTests { ret, err := parseBigInt(test.in, false, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if test.ok { if ret.String() != test.base10 { t.Errorf("#%d: bad result from %x, got %s want %s", i, test.in, ret.String(), test.base10) } e, err := makeBigInt(ret, "fieldname") if err != nil { t.Errorf("%d: err=%q", i, err) continue } result := make([]byte, e.Len()) e.Encode(result) if !bytes.Equal(result, test.in) { t.Errorf("#%d: got %x from marshaling %s, want %x", i, result, ret, test.in) } } ret, err = parseBigInt(test.in, true, "fieldname") if (err == nil) != test.okLax { t.Errorf("#%d: Incorrect lax error result (did fail? %v, expected: %v)", i, err == nil, test.okLax) } if test.okLax { if ret.String() != test.base10 { t.Errorf("#%d: bad result from %x, got %s want %s", i, test.in, ret.String(), test.base10) } } } } type bitStringTest struct { in []byte ok bool out []byte bitLength int } var bitStringTestData = []bitStringTest{ {[]byte{}, false, []byte{}, 0}, {[]byte{0x00}, true, []byte{}, 0}, {[]byte{0x07, 0x00}, true, []byte{0x00}, 1}, {[]byte{0x07, 0x01}, false, []byte{}, 0}, {[]byte{0x07, 0x40}, false, []byte{}, 0}, {[]byte{0x08, 0x00}, false, []byte{}, 0}, } func TestBitString(t *testing.T) { for i, test := range bitStringTestData { ret, err := parseBitString(test.in, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if err == nil { if test.bitLength != ret.BitLength || !bytes.Equal(ret.Bytes, test.out) { t.Errorf("#%d: Bad result: %v (expected %v %v)", i, ret, test.out, test.bitLength) } } } } func TestBitStringAt(t *testing.T) { bs := BitString{[]byte{0x82, 0x40}, 16} if bs.At(0) != 1 { t.Error("#1: Failed") } if bs.At(1) != 0 { t.Error("#2: Failed") } if bs.At(6) != 1 { t.Error("#3: Failed") } if bs.At(9) != 1 { t.Error("#4: Failed") } if bs.At(-1) != 0 { t.Error("#5: Failed") } if bs.At(17) != 0 { t.Error("#6: Failed") } } type bitStringRightAlignTest struct { in []byte inlen int out []byte } var bitStringRightAlignTests = []bitStringRightAlignTest{ {[]byte{0x80}, 1, []byte{0x01}}, {[]byte{0x80, 0x80}, 9, []byte{0x01, 0x01}}, {[]byte{}, 0, []byte{}}, {[]byte{0xce}, 8, []byte{0xce}}, {[]byte{0xce, 0x47}, 16, []byte{0xce, 0x47}}, {[]byte{0x34, 0x50}, 12, []byte{0x03, 0x45}}, } func TestBitStringRightAlign(t *testing.T) { for i, test := range bitStringRightAlignTests { bs := BitString{test.in, test.inlen} out := bs.RightAlign() if !bytes.Equal(out, test.out) { t.Errorf("#%d got: %x want: %x", i, out, test.out) } } } type objectIdentifierTest struct { in []byte lax bool ok bool out ObjectIdentifier // has base type[]int } var objectIdentifierTestData = []objectIdentifierTest{ {in: []byte{}, ok: false}, {in: []byte{}, lax: true, ok: true, out: []int{}}, {in: []byte{85}, ok: true, out: []int{2, 5}}, {in: []byte{85, 0x02}, ok: true, out: []int{2, 5, 2}}, {in: []byte{85, 0x02, 0xc0, 0x00}, ok: true, out: []int{2, 5, 2, 0x2000}}, {in: []byte{0x81, 0x34, 0x03}, ok: true, out: []int{2, 100, 3}}, {in: []byte{85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, ok: false}, } func TestObjectIdentifier(t *testing.T) { for i, test := range objectIdentifierTestData { ret, err := parseObjectIdentifier(test.in, test.lax, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if err == nil { if !reflect.DeepEqual(test.out, ret) { t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out) } } } if s := ObjectIdentifier([]int{1, 2, 3, 4}).String(); s != "1.2.3.4" { t.Errorf("bad ObjectIdentifier.String(). Got %s, want 1.2.3.4", s) } } type timeTest struct { in string ok bool out time.Time } var utcTestData = []timeTest{ {"910506164540-0700", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", -7*60*60))}, {"910506164540+0730", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", 7*60*60+30*60))}, {"910506234540Z", true, time.Date(1991, 05, 06, 23, 45, 40, 0, time.UTC)}, {"9105062345Z", true, time.Date(1991, 05, 06, 23, 45, 0, 0, time.UTC)}, {"5105062345Z", true, time.Date(1951, 05, 06, 23, 45, 0, 0, time.UTC)}, {"a10506234540Z", false, time.Time{}}, {"91a506234540Z", false, time.Time{}}, {"9105a6234540Z", false, time.Time{}}, {"910506a34540Z", false, time.Time{}}, {"910506334a40Z", false, time.Time{}}, {"91050633444aZ", false, time.Time{}}, {"910506334461Z", false, time.Time{}}, {"910506334400Za", false, time.Time{}}, /* These are invalid times. However, the time package normalises times * and they were accepted in some versions. See #11134. */ {"000100000000Z", false, time.Time{}}, {"101302030405Z", false, time.Time{}}, {"100002030405Z", false, time.Time{}}, {"100100030405Z", false, time.Time{}}, {"100132030405Z", false, time.Time{}}, {"100231030405Z", false, time.Time{}}, {"100102240405Z", false, time.Time{}}, {"100102036005Z", false, time.Time{}}, {"100102030460Z", false, time.Time{}}, {"-100102030410Z", false, time.Time{}}, {"10-0102030410Z", false, time.Time{}}, {"10-0002030410Z", false, time.Time{}}, {"1001-02030410Z", false, time.Time{}}, {"100102-030410Z", false, time.Time{}}, {"10010203-0410Z", false, time.Time{}}, {"1001020304-10Z", false, time.Time{}}, } func TestUTCTime(t *testing.T) { for i, test := range utcTestData { ret, err := parseUTCTime([]byte(test.in)) if err != nil { if test.ok { t.Errorf("#%d: parseUTCTime(%q) = error %v", i, test.in, err) } continue } if !test.ok { t.Errorf("#%d: parseUTCTime(%q) succeeded, should have failed", i, test.in) continue } const format = "Jan _2 15:04:05 -0700 2006" // ignore zone name, just offset have := ret.Format(format) want := test.out.Format(format) if have != want { t.Errorf("#%d: parseUTCTime(%q) = %s, want %s", i, test.in, have, want) } } } var generalizedTimeTestData = []timeTest{ {"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)}, {"20100102030405", false, time.Time{}}, {"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))}, {"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))}, /* These are invalid times. However, the time package normalises times * and they were accepted in some versions. See #11134. */ {"00000100000000Z", false, time.Time{}}, {"20101302030405Z", false, time.Time{}}, {"20100002030405Z", false, time.Time{}}, {"20100100030405Z", false, time.Time{}}, {"20100132030405Z", false, time.Time{}}, {"20100231030405Z", false, time.Time{}}, {"20100102240405Z", false, time.Time{}}, {"20100102036005Z", false, time.Time{}}, {"20100102030460Z", false, time.Time{}}, {"-20100102030410Z", false, time.Time{}}, {"2010-0102030410Z", false, time.Time{}}, {"2010-0002030410Z", false, time.Time{}}, {"201001-02030410Z", false, time.Time{}}, {"20100102-030410Z", false, time.Time{}}, {"2010010203-0410Z", false, time.Time{}}, {"201001020304-10Z", false, time.Time{}}, } func TestGeneralizedTime(t *testing.T) { for i, test := range generalizedTimeTestData { ret, err := parseGeneralizedTime([]byte(test.in)) if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) } if err == nil { if !reflect.DeepEqual(test.out, ret) { t.Errorf("#%d: Bad result: %q → %v (expected %v)", i, test.in, ret, test.out) } } } } type tagAndLengthTest struct { in []byte ok bool out tagAndLength } var tagAndLengthData = []tagAndLengthTest{ {[]byte{0x80, 0x01}, true, tagAndLength{2, 0, 1, false}}, {[]byte{0xa0, 0x01}, true, tagAndLength{2, 0, 1, true}}, {[]byte{0x02, 0x00}, true, tagAndLength{0, 2, 0, false}}, {[]byte{0xfe, 0x00}, true, tagAndLength{3, 30, 0, true}}, {[]byte{0x1f, 0x1f, 0x00}, true, tagAndLength{0, 31, 0, false}}, {[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{0, 128, 0, false}}, {[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{0, 0x4001, 0, false}}, {[]byte{0x00, 0x81, 0x80}, true, tagAndLength{0, 0, 128, false}}, {[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{0, 0, 256, false}}, {[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}}, {[]byte{0x1f, 0x85}, false, tagAndLength{}}, {[]byte{0x30, 0x80}, false, tagAndLength{}}, // Superfluous zeros in the length should be an error. {[]byte{0xa0, 0x82, 0x00, 0xff}, false, tagAndLength{}}, // Lengths up to the maximum size of an int should work. {[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}}, // Lengths that would overflow an int should be rejected. {[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}}, // Long length form may not be used for lengths that fit in short form. {[]byte{0xa0, 0x81, 0x7f}, false, tagAndLength{}}, // Tag numbers which would overflow int32 are rejected. (The value below is 2^31.) {[]byte{0x1f, 0x88, 0x80, 0x80, 0x80, 0x00, 0x00}, false, tagAndLength{}}, // Tag numbers that fit in an int32 are valid. (The value below is 2^31 - 1.) {[]byte{0x1f, 0x87, 0xFF, 0xFF, 0xFF, 0x7F, 0x00}, true, tagAndLength{tag: math.MaxInt32}}, // Long tag number form may not be used for tags that fit in short form. {[]byte{0x1f, 0x1e, 0x00}, false, tagAndLength{}}, } func TestParseTagAndLength(t *testing.T) { for i, test := range tagAndLengthData { tagAndLength, _, err := parseTagAndLength(test.in, 0, "fieldname") if (err == nil) != test.ok { t.Errorf("#%d: Incorrect error result (did pass? %v, expected: %v)", i, err == nil, test.ok) } if err == nil && !reflect.DeepEqual(test.out, tagAndLength) { t.Errorf("#%d: Bad result: %v (expected %v)", i, tagAndLength, test.out) } } } type parseFieldParametersTest struct { in string out fieldParameters } func newInt(n int) *int { return &n } func newInt64(n int64) *int64 { return &n } func newString(s string) *string { return &s } func newBool(b bool) *bool { return &b } var parseFieldParametersTestData = []parseFieldParametersTest{ {"", fieldParameters{}}, {"ia5", fieldParameters{stringType: TagIA5String}}, {"generalized", fieldParameters{timeType: TagGeneralizedTime}}, {"utc", fieldParameters{timeType: TagUTCTime}}, {"printable", fieldParameters{stringType: TagPrintableString}}, {"numeric", fieldParameters{stringType: TagNumericString}}, {"optional", fieldParameters{optional: true}}, {"explicit", fieldParameters{explicit: true, tag: new(int)}}, {"application", fieldParameters{application: true, tag: new(int)}}, {"private", fieldParameters{private: true, tag: new(int)}}, {"optional,explicit", fieldParameters{optional: true, explicit: true, tag: new(int)}}, {"default:42", fieldParameters{defaultValue: newInt64(42)}}, {"tag:17", fieldParameters{tag: newInt(17)}}, {"lax", fieldParameters{lax: true}}, {"optional,explicit,default:42,tag:17", fieldParameters{optional: true, explicit: true, defaultValue: newInt64(42), tag: newInt(17)}}, {"optional,explicit,default:42,tag:17,rubbish1", fieldParameters{optional: true, explicit: true, defaultValue: newInt64(42), tag: newInt(17)}}, {"set", fieldParameters{set: true}}, } func TestParseFieldParameters(t *testing.T) { for i, test := range parseFieldParametersTestData { f := parseFieldParameters(test.in) if !reflect.DeepEqual(f, test.out) { t.Errorf("#%d: Bad result: %v (expected %v)", i, f, test.out) } } } type TestObjectIdentifierStruct struct { OID ObjectIdentifier } type TestContextSpecificTags struct { A int `asn1:"tag:1"` } type TestContextSpecificTags2 struct { A int `asn1:"explicit,tag:1"` B int } type TestContextSpecificTags3 struct { S string `asn1:"tag:1,utf8"` } type TestElementsAfterString struct { S string A, B int } type TestBigInt struct { X *big.Int } type TestSet struct { Ints []int `asn1:"set"` } type TestAuthKeyID struct { ID []byte `asn1:"optional,tag:0"` Issuer RawValue `asn1:"optional,tag:1"` SerialNumber *big.Int `asn1:"optional,tag:2"` } type TestSetOfAny struct { Values anySET } type anySET []RawValue var unmarshalTestData = []struct { in []byte params string out interface{} }{ {[]byte{0x02, 0x01, 0x42}, "", newInt(0x42)}, {[]byte{0x05, 0x00}, "", &RawValue{0, 5, false, []byte{}, []byte{0x05, 0x00}}}, {[]byte{0x30, 0x08, 0x06, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d}, "", &TestObjectIdentifierStruct{[]int{1, 2, 840, 113549}}}, {[]byte{0x03, 0x04, 0x06, 0x6e, 0x5d, 0xc0}, "", &BitString{[]byte{110, 93, 192}, 18}}, {[]byte{0x30, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, "", &[]int{1, 2, 3}}, {[]byte{0x02, 0x01, 0x10}, "", newInt(16)}, {[]byte{0x13, 0x04, 't', 'e', 's', 't'}, "", newString("test")}, {[]byte{0x16, 0x04, 't', 'e', 's', 't'}, "", newString("test")}, // Ampersand is allowed in PrintableString due to mistakes by major CAs. {[]byte{0x13, 0x05, 't', 'e', 's', 't', '&'}, "", newString("test&")}, {[]byte{0x16, 0x04, 't', 'e', 's', 't'}, "", &RawValue{0, 22, false, []byte("test"), []byte("\x16\x04test")}}, {[]byte{0x04, 0x04, 1, 2, 3, 4}, "", &RawValue{0, 4, false, []byte{1, 2, 3, 4}, []byte{4, 4, 1, 2, 3, 4}}}, {[]byte{0x30, 0x03, 0x81, 0x01, 0x01}, "", &TestContextSpecificTags{1}}, {[]byte{0x30, 0x08, 0xa1, 0x03, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}, "", &TestContextSpecificTags2{1, 2}}, {[]byte{0x30, 0x03, 0x81, 0x01, '@'}, "", &TestContextSpecificTags3{"@"}}, {[]byte{0x01, 0x01, 0x00}, "", newBool(false)}, {[]byte{0x01, 0x01, 0xff}, "", newBool(true)}, {[]byte{0x30, 0x0b, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x22, 0x02, 0x01, 0x33}, "", &TestElementsAfterString{"foo", 0x22, 0x33}}, {[]byte{0x30, 0x05, 0x02, 0x03, 0x12, 0x34, 0x56}, "", &TestBigInt{big.NewInt(0x123456)}}, {[]byte{0x30, 0x0b, 0x31, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, "", &TestSet{Ints: []int{1, 2, 3}}}, {[]byte{0x12, 0x0b, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '}, "", newString("0123456789 ")}, {[]byte{0x30, 0x0e, 0x80, 0x04, 0x01, 0x02, 0x03, 0x04, 0x82, 0x06, 0x01, 0x22, 0x33, 0x44, 0x55, 0x66}, "", &TestAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}, SerialNumber: big.NewInt(0x12233445566)}}, {[]byte{0x30, 0x12, 0x80, 0x04, 0x01, 0x02, 0x03, 0x04, 0x81, 0x02, 0xFF, 0xFF, 0x82, 0x06, 0x01, 0x22, 0x33, 0x44, 0x55, 0x66}, "", &TestAuthKeyID{ ID: []byte{0x01, 0x02, 0x03, 0x04}, Issuer: RawValue{ Class: ClassContextSpecific, Tag: 1, IsCompound: false, Bytes: []byte{0xff, 0xff}, FullBytes: []byte{0x81, 0x02, 0xff, 0xff}, }, SerialNumber: big.NewInt(0x12233445566), }}, {[]byte{0x30, 0x06, 0x80, 0x04, 0x01, 0x02, 0x03, 0x04}, "", &TestAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}}}, {[]byte{0x30, 0x05, 0x31, 0x03, 0x02, 0x01, 0x42}, "", &TestSetOfAny{ Values: []RawValue{ RawValue{Class: 0, Tag: 2, Bytes: []byte{0x42}, FullBytes: []byte{0x02, 0x01, 0x42}}, }, }, }, // PrintableString that erroneously contains non-printable contents: // 0xE9 is é in ISO8859-1, @ in T.61, taken to be the former. {[]byte{0x13, 0x04, 'c', 'a', 'f', 0xE9}, "lax", newString("café")}, // 0xFB is û in ISO8859-1, ß in T.61, taken to be the former. {[]byte{0x13, 0x04, 'a', 'b', 'c', 0xFB}, "lax", newString("abcû")}, } func TestUnmarshal(t *testing.T) { for i, test := range unmarshalTestData { pv := reflect.New(reflect.TypeOf(test.out).Elem()) val := pv.Interface() _, err := UnmarshalWithParams(test.in, val, test.params) if err != nil { t.Errorf("Unmarshal failed at index %d %v", i, err) } if !reflect.DeepEqual(val, test.out) { t.Errorf("#%d:\nhave %#v\nwant %#v", i, val, test.out) } } } type stringHolder struct { Value string } func TestLaxPropagate(t *testing.T) { b := []byte{0x30, 0x06, // SEQUENCE length 6 0x13, 0x04, // PrintableString length 4 'c', 'a', 'f', 0xE9} var got stringHolder wantErr := "invalid character" _, err := UnmarshalWithParams(b, &got, "") if err == nil { t.Errorf("Unmarshal()=_,nil; want _, err containing %q", wantErr) } else if !strings.Contains(err.Error(), wantErr) { t.Errorf("Unmarshal()=_,%v; want _, err containing %q", err, wantErr) } _, err = UnmarshalWithParams(b, &got, "lax") if err != nil { t.Errorf("Unmarshal(lax)=_,%v; want _, nil", err) } if want := "café"; got.Value != want { t.Errorf("Unmarshal(lax)=%q; want %q", got.Value, want) } } type Certificate struct { TBSCertificate TBSCertificate SignatureAlgorithm AlgorithmIdentifier SignatureValue BitString } type TBSCertificate struct { Version int `asn1:"optional,explicit,default:0,tag:0"` SerialNumber RawValue SignatureAlgorithm AlgorithmIdentifier Issuer RDNSequence Validity Validity Subject RDNSequence PublicKey PublicKeyInfo } type AlgorithmIdentifier struct { Algorithm ObjectIdentifier } type RDNSequence []RelativeDistinguishedNameSET type RelativeDistinguishedNameSET []AttributeTypeAndValue type AttributeTypeAndValue struct { Type ObjectIdentifier Value interface{} } type Validity struct { NotBefore, NotAfter time.Time } type PublicKeyInfo struct { Algorithm AlgorithmIdentifier PublicKey BitString } func TestCertificate(t *testing.T) { // This is a minimal, self-signed certificate that should parse correctly. var cert Certificate if _, err := Unmarshal(derEncodedSelfSignedCertBytes, &cert); err != nil { t.Errorf("Unmarshal failed: %v", err) } if !reflect.DeepEqual(cert, derEncodedSelfSignedCert) { t.Errorf("Bad result:\ngot: %+v\nwant: %+v", cert, derEncodedSelfSignedCert) } } func TestCertificateWithNUL(t *testing.T) { // This is the paypal NUL-hack certificate. It should fail to parse because // NUL isn't a permitted character in a PrintableString. var cert Certificate if _, err := Unmarshal(derEncodedPaypalNULCertBytes, &cert); err == nil { t.Error("Unmarshal succeeded, should not have") } } type rawStructTest struct { Raw RawContent A int } func TestRawStructs(t *testing.T) { var s rawStructTest input := []byte{0x30, 0x03, 0x02, 0x01, 0x50} rest, err := Unmarshal(input, &s) if len(rest) != 0 { t.Errorf("incomplete parse: %x", rest) return } if err != nil { t.Error(err) return } if s.A != 0x50 { t.Errorf("bad value for A: got %d want %d", s.A, 0x50) } if !bytes.Equal([]byte(s.Raw), input) { t.Errorf("bad value for Raw: got %x want %x", s.Raw, input) } } func TestCouldBeISO8859_1(t *testing.T) { for i := 0; i < 0xff; i++ { b := []byte("StringWithA") b = append(b, byte(i)) switch { // These values are disallowed: case i < 0x20, i >= 0x7f && i < 0xa0: if couldBeISO8859_1(b) { t.Fatalf("Allowed invalid value %d", i) } // These values are allowed: case i >= 0x20 && i < 0x7f, i >= 0xa0 && i <= 0xff: if !couldBeISO8859_1(b) { t.Fatalf("Disallowed valid value %d", i) } default: t.Fatalf("Test logic error - value %d not covered above", i) } } } func TestCouldBeT61(t *testing.T) { for i := 0; i < 255; i++ { b := []byte("StringWithA") b = append(b, byte(i)) if couldBeT61(b) { switch i { case 0x00: fallthrough case 0x23, 0x24, 0x5C, 0x5E, 0x60, 0x7B, 0x7D, 0x7E, 0xA5, 0xA6, 0xAC, 0xAD, 0xAE, 0xAF, 0xB9, 0xBA, 0xC0, 0xC9, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDE, 0xDF, 0xE5, 0xFF: t.Fatalf("Allowed string with byte %d", i) } } } } func TestISO8859_1ToUTF8(t *testing.T) { b := []byte{'c', 'a', 'f', 0xE9} // 0xE9 == é in ISO8859-1, but is invalid in UTF8 if string(b) == "café" { t.Fatal("Sanity failure: that shouldn't have matched") } if iso8859_1ToUTF8(b) != "café" { t.Fatalf("Failed to convert properly, got %v", iso8859_1ToUTF8(b)) } } type oiEqualTest struct { first ObjectIdentifier second ObjectIdentifier same bool } var oiEqualTests = []oiEqualTest{ { ObjectIdentifier{1, 2, 3}, ObjectIdentifier{1, 2, 3}, true, }, { ObjectIdentifier{1}, ObjectIdentifier{1, 2, 3}, false, }, { ObjectIdentifier{1, 2, 3}, ObjectIdentifier{10, 11, 12}, false, }, } func TestObjectIdentifierEqual(t *testing.T) { for _, o := range oiEqualTests { if s := o.first.Equal(o.second); s != o.same { t.Errorf("ObjectIdentifier.Equal: got: %t want: %t", s, o.same) } } } var derEncodedSelfSignedCert = Certificate{ TBSCertificate: TBSCertificate{ Version: 0, SerialNumber: RawValue{Class: 0, Tag: 2, IsCompound: false, Bytes: []uint8{0x0, 0x8c, 0xc3, 0x37, 0x92, 0x10, 0xec, 0x2c, 0x98}, FullBytes: []byte{2, 9, 0x0, 0x8c, 0xc3, 0x37, 0x92, 0x10, 0xec, 0x2c, 0x98}}, SignatureAlgorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}}, Issuer: RDNSequence{ RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 6}, Value: "XX"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 8}, Value: "Some-State"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 7}, Value: "City"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 10}, Value: "Internet Widgits Pty Ltd"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 3}, Value: "false.example.com"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "false@example.com"}}, }, Validity: Validity{ NotBefore: time.Date(2009, 10, 8, 00, 25, 53, 0, time.UTC), NotAfter: time.Date(2010, 10, 8, 00, 25, 53, 0, time.UTC), }, Subject: RDNSequence{ RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 6}, Value: "XX"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 8}, Value: "Some-State"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 7}, Value: "City"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 10}, Value: "Internet Widgits Pty Ltd"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 3}, Value: "false.example.com"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "false@example.com"}}, }, PublicKey: PublicKeyInfo{ Algorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}}, PublicKey: BitString{ Bytes: []uint8{ 0x30, 0x48, 0x2, 0x41, 0x0, 0xcd, 0xb7, 0x63, 0x9c, 0x32, 0x78, 0xf0, 0x6, 0xaa, 0x27, 0x7f, 0x6e, 0xaf, 0x42, 0x90, 0x2b, 0x59, 0x2d, 0x8c, 0xbc, 0xbe, 0x38, 0xa1, 0xc9, 0x2b, 0xa4, 0x69, 0x5a, 0x33, 0x1b, 0x1d, 0xea, 0xde, 0xad, 0xd8, 0xe9, 0xa5, 0xc2, 0x7e, 0x8c, 0x4c, 0x2f, 0xd0, 0xa8, 0x88, 0x96, 0x57, 0x72, 0x2a, 0x4f, 0x2a, 0xf7, 0x58, 0x9c, 0xf2, 0xc7, 0x70, 0x45, 0xdc, 0x8f, 0xde, 0xec, 0x35, 0x7d, 0x2, 0x3, 0x1, 0x0, 0x1, }, BitLength: 592, }, }, }, SignatureAlgorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}}, SignatureValue: BitString{ Bytes: []uint8{ 0xa6, 0x7b, 0x6, 0xec, 0x5e, 0xce, 0x92, 0x77, 0x2c, 0xa4, 0x13, 0xcb, 0xa3, 0xca, 0x12, 0x56, 0x8f, 0xdc, 0x6c, 0x7b, 0x45, 0x11, 0xcd, 0x40, 0xa7, 0xf6, 0x59, 0x98, 0x4, 0x2, 0xdf, 0x2b, 0x99, 0x8b, 0xb9, 0xa4, 0xa8, 0xcb, 0xeb, 0x34, 0xc0, 0xf0, 0xa7, 0x8c, 0xf8, 0xd9, 0x1e, 0xde, 0x14, 0xa5, 0xed, 0x76, 0xbf, 0x11, 0x6f, 0xe3, 0x60, 0xaa, 0xfa, 0x88, 0x21, 0x49, 0x4, 0x35, }, BitLength: 512, }, } var derEncodedSelfSignedCertBytes = []byte{ 0x30, 0x82, 0x02, 0x18, 0x30, 0x82, 0x01, 0xc2, 0x02, 0x09, 0x00, 0x8c, 0xc3, 0x37, 0x92, 0x10, 0xec, 0x2c, 0x98, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0x92, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x58, 0x58, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x04, 0x43, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x30, 0x30, 0x38, 0x30, 0x30, 0x32, 0x35, 0x35, 0x33, 0x5a, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x30, 0x30, 0x38, 0x30, 0x30, 0x32, 0x35, 0x35, 0x33, 0x5a, 0x30, 0x81, 0x92, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x58, 0x58, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x04, 0x43, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xcd, 0xb7, 0x63, 0x9c, 0x32, 0x78, 0xf0, 0x06, 0xaa, 0x27, 0x7f, 0x6e, 0xaf, 0x42, 0x90, 0x2b, 0x59, 0x2d, 0x8c, 0xbc, 0xbe, 0x38, 0xa1, 0xc9, 0x2b, 0xa4, 0x69, 0x5a, 0x33, 0x1b, 0x1d, 0xea, 0xde, 0xad, 0xd8, 0xe9, 0xa5, 0xc2, 0x7e, 0x8c, 0x4c, 0x2f, 0xd0, 0xa8, 0x88, 0x96, 0x57, 0x72, 0x2a, 0x4f, 0x2a, 0xf7, 0x58, 0x9c, 0xf2, 0xc7, 0x70, 0x45, 0xdc, 0x8f, 0xde, 0xec, 0x35, 0x7d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00, 0xa6, 0x7b, 0x06, 0xec, 0x5e, 0xce, 0x92, 0x77, 0x2c, 0xa4, 0x13, 0xcb, 0xa3, 0xca, 0x12, 0x56, 0x8f, 0xdc, 0x6c, 0x7b, 0x45, 0x11, 0xcd, 0x40, 0xa7, 0xf6, 0x59, 0x98, 0x04, 0x02, 0xdf, 0x2b, 0x99, 0x8b, 0xb9, 0xa4, 0xa8, 0xcb, 0xeb, 0x34, 0xc0, 0xf0, 0xa7, 0x8c, 0xf8, 0xd9, 0x1e, 0xde, 0x14, 0xa5, 0xed, 0x76, 0xbf, 0x11, 0x6f, 0xe3, 0x60, 0xaa, 0xfa, 0x88, 0x21, 0x49, 0x04, 0x35, } var derEncodedPaypalNULCertBytes = []byte{ 0x30, 0x82, 0x06, 0x44, 0x30, 0x82, 0x05, 0xad, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x03, 0x00, 0xf0, 0x9b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x82, 0x01, 0x12, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x45, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x09, 0x42, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x42, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x20, 0x49, 0x50, 0x53, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x73, 0x2e, 0x6c, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x14, 0x25, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x43, 0x2e, 0x49, 0x2e, 0x46, 0x2e, 0x20, 0x20, 0x42, 0x2d, 0x42, 0x36, 0x32, 0x32, 0x31, 0x30, 0x36, 0x39, 0x35, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x25, 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x32, 0x32, 0x34, 0x32, 0x33, 0x30, 0x34, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x32, 0x32, 0x34, 0x32, 0x33, 0x30, 0x34, 0x31, 0x37, 0x5a, 0x30, 0x81, 0x94, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x53, 0x61, 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0b, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x55, 0x6e, 0x69, 0x74, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x61, 0x79, 0x70, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x73, 0x73, 0x6c, 0x2e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x63, 0x63, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd2, 0x69, 0xfa, 0x6f, 0x3a, 0x00, 0xb4, 0x21, 0x1b, 0xc8, 0xb1, 0x02, 0xd7, 0x3f, 0x19, 0xb2, 0xc4, 0x6d, 0xb4, 0x54, 0xf8, 0x8b, 0x8a, 0xcc, 0xdb, 0x72, 0xc2, 0x9e, 0x3c, 0x60, 0xb9, 0xc6, 0x91, 0x3d, 0x82, 0xb7, 0x7d, 0x99, 0xff, 0xd1, 0x29, 0x84, 0xc1, 0x73, 0x53, 0x9c, 0x82, 0xdd, 0xfc, 0x24, 0x8c, 0x77, 0xd5, 0x41, 0xf3, 0xe8, 0x1e, 0x42, 0xa1, 0xad, 0x2d, 0x9e, 0xff, 0x5b, 0x10, 0x26, 0xce, 0x9d, 0x57, 0x17, 0x73, 0x16, 0x23, 0x38, 0xc8, 0xd6, 0xf1, 0xba, 0xa3, 0x96, 0x5b, 0x16, 0x67, 0x4a, 0x4f, 0x73, 0x97, 0x3a, 0x4d, 0x14, 0xa4, 0xf4, 0xe2, 0x3f, 0x8b, 0x05, 0x83, 0x42, 0xd1, 0xd0, 0xdc, 0x2f, 0x7a, 0xe5, 0xb6, 0x10, 0xb2, 0x11, 0xc0, 0xdc, 0x21, 0x2a, 0x90, 0xff, 0xae, 0x97, 0x71, 0x5a, 0x49, 0x81, 0xac, 0x40, 0xf3, 0x3b, 0xb8, 0x59, 0xb2, 0x4f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x03, 0x21, 0x30, 0x82, 0x03, 0x1d, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x06, 0x40, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x03, 0xf8, 0x30, 0x13, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x61, 0x8f, 0x61, 0x34, 0x43, 0x55, 0x14, 0x7f, 0x27, 0x09, 0xce, 0x4c, 0x8b, 0xea, 0x9b, 0x7b, 0x19, 0x25, 0xbc, 0x6e, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x0e, 0x07, 0x60, 0xd4, 0x39, 0xc9, 0x1b, 0x5b, 0x5d, 0x90, 0x7b, 0x23, 0xc8, 0xd2, 0x34, 0x9d, 0x4a, 0x9a, 0x46, 0x39, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x1d, 0x12, 0x04, 0x15, 0x30, 0x13, 0x81, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x72, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x04, 0x65, 0x16, 0x63, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x4f, 0x54, 0x20, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x45, 0x44, 0x2e, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x2f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x02, 0x04, 0x22, 0x16, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x30, 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x04, 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x03, 0x04, 0x39, 0x16, 0x37, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x72, 0x65, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3f, 0x30, 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x07, 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3f, 0x30, 0x41, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x08, 0x04, 0x34, 0x16, 0x32, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x30, 0x81, 0x83, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x7c, 0x30, 0x7a, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0xa0, 0x3b, 0xa0, 0x39, 0x86, 0x37, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x68, 0xee, 0x79, 0x97, 0x97, 0xdd, 0x3b, 0xef, 0x16, 0x6a, 0x06, 0xf2, 0x14, 0x9a, 0x6e, 0xcd, 0x9e, 0x12, 0xf7, 0xaa, 0x83, 0x10, 0xbd, 0xd1, 0x7c, 0x98, 0xfa, 0xc7, 0xae, 0xd4, 0x0e, 0x2c, 0x9e, 0x38, 0x05, 0x9d, 0x52, 0x60, 0xa9, 0x99, 0x0a, 0x81, 0xb4, 0x98, 0x90, 0x1d, 0xae, 0xbb, 0x4a, 0xd7, 0xb9, 0xdc, 0x88, 0x9e, 0x37, 0x78, 0x41, 0x5b, 0xf7, 0x82, 0xa5, 0xf2, 0xba, 0x41, 0x25, 0x5a, 0x90, 0x1a, 0x1e, 0x45, 0x38, 0xa1, 0x52, 0x58, 0x75, 0x94, 0x26, 0x44, 0xfb, 0x20, 0x07, 0xba, 0x44, 0xcc, 0xe5, 0x4a, 0x2d, 0x72, 0x3f, 0x98, 0x47, 0xf6, 0x26, 0xdc, 0x05, 0x46, 0x05, 0x07, 0x63, 0x21, 0xab, 0x46, 0x9b, 0x9c, 0x78, 0xd5, 0x54, 0x5b, 0x3d, 0x0c, 0x1e, 0xc8, 0x64, 0x8c, 0xb5, 0x50, 0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43, 0x96, 0x07, 0xa8, 0xbb, } var stringSliceTestData = [][]string{ {"foo", "bar"}, {"foo", "\\bar"}, {"foo", "\"bar\""}, {"foo", "åäö"}, } func TestStringSlice(t *testing.T) { for _, test := range stringSliceTestData { bs, err := Marshal(test) if err != nil { t.Error(err) } var res []string _, err = Unmarshal(bs, &res) if err != nil { t.Error(err) } if fmt.Sprintf("%v", res) != fmt.Sprintf("%v", test) { t.Errorf("incorrect marshal/unmarshal; %v != %v", res, test) } } } type explicitTaggedTimeTest struct { Time time.Time `asn1:"explicit,tag:0"` } var explicitTaggedTimeTestData = []struct { in []byte out explicitTaggedTimeTest }{ {[]byte{0x30, 0x11, 0xa0, 0xf, 0x17, 0xd, '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z'}, explicitTaggedTimeTest{time.Date(1991, 05, 06, 16, 45, 40, 0, time.UTC)}}, {[]byte{0x30, 0x17, 0xa0, 0xf, 0x18, 0x13, '2', '0', '1', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '+', '0', '6', '0', '7'}, explicitTaggedTimeTest{time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))}}, } func TestExplicitTaggedTime(t *testing.T) { // Test that a time.Time will match either tagUTCTime or // tagGeneralizedTime. for i, test := range explicitTaggedTimeTestData { var got explicitTaggedTimeTest _, err := Unmarshal(test.in, &got) if err != nil { t.Errorf("Unmarshal failed at index %d %v", i, err) } if !got.Time.Equal(test.out.Time) { t.Errorf("#%d: got %v, want %v", i, got.Time, test.out.Time) } } } type implicitTaggedTimeTest struct { Time time.Time `asn1:"tag:24"` } func TestImplicitTaggedTime(t *testing.T) { // An implicitly tagged time value, that happens to have an implicit // tag equal to a GENERALIZEDTIME, should still be parsed as a UTCTime. // (There's no "timeType" in fieldParameters to determine what type of // time should be expected when implicitly tagged.) der := []byte{0x30, 0x0f, 0x80 | 24, 0xd, '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z'} var result implicitTaggedTimeTest if _, err := Unmarshal(der, &result); err != nil { t.Fatalf("Error while parsing: %s", err) } if expected := time.Date(1991, 05, 06, 16, 45, 40, 0, time.UTC); !result.Time.Equal(expected) { t.Errorf("Wrong result. Got %v, want %v", result.Time, expected) } } type truncatedExplicitTagTest struct { Test int `asn1:"explicit,tag:0"` } func TestTruncatedExplicitTag(t *testing.T) { // This crashed Unmarshal in the past. See #11154. der := []byte{ 0x30, // SEQUENCE 0x02, // two bytes long 0xa0, // context-specific, tag 0 0x30, // 48 bytes long } var result truncatedExplicitTagTest if _, err := Unmarshal(der, &result); err == nil { t.Error("Unmarshal returned without error") } } type invalidUTF8Test struct { Str string `asn1:"utf8"` } func TestUnmarshalInvalidUTF8(t *testing.T) { data := []byte("0\x05\f\x03a\xc9c") var result invalidUTF8Test _, err := Unmarshal(data, &result) const expectedSubstring = "UTF" if err == nil { t.Fatal("Successfully unmarshaled invalid UTF-8 data") } else if !strings.Contains(err.Error(), expectedSubstring) { t.Fatalf("Expected error to mention %q but error was %q", expectedSubstring, err.Error()) } } func TestMarshalNilValue(t *testing.T) { nilValueTestData := []interface{}{ nil, struct{ V interface{} }{}, } for i, test := range nilValueTestData { if _, err := Marshal(test); err == nil { t.Fatalf("#%d: successfully marshaled nil value", i) } } } type unexported struct { X int y int } type exported struct { X int Y int } func TestUnexportedStructField(t *testing.T) { want := StructuralError{"struct contains unexported fields", "y"} _, err := Marshal(unexported{X: 5, y: 1}) if err != want { t.Errorf("got %v, want %v", err, want) } bs, err := Marshal(exported{X: 5, Y: 1}) if err != nil { t.Fatal(err) } var u unexported _, err = Unmarshal(bs, &u) if err != want { t.Errorf("got %v, want %v", err, want) } } func TestNull(t *testing.T) { marshaled, err := Marshal(NullRawValue) if err != nil { t.Fatal(err) } if !bytes.Equal(NullBytes, marshaled) { t.Errorf("Expected Marshal of NullRawValue to yield %x, got %x", NullBytes, marshaled) } unmarshaled := RawValue{} if _, err := Unmarshal(NullBytes, &unmarshaled); err != nil { t.Fatal(err) } unmarshaled.FullBytes = NullRawValue.FullBytes if len(unmarshaled.Bytes) == 0 { // DeepEqual considers a nil slice and an empty slice to be different. unmarshaled.Bytes = NullRawValue.Bytes } if !reflect.DeepEqual(NullRawValue, unmarshaled) { t.Errorf("Expected Unmarshal of NullBytes to yield %v, got %v", NullRawValue, unmarshaled) } } func TestExplicitTagRawValueStruct(t *testing.T) { type foo struct { A RawValue `asn1:"optional,explicit,tag:5"` B []byte `asn1:"optional,explicit,tag:6"` } before := foo{B: []byte{1, 2, 3}} derBytes, err := Marshal(before) if err != nil { t.Fatal(err) } var after foo if rest, err := Unmarshal(derBytes, &after); err != nil || len(rest) != 0 { t.Fatal(err) } got := fmt.Sprintf("%#v", after) want := fmt.Sprintf("%#v", before) if got != want { t.Errorf("got %s, want %s (DER: %x)", got, want, derBytes) } } func TestTaggedRawValue(t *testing.T) { type taggedRawValue struct { A RawValue `asn1:"tag:5"` } type untaggedRawValue struct { A RawValue } const isCompound = 0x20 const tag = 5 tests := []struct { shouldMatch bool derBytes []byte }{ {false, []byte{0x30, 3, TagInteger, 1, 1}}, {true, []byte{0x30, 3, (ClassContextSpecific << 6) | tag, 1, 1}}, {true, []byte{0x30, 3, (ClassContextSpecific << 6) | tag | isCompound, 1, 1}}, {false, []byte{0x30, 3, (ClassApplication << 6) | tag | isCompound, 1, 1}}, {false, []byte{0x30, 3, (ClassPrivate << 6) | tag | isCompound, 1, 1}}, } for i, test := range tests { var tagged taggedRawValue if _, err := Unmarshal(test.derBytes, &tagged); (err == nil) != test.shouldMatch { t.Errorf("#%d: unexpected result parsing %x: %s", i, test.derBytes, err) } // An untagged RawValue should accept anything. var untagged untaggedRawValue if _, err := Unmarshal(test.derBytes, &untagged); err != nil { t.Errorf("#%d: unexpected failure parsing %x with untagged RawValue: %s", i, test.derBytes, err) } } } var bmpStringTests = []struct { decoded string encodedHex string }{ {"", "0000"}, // Example from https://tools.ietf.org/html/rfc7292#appendix-B. {"Beavis", "0042006500610076006900730000"}, // Some characters from the "Letterlike Symbols Unicode block". {"\u2115 - Double-struck N", "21150020002d00200044006f00750062006c0065002d00730074007200750063006b0020004e0000"}, } func TestBMPString(t *testing.T) { for i, test := range bmpStringTests { encoded, err := hex.DecodeString(test.encodedHex) if err != nil { t.Fatalf("#%d: failed to decode from hex string", i) } decoded, err := parseBMPString(encoded) if err != nil { t.Errorf("#%d: decoding output gave an error: %s", i, err) continue } if decoded != test.decoded { t.Errorf("#%d: decoding output resulted in %q, but it should have been %q", i, decoded, test.decoded) continue } } } google-certificate-transparency-go-2308f62/asn1/common.go000066400000000000000000000132071462611535200233250ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package asn1 import ( "reflect" "strconv" "strings" ) // ASN.1 objects have metadata preceding them: // the tag: the type of the object // a flag denoting if this object is compound or not // the class type: the namespace of the tag // the length of the object, in bytes // Here are some standard tags and classes // ASN.1 tags represent the type of the following object. const ( TagBoolean = 1 TagInteger = 2 TagBitString = 3 TagOctetString = 4 TagNull = 5 TagOID = 6 TagEnum = 10 TagUTF8String = 12 TagSequence = 16 TagSet = 17 TagNumericString = 18 TagPrintableString = 19 TagT61String = 20 TagIA5String = 22 TagUTCTime = 23 TagGeneralizedTime = 24 TagGeneralString = 27 TagBMPString = 30 ) // ASN.1 class types represent the namespace of the tag. const ( ClassUniversal = 0 ClassApplication = 1 ClassContextSpecific = 2 ClassPrivate = 3 ) type tagAndLength struct { class, tag, length int isCompound bool } // ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead // of" and "in addition to". When not specified, every primitive type has a // default tag in the UNIVERSAL class. // // For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1 // doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT // CONTEXT-SPECIFIC 42], that means that the tag is replaced by another. // // On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an // /additional/ tag would wrap the default tag. This explicit tag will have the // compound flag set. // // (This is used in order to remove ambiguity with optional elements.) // // You can layer EXPLICIT and IMPLICIT tags to an arbitrary depth, however we // don't support that here. We support a single layer of EXPLICIT or IMPLICIT // tagging with tag strings on the fields of a structure. // fieldParameters is the parsed representation of tag string from a structure field. type fieldParameters struct { optional bool // true iff the field is OPTIONAL explicit bool // true iff an EXPLICIT tag is in use. application bool // true iff an APPLICATION tag is in use. private bool // true iff a PRIVATE tag is in use. defaultValue *int64 // a default value for INTEGER typed fields (maybe nil). tag *int // the EXPLICIT or IMPLICIT tag (maybe nil). stringType int // the string tag to use when marshaling. timeType int // the time tag to use when marshaling. set bool // true iff this should be encoded as a SET omitEmpty bool // true iff this should be omitted if empty when marshaling. lax bool // true iff unmarshalling should skip some error checks name string // name of field for better diagnostics // Invariants: // if explicit is set, tag is non-nil. } // Given a tag string with the format specified in the package comment, // parseFieldParameters will parse it into a fieldParameters structure, // ignoring unknown parts of the string. func parseFieldParameters(str string) (ret fieldParameters) { for _, part := range strings.Split(str, ",") { switch { case part == "optional": ret.optional = true case part == "explicit": ret.explicit = true if ret.tag == nil { ret.tag = new(int) } case part == "generalized": ret.timeType = TagGeneralizedTime case part == "utc": ret.timeType = TagUTCTime case part == "ia5": ret.stringType = TagIA5String case part == "printable": ret.stringType = TagPrintableString case part == "numeric": ret.stringType = TagNumericString case part == "utf8": ret.stringType = TagUTF8String case strings.HasPrefix(part, "default:"): i, err := strconv.ParseInt(part[8:], 10, 64) if err == nil { ret.defaultValue = new(int64) *ret.defaultValue = i } case strings.HasPrefix(part, "tag:"): i, err := strconv.Atoi(part[4:]) if err == nil { ret.tag = new(int) *ret.tag = i } case part == "set": ret.set = true case part == "application": ret.application = true if ret.tag == nil { ret.tag = new(int) } case part == "private": ret.private = true if ret.tag == nil { ret.tag = new(int) } case part == "omitempty": ret.omitEmpty = true case part == "lax": ret.lax = true } } return } // Given a reflected Go type, getUniversalType returns the default tag number // and expected compound flag. func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) { switch t { case rawValueType: return true, -1, false, true case objectIdentifierType: return false, TagOID, false, true case bitStringType: return false, TagBitString, false, true case timeType: return false, TagUTCTime, false, true case enumeratedType: return false, TagEnum, false, true case bigIntType: return false, TagInteger, false, true } switch t.Kind() { case reflect.Bool: return false, TagBoolean, false, true case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return false, TagInteger, false, true case reflect.Struct: return false, TagSequence, true, true case reflect.Slice: if t.Elem().Kind() == reflect.Uint8 { return false, TagOctetString, false, true } if strings.HasSuffix(t.Name(), "SET") { return false, TagSet, true, true } return false, TagSequence, true, true case reflect.String: return false, TagPrintableString, false, true } return false, 0, false, false } google-certificate-transparency-go-2308f62/asn1/marshal.go000066400000000000000000000377041462611535200234740ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package asn1 import ( "errors" "fmt" "math/big" "reflect" "time" "unicode/utf8" ) var ( byte00Encoder encoder = byteEncoder(0x00) byteFFEncoder encoder = byteEncoder(0xff) ) // encoder represents an ASN.1 element that is waiting to be marshaled. type encoder interface { // Len returns the number of bytes needed to marshal this element. Len() int // Encode encodes this element by writing Len() bytes to dst. Encode(dst []byte) } type byteEncoder byte func (c byteEncoder) Len() int { return 1 } func (c byteEncoder) Encode(dst []byte) { dst[0] = byte(c) } type bytesEncoder []byte func (b bytesEncoder) Len() int { return len(b) } func (b bytesEncoder) Encode(dst []byte) { if copy(dst, b) != len(b) { panic("internal error") } } type stringEncoder string func (s stringEncoder) Len() int { return len(s) } func (s stringEncoder) Encode(dst []byte) { if copy(dst, s) != len(s) { panic("internal error") } } type multiEncoder []encoder func (m multiEncoder) Len() int { var size int for _, e := range m { size += e.Len() } return size } func (m multiEncoder) Encode(dst []byte) { var off int for _, e := range m { e.Encode(dst[off:]) off += e.Len() } } type taggedEncoder struct { // scratch contains temporary space for encoding the tag and length of // an element in order to avoid extra allocations. scratch [8]byte tag encoder body encoder } func (t *taggedEncoder) Len() int { return t.tag.Len() + t.body.Len() } func (t *taggedEncoder) Encode(dst []byte) { t.tag.Encode(dst) t.body.Encode(dst[t.tag.Len():]) } type int64Encoder int64 func (i int64Encoder) Len() int { n := 1 for i > 127 { n++ i >>= 8 } for i < -128 { n++ i >>= 8 } return n } func (i int64Encoder) Encode(dst []byte) { n := i.Len() for j := 0; j < n; j++ { dst[j] = byte(i >> uint((n-1-j)*8)) } } func base128IntLength(n int64) int { if n == 0 { return 1 } l := 0 for i := n; i > 0; i >>= 7 { l++ } return l } func appendBase128Int(dst []byte, n int64) []byte { l := base128IntLength(n) for i := l - 1; i >= 0; i-- { o := byte(n >> uint(i*7)) o &= 0x7f if i != 0 { o |= 0x80 } dst = append(dst, o) } return dst } func makeBigInt(n *big.Int, fieldName string) (encoder, error) { if n == nil { return nil, StructuralError{"empty integer", fieldName} } if n.Sign() < 0 { // A negative number has to be converted to two's-complement // form. So we'll invert and subtract 1. If the // most-significant-bit isn't set then we'll need to pad the // beginning with 0xff in order to keep the number negative. nMinus1 := new(big.Int).Neg(n) nMinus1.Sub(nMinus1, bigOne) bytes := nMinus1.Bytes() for i := range bytes { bytes[i] ^= 0xff } if len(bytes) == 0 || bytes[0]&0x80 == 0 { return multiEncoder([]encoder{byteFFEncoder, bytesEncoder(bytes)}), nil } return bytesEncoder(bytes), nil } else if n.Sign() == 0 { // Zero is written as a single 0 zero rather than no bytes. return byte00Encoder, nil } else { bytes := n.Bytes() if len(bytes) > 0 && bytes[0]&0x80 != 0 { // We'll have to pad this with 0x00 in order to stop it // looking like a negative number. return multiEncoder([]encoder{byte00Encoder, bytesEncoder(bytes)}), nil } return bytesEncoder(bytes), nil } } func appendLength(dst []byte, i int) []byte { n := lengthLength(i) for ; n > 0; n-- { dst = append(dst, byte(i>>uint((n-1)*8))) } return dst } func lengthLength(i int) (numBytes int) { numBytes = 1 for i > 255 { numBytes++ i >>= 8 } return } func appendTagAndLength(dst []byte, t tagAndLength) []byte { b := uint8(t.class) << 6 if t.isCompound { b |= 0x20 } if t.tag >= 31 { b |= 0x1f dst = append(dst, b) dst = appendBase128Int(dst, int64(t.tag)) } else { b |= uint8(t.tag) dst = append(dst, b) } if t.length >= 128 { l := lengthLength(t.length) dst = append(dst, 0x80|byte(l)) dst = appendLength(dst, t.length) } else { dst = append(dst, byte(t.length)) } return dst } type bitStringEncoder BitString func (b bitStringEncoder) Len() int { return len(b.Bytes) + 1 } func (b bitStringEncoder) Encode(dst []byte) { dst[0] = byte((8 - b.BitLength%8) % 8) if copy(dst[1:], b.Bytes) != len(b.Bytes) { panic("internal error") } } type oidEncoder []int func (oid oidEncoder) Len() int { l := base128IntLength(int64(oid[0]*40 + oid[1])) for i := 2; i < len(oid); i++ { l += base128IntLength(int64(oid[i])) } return l } func (oid oidEncoder) Encode(dst []byte) { dst = appendBase128Int(dst[:0], int64(oid[0]*40+oid[1])) for i := 2; i < len(oid); i++ { dst = appendBase128Int(dst, int64(oid[i])) } } func makeObjectIdentifier(oid []int, fieldName string) (e encoder, err error) { if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) { return nil, StructuralError{"invalid object identifier", fieldName} } return oidEncoder(oid), nil } func makePrintableString(s, fieldName string) (e encoder, err error) { for i := 0; i < len(s); i++ { // The asterisk is often used in PrintableString, even though // it is invalid. If a PrintableString was specifically // requested then the asterisk is permitted by this code. // Ampersand is allowed in parsing due a handful of CA // certificates, however when making new certificates // it is rejected. if !isPrintable(s[i], allowAsterisk, rejectAmpersand) { return nil, StructuralError{"PrintableString contains invalid character", fieldName} } } return stringEncoder(s), nil } func makeIA5String(s, fieldName string) (e encoder, err error) { for i := 0; i < len(s); i++ { if s[i] > 127 { return nil, StructuralError{"IA5String contains invalid character", fieldName} } } return stringEncoder(s), nil } func makeNumericString(s string, fieldName string) (e encoder, err error) { for i := 0; i < len(s); i++ { if !isNumeric(s[i]) { return nil, StructuralError{"NumericString contains invalid character", fieldName} } } return stringEncoder(s), nil } func makeUTF8String(s string) encoder { return stringEncoder(s) } func appendTwoDigits(dst []byte, v int) []byte { return append(dst, byte('0'+(v/10)%10), byte('0'+v%10)) } func appendFourDigits(dst []byte, v int) []byte { var bytes [4]byte for i := range bytes { bytes[3-i] = '0' + byte(v%10) v /= 10 } return append(dst, bytes[:]...) } func outsideUTCRange(t time.Time) bool { year := t.Year() return year < 1950 || year >= 2050 } func makeUTCTime(t time.Time, fieldName string) (e encoder, err error) { dst := make([]byte, 0, 18) dst, err = appendUTCTime(dst, t, fieldName) if err != nil { return nil, err } return bytesEncoder(dst), nil } func makeGeneralizedTime(t time.Time, fieldName string) (e encoder, err error) { dst := make([]byte, 0, 20) dst, err = appendGeneralizedTime(dst, t, fieldName) if err != nil { return nil, err } return bytesEncoder(dst), nil } func appendUTCTime(dst []byte, t time.Time, fieldName string) (ret []byte, err error) { year := t.Year() switch { case 1950 <= year && year < 2000: dst = appendTwoDigits(dst, year-1900) case 2000 <= year && year < 2050: dst = appendTwoDigits(dst, year-2000) default: return nil, StructuralError{"cannot represent time as UTCTime", fieldName} } return appendTimeCommon(dst, t), nil } func appendGeneralizedTime(dst []byte, t time.Time, fieldName string) (ret []byte, err error) { year := t.Year() if year < 0 || year > 9999 { return nil, StructuralError{"cannot represent time as GeneralizedTime", fieldName} } dst = appendFourDigits(dst, year) return appendTimeCommon(dst, t), nil } func appendTimeCommon(dst []byte, t time.Time) []byte { _, month, day := t.Date() dst = appendTwoDigits(dst, int(month)) dst = appendTwoDigits(dst, day) hour, min, sec := t.Clock() dst = appendTwoDigits(dst, hour) dst = appendTwoDigits(dst, min) dst = appendTwoDigits(dst, sec) _, offset := t.Zone() switch { case offset/60 == 0: return append(dst, 'Z') case offset > 0: dst = append(dst, '+') case offset < 0: dst = append(dst, '-') } offsetMinutes := offset / 60 if offsetMinutes < 0 { offsetMinutes = -offsetMinutes } dst = appendTwoDigits(dst, offsetMinutes/60) dst = appendTwoDigits(dst, offsetMinutes%60) return dst } func stripTagAndLength(in []byte) []byte { _, offset, err := parseTagAndLength(in, 0, "") if err != nil { return in } return in[offset:] } func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error) { switch value.Type() { case flagType: return bytesEncoder(nil), nil case timeType: t := value.Interface().(time.Time) if params.timeType == TagGeneralizedTime || outsideUTCRange(t) { return makeGeneralizedTime(t, params.name) } return makeUTCTime(t, params.name) case bitStringType: return bitStringEncoder(value.Interface().(BitString)), nil case objectIdentifierType: return makeObjectIdentifier(value.Interface().(ObjectIdentifier), params.name) case bigIntType: return makeBigInt(value.Interface().(*big.Int), params.name) } switch v := value; v.Kind() { case reflect.Bool: if v.Bool() { return byteFFEncoder, nil } return byte00Encoder, nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return int64Encoder(v.Int()), nil case reflect.Struct: t := v.Type() for i := 0; i < t.NumField(); i++ { if t.Field(i).PkgPath != "" { return nil, StructuralError{"struct contains unexported fields", t.Field(i).Name} } } startingField := 0 n := t.NumField() if n == 0 { return bytesEncoder(nil), nil } // If the first element of the structure is a non-empty // RawContents, then we don't bother serializing the rest. if t.Field(0).Type == rawContentsType { s := v.Field(0) if s.Len() > 0 { bytes := s.Bytes() /* The RawContents will contain the tag and * length fields but we'll also be writing * those ourselves, so we strip them out of * bytes */ return bytesEncoder(stripTagAndLength(bytes)), nil } startingField = 1 } switch n1 := n - startingField; n1 { case 0: return bytesEncoder(nil), nil case 1: return makeField(v.Field(startingField), parseFieldParameters(t.Field(startingField).Tag.Get("asn1"))) default: m := make([]encoder, n1) for i := 0; i < n1; i++ { m[i], err = makeField(v.Field(i+startingField), parseFieldParameters(t.Field(i+startingField).Tag.Get("asn1"))) if err != nil { return nil, err } } return multiEncoder(m), nil } case reflect.Slice: sliceType := v.Type() if sliceType.Elem().Kind() == reflect.Uint8 { return bytesEncoder(v.Bytes()), nil } var fp fieldParameters switch l := v.Len(); l { case 0: return bytesEncoder(nil), nil case 1: return makeField(v.Index(0), fp) default: m := make([]encoder, l) for i := 0; i < l; i++ { m[i], err = makeField(v.Index(i), fp) if err != nil { return nil, err } } return multiEncoder(m), nil } case reflect.String: switch params.stringType { case TagIA5String: return makeIA5String(v.String(), params.name) case TagPrintableString: return makePrintableString(v.String(), params.name) case TagNumericString: return makeNumericString(v.String(), params.name) default: return makeUTF8String(v.String()), nil } } return nil, StructuralError{"unknown Go type", params.name} } func makeField(v reflect.Value, params fieldParameters) (e encoder, err error) { if !v.IsValid() { return nil, fmt.Errorf("asn1: cannot marshal nil value") } // If the field is an interface{} then recurse into it. if v.Kind() == reflect.Interface && v.Type().NumMethod() == 0 { return makeField(v.Elem(), params) } if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty { return bytesEncoder(nil), nil } if params.optional && params.defaultValue != nil && canHaveDefaultValue(v.Kind()) { defaultValue := reflect.New(v.Type()).Elem() defaultValue.SetInt(*params.defaultValue) if reflect.DeepEqual(v.Interface(), defaultValue.Interface()) { return bytesEncoder(nil), nil } } // If no default value is given then the zero value for the type is // assumed to be the default value. This isn't obviously the correct // behavior, but it's what Go has traditionally done. if params.optional && params.defaultValue == nil { if reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) { return bytesEncoder(nil), nil } } if v.Type() == rawValueType { rv := v.Interface().(RawValue) if len(rv.FullBytes) != 0 { return bytesEncoder(rv.FullBytes), nil } t := new(taggedEncoder) t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound})) t.body = bytesEncoder(rv.Bytes) return t, nil } matchAny, tag, isCompound, ok := getUniversalType(v.Type()) if !ok || matchAny { return nil, StructuralError{fmt.Sprintf("unknown Go type: %v", v.Type()), params.name} } if params.timeType != 0 && tag != TagUTCTime { return nil, StructuralError{"explicit time type given to non-time member", params.name} } if params.stringType != 0 && tag != TagPrintableString { return nil, StructuralError{"explicit string type given to non-string member", params.name} } switch tag { case TagPrintableString: if params.stringType == 0 { // This is a string without an explicit string type. We'll use // a PrintableString if the character set in the string is // sufficiently limited, otherwise we'll use a UTF8String. for _, r := range v.String() { if r >= utf8.RuneSelf || !isPrintable(byte(r), rejectAsterisk, rejectAmpersand) { if !utf8.ValidString(v.String()) { return nil, errors.New("asn1: string not valid UTF-8") } tag = TagUTF8String break } } } else { tag = params.stringType } case TagUTCTime: if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) { tag = TagGeneralizedTime } } if params.set { if tag != TagSequence { return nil, StructuralError{"non sequence tagged as set", params.name} } tag = TagSet } t := new(taggedEncoder) t.body, err = makeBody(v, params) if err != nil { return nil, err } bodyLen := t.body.Len() class := ClassUniversal if params.tag != nil { if params.application { class = ClassApplication } else if params.private { class = ClassPrivate } else { class = ClassContextSpecific } if params.explicit { t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{ClassUniversal, tag, bodyLen, isCompound})) tt := new(taggedEncoder) tt.body = t tt.tag = bytesEncoder(appendTagAndLength(tt.scratch[:0], tagAndLength{ class: class, tag: *params.tag, length: bodyLen + t.tag.Len(), isCompound: true, })) return tt, nil } // implicit tag. tag = *params.tag } t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{class, tag, bodyLen, isCompound})) return t, nil } // Marshal returns the ASN.1 encoding of val. // // In addition to the struct tags recognised by Unmarshal, the following can be // used: // // ia5: causes strings to be marshaled as ASN.1, IA5String values // omitempty: causes empty slices to be skipped // printable: causes strings to be marshaled as ASN.1, PrintableString values // utf8: causes strings to be marshaled as ASN.1, UTF8String values // utc: causes time.Time to be marshaled as ASN.1, UTCTime values // generalized: causes time.Time to be marshaled as ASN.1, GeneralizedTime values func Marshal(val interface{}) ([]byte, error) { return MarshalWithParams(val, "") } // MarshalWithParams allows field parameters to be specified for the // top-level element. The form of the params is the same as the field tags. func MarshalWithParams(val interface{}, params string) ([]byte, error) { e, err := makeField(reflect.ValueOf(val), parseFieldParameters(params)) if err != nil { return nil, err } b := make([]byte, e.Len()) e.Encode(b) return b, nil } google-certificate-transparency-go-2308f62/asn1/marshal_test.go000066400000000000000000000207571462611535200245330ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package asn1 import ( "bytes" "encoding/hex" "math/big" "reflect" "strings" "testing" "time" ) type intStruct struct { A int } type twoIntStruct struct { A int B int } type bigIntStruct struct { A *big.Int } type nestedStruct struct { A intStruct } type rawContentsStruct struct { Raw RawContent A int } type implicitTagTest struct { A int `asn1:"implicit,tag:5"` } type explicitTagTest struct { A int `asn1:"explicit,tag:5"` } type flagTest struct { A Flag `asn1:"tag:0,optional"` } type generalizedTimeTest struct { A time.Time `asn1:"generalized"` } type ia5StringTest struct { A string `asn1:"ia5"` } type printableStringTest struct { A string `asn1:"printable"` } type genericStringTest struct { A string } type optionalRawValueTest struct { A RawValue `asn1:"optional"` } type omitEmptyTest struct { A []string `asn1:"omitempty"` } type defaultTest struct { A int `asn1:"optional,default:1"` } type applicationTest struct { A int `asn1:"application,tag:0"` B int `asn1:"application,tag:1,explicit"` } type privateTest struct { A int `asn1:"private,tag:0"` B int `asn1:"private,tag:1,explicit"` C int `asn1:"private,tag:31"` // tag size should be 2 octet D int `asn1:"private,tag:128"` // tag size should be 3 octet } type numericStringTest struct { A string `asn1:"numeric"` } type testAuthKeyID struct { ID []byte `asn1:"optional,tag:0"` Issuer RawValue `asn1:"optional,tag:1"` SerialNumber *big.Int `asn1:"optional,tag:2"` } type testSET []int var PST = time.FixedZone("PST", -8*60*60) type marshalTest struct { in interface{} out string // hex encoded } func farFuture() time.Time { t, err := time.Parse(time.RFC3339, "2100-04-05T12:01:01Z") if err != nil { panic(err) } return t } var marshalTests = []marshalTest{ {10, "02010a"}, {127, "02017f"}, {128, "02020080"}, {-128, "020180"}, {-129, "0202ff7f"}, {intStruct{64}, "3003020140"}, {bigIntStruct{big.NewInt(0x123456)}, "30050203123456"}, {twoIntStruct{64, 65}, "3006020140020141"}, {nestedStruct{intStruct{127}}, "3005300302017f"}, {[]byte{1, 2, 3}, "0403010203"}, {implicitTagTest{64}, "3003850140"}, {explicitTagTest{64}, "3005a503020140"}, {flagTest{true}, "30028000"}, {flagTest{false}, "3000"}, {time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"}, {time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"}, {time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"}, {farFuture(), "180f32313030303430353132303130315a"}, {generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"}, {BitString{[]byte{0x80}, 1}, "03020780"}, {BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"}, {ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"}, {ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"}, {ObjectIdentifier([]int{2, 100, 3}), "0603813403"}, {"test", "130474657374"}, { "" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x' "137f" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "78787878787878787878787878787878787878787878787878787878787878", }, { "" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x' "138180" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878", }, {ia5StringTest{"test"}, "3006160474657374"}, {optionalRawValueTest{}, "3000"}, {printableStringTest{"test"}, "3006130474657374"}, {printableStringTest{"test*"}, "30071305746573742a"}, {genericStringTest{"test"}, "3006130474657374"}, {genericStringTest{"test*"}, "30070c05746573742a"}, {genericStringTest{"test&"}, "30070c057465737426"}, {rawContentsStruct{nil, 64}, "3003020140"}, {rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"}, {RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"}, {testSET([]int{10}), "310302010a"}, {omitEmptyTest{[]string{}}, "3000"}, {omitEmptyTest{[]string{"1"}}, "30053003130131"}, {"Σ", "0c02cea3"}, {defaultTest{0}, "3003020100"}, {defaultTest{1}, "3000"}, {defaultTest{2}, "3003020102"}, {applicationTest{1, 2}, "30084001016103020102"}, {privateTest{1, 2, 3, 4}, "3011c00101e103020102df1f0103df81000104"}, {numericStringTest{"1 9"}, "30051203312039"}, {testAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}, SerialNumber: big.NewInt(0x12233445566)}, "300e8004010203048206012233445566"}, {testAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}}, "3006800401020304"}, } func TestMarshal(t *testing.T) { for i, test := range marshalTests { data, err := Marshal(test.in) if err != nil { t.Errorf("#%d failed: %s", i, err) } out, _ := hex.DecodeString(test.out) if !bytes.Equal(out, data) { t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) } } } type marshalWithParamsTest struct { in interface{} params string out string // hex encoded } var marshalWithParamsTests = []marshalWithParamsTest{ {intStruct{10}, "set", "310302010a"}, {intStruct{10}, "application", "600302010a"}, {intStruct{10}, "private", "e00302010a"}, } func TestMarshalWithParams(t *testing.T) { for i, test := range marshalWithParamsTests { data, err := MarshalWithParams(test.in, test.params) if err != nil { t.Errorf("#%d failed: %s", i, err) } out, _ := hex.DecodeString(test.out) if !bytes.Equal(out, data) { t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) } } } type marshalErrTest struct { in interface{} err string } var marshalErrTests = []marshalErrTest{ {bigIntStruct{nil}, "empty integer"}, {numericStringTest{"a"}, "invalid character"}, {ia5StringTest{"\xb0"}, "invalid character"}, {printableStringTest{"!"}, "invalid character"}, } func TestMarshalError(t *testing.T) { for i, test := range marshalErrTests { _, err := Marshal(test.in) if err == nil { t.Errorf("#%d should fail, but success", i) continue } if !strings.Contains(err.Error(), test.err) { t.Errorf("#%d got: %v want %v", i, err, test.err) } } } func TestInvalidUTF8(t *testing.T) { _, err := Marshal(string([]byte{0xff, 0xff})) if err == nil { t.Errorf("invalid UTF8 string was accepted") } } func TestMarshalOID(t *testing.T) { var marshalTestsOID = []marshalTest{ {[]byte("\x06\x01\x30"), "0403060130"}, // bytes format returns a byte sequence \x04 // {ObjectIdentifier([]int{0}), "060100"}, // returns an error as OID 0.0 has the same encoding {[]byte("\x06\x010"), "0403060130"}, // same as above "\x06\x010" = "\x06\x01" + "0" {ObjectIdentifier([]int{2, 999, 3}), "0603883703"}, // Example of ITU-T X.690 {ObjectIdentifier([]int{0, 0}), "060100"}, // zero OID } for i, test := range marshalTestsOID { data, err := Marshal(test.in) if err != nil { t.Errorf("#%d failed: %s", i, err) } out, _ := hex.DecodeString(test.out) if !bytes.Equal(out, data) { t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) } } } func TestIssue11130(t *testing.T) { data := []byte("\x06\x010") // == \x06\x01\x30 == OID = 0 (the figure) var v interface{} // v has Zero value here and Elem() would panic _, err := Unmarshal(data, &v) if err != nil { t.Errorf("%v", err) return } if reflect.TypeOf(v).String() != reflect.TypeOf(ObjectIdentifier{}).String() { t.Errorf("marshal OID returned an invalid type") return } data1, err := Marshal(v) if err != nil { t.Errorf("%v", err) return } if !bytes.Equal(data, data1) { t.Errorf("got: %q, want: %q \n", data1, data) return } var v1 interface{} _, err = Unmarshal(data1, &v1) if err != nil { t.Errorf("%v", err) return } if !reflect.DeepEqual(v, v1) { t.Errorf("got: %#v data=%q , want : %#v data=%q\n ", v1, data1, v, data) } } func BenchmarkMarshal(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { for _, test := range marshalTests { Marshal(test.in) } } } google-certificate-transparency-go-2308f62/client/000077500000000000000000000000001462611535200221175ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/client/configpb/000077500000000000000000000000001462611535200237065ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/client/configpb/multilog.pb.go000066400000000000000000000246651462611535200265060ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.1 // protoc v3.20.1 // source: client/configpb/multilog.proto package configpb import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // TemporalLogConfig is a set of LogShardConfig messages, whose // time limits should be contiguous. type TemporalLogConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Shard []*LogShardConfig `protobuf:"bytes,1,rep,name=shard,proto3" json:"shard,omitempty"` } func (x *TemporalLogConfig) Reset() { *x = TemporalLogConfig{} if protoimpl.UnsafeEnabled { mi := &file_client_configpb_multilog_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TemporalLogConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*TemporalLogConfig) ProtoMessage() {} func (x *TemporalLogConfig) ProtoReflect() protoreflect.Message { mi := &file_client_configpb_multilog_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TemporalLogConfig.ProtoReflect.Descriptor instead. func (*TemporalLogConfig) Descriptor() ([]byte, []int) { return file_client_configpb_multilog_proto_rawDescGZIP(), []int{0} } func (x *TemporalLogConfig) GetShard() []*LogShardConfig { if x != nil { return x.Shard } return nil } // LogShardConfig describes the acceptable date range for a single shard of a temporal // log. type LogShardConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` // The log's public key in DER-encoded PKIX form. PublicKeyDer []byte `protobuf:"bytes,2,opt,name=public_key_der,json=publicKeyDer,proto3" json:"public_key_der,omitempty"` // not_after_start defines the start of the range of acceptable NotAfter // values, inclusive. // Leaving this unset implies no lower bound to the range. NotAfterStart *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=not_after_start,json=notAfterStart,proto3" json:"not_after_start,omitempty"` // not_after_limit defines the end of the range of acceptable NotAfter values, // exclusive. // Leaving this unset implies no upper bound to the range. NotAfterLimit *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=not_after_limit,json=notAfterLimit,proto3" json:"not_after_limit,omitempty"` } func (x *LogShardConfig) Reset() { *x = LogShardConfig{} if protoimpl.UnsafeEnabled { mi := &file_client_configpb_multilog_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogShardConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogShardConfig) ProtoMessage() {} func (x *LogShardConfig) ProtoReflect() protoreflect.Message { mi := &file_client_configpb_multilog_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogShardConfig.ProtoReflect.Descriptor instead. func (*LogShardConfig) Descriptor() ([]byte, []int) { return file_client_configpb_multilog_proto_rawDescGZIP(), []int{1} } func (x *LogShardConfig) GetUri() string { if x != nil { return x.Uri } return "" } func (x *LogShardConfig) GetPublicKeyDer() []byte { if x != nil { return x.PublicKeyDer } return nil } func (x *LogShardConfig) GetNotAfterStart() *timestamppb.Timestamp { if x != nil { return x.NotAfterStart } return nil } func (x *LogShardConfig) GetNotAfterLimit() *timestamppb.Timestamp { if x != nil { return x.NotAfterLimit } return nil } var File_client_configpb_multilog_proto protoreflect.FileDescriptor var file_client_configpb_multilog_proto_rawDesc = []byte{ 0x0a, 0x1e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x43, 0x0a, 0x11, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0xd0, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_client_configpb_multilog_proto_rawDescOnce sync.Once file_client_configpb_multilog_proto_rawDescData = file_client_configpb_multilog_proto_rawDesc ) func file_client_configpb_multilog_proto_rawDescGZIP() []byte { file_client_configpb_multilog_proto_rawDescOnce.Do(func() { file_client_configpb_multilog_proto_rawDescData = protoimpl.X.CompressGZIP(file_client_configpb_multilog_proto_rawDescData) }) return file_client_configpb_multilog_proto_rawDescData } var file_client_configpb_multilog_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_client_configpb_multilog_proto_goTypes = []interface{}{ (*TemporalLogConfig)(nil), // 0: configpb.TemporalLogConfig (*LogShardConfig)(nil), // 1: configpb.LogShardConfig (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp } var file_client_configpb_multilog_proto_depIdxs = []int32{ 1, // 0: configpb.TemporalLogConfig.shard:type_name -> configpb.LogShardConfig 2, // 1: configpb.LogShardConfig.not_after_start:type_name -> google.protobuf.Timestamp 2, // 2: configpb.LogShardConfig.not_after_limit:type_name -> google.protobuf.Timestamp 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_client_configpb_multilog_proto_init() } func file_client_configpb_multilog_proto_init() { if File_client_configpb_multilog_proto != nil { return } if !protoimpl.UnsafeEnabled { file_client_configpb_multilog_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TemporalLogConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_client_configpb_multilog_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogShardConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_client_configpb_multilog_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_client_configpb_multilog_proto_goTypes, DependencyIndexes: file_client_configpb_multilog_proto_depIdxs, MessageInfos: file_client_configpb_multilog_proto_msgTypes, }.Build() File_client_configpb_multilog_proto = out.File file_client_configpb_multilog_proto_rawDesc = nil file_client_configpb_multilog_proto_goTypes = nil file_client_configpb_multilog_proto_depIdxs = nil } google-certificate-transparency-go-2308f62/client/configpb/multilog.proto000066400000000000000000000030721462611535200266310ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package configpb; option go_package = "github.com/google/certificate-transparency-go/client/multilog/configpb"; import "google/protobuf/timestamp.proto"; // TemporalLogConfig is a set of LogShardConfig messages, whose // time limits should be contiguous. message TemporalLogConfig { repeated LogShardConfig shard = 1; } // LogShardConfig describes the acceptable date range for a single shard of a temporal // log. message LogShardConfig { string uri = 1; // The log's public key in DER-encoded PKIX form. bytes public_key_der = 2; // not_after_start defines the start of the range of acceptable NotAfter // values, inclusive. // Leaving this unset implies no lower bound to the range. google.protobuf.Timestamp not_after_start = 3; // not_after_limit defines the end of the range of acceptable NotAfter values, // exclusive. // Leaving this unset implies no upper bound to the range. google.protobuf.Timestamp not_after_limit = 4; } google-certificate-transparency-go-2308f62/client/ctclient/000077500000000000000000000000001462611535200237245ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/client/ctclient/cmd/000077500000000000000000000000001462611535200244675ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/client/ctclient/cmd/bisect.go000066400000000000000000000053531462611535200262750ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "fmt" "sort" ct "github.com/google/certificate-transparency-go" "github.com/spf13/cobra" "k8s.io/klog/v2" ) func init() { cmd := cobra.Command{ Use: fmt.Sprintf("bisect %s --timestamp=ts [--chain] [--text=false]", connectionFlags), Aliases: []string{"find-timestamp"}, Short: "Find a log entry by timestamp", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runBisect(cmd.Context()) }, } cmd.Flags().Int64Var(×tamp, "timestamp", 0, "Timestamp to use for inclusion checking") // TODO(pavelkalinnikov): Don't share these parameters with get-entries. cmd.Flags().BoolVar(&chainOut, "chain", false, "Display entire certificate chain") cmd.Flags().BoolVar(&textOut, "text", true, "Display certificates as text") rootCmd.AddCommand(&cmd) } // runBisect runs the bisect command. func runBisect(ctx context.Context) { logClient := connect(ctx) if timestamp == 0 { klog.Exit("No -timestamp option supplied") } target := timestamp sth, err := logClient.GetSTH(ctx) if err != nil { exitWithDetails(err) } getEntry := func(idx int64) *ct.RawLogEntry { entries, err := logClient.GetRawEntries(ctx, idx, idx) if err != nil { exitWithDetails(err) } if l := len(entries.Entries); l != 1 { klog.Exitf("Unexpected number (%d) of entries received requesting index %d", l, idx) } logEntry, err := ct.RawLogEntryFromLeaf(idx, &entries.Entries[0]) if err != nil { klog.Exitf("Failed to parse leaf %d: %v", idx, err) } return logEntry } // Performing a binary search assumes that the timestamps are monotonically // increasing. idx := sort.Search(int(sth.TreeSize), func(idx int) bool { klog.V(1).Infof("check timestamp at index %d", idx) entry := getEntry(int64(idx)) return entry.Leaf.TimestampedEntry.Timestamp >= uint64(target) }) when := ct.TimestampToTime(uint64(target)) if idx >= int(sth.TreeSize) { fmt.Printf("No entry with timestamp>=%d (%v) found up to tree size %d\n", target, when, sth.TreeSize) return } fmt.Printf("First entry with timestamp>=%d (%v) found at index %d\n", target, when, idx) showRawLogEntry(getEntry(int64(idx))) } google-certificate-transparency-go-2308f62/client/ctclient/cmd/get_consistency_proof.go000066400000000000000000000074431462611535200314330ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "github.com/google/certificate-transparency-go/client" "github.com/spf13/cobra" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" "k8s.io/klog/v2" ) var ( treeHash string prevSize uint64 prevHash string ) func init() { cmd := cobra.Command{ Use: fmt.Sprintf("get-consistency-proof %s --size=N --tree_hash=hash --prev_size=N --prev_hash=hash", connectionFlags), Aliases: []string{"getconsistencyproof", "consistency-proof", "consistency"}, Short: "Fetch and verify a consistency proof between two tree states", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runGetConsistencyProof(cmd.Context()) }, } // TODO(pavelkalinnikov): Don't share this parameter with get-inclusion-proof. cmd.Flags().Uint64Var(&treeSize, "size", 0, "Tree size to query at") cmd.Flags().StringVar(&treeHash, "tree_hash", "", "Tree hash to check against (as hex string or base64)") cmd.Flags().Uint64Var(&prevSize, "prev_size", 0, "Previous tree size to get consistency against") cmd.Flags().StringVar(&prevHash, "prev_hash", "", "Previous tree hash to check against (as hex string or base64)") rootCmd.AddCommand(&cmd) } // runGetConsistencyProof runs the get-consistency-proof command. func runGetConsistencyProof(ctx context.Context) { logClient := connect(ctx) if treeSize <= 0 { klog.Exit("No valid --size supplied") } if prevSize <= 0 { klog.Exit("No valid --prev_size supplied") } var hash1, hash2 []byte if prevHash != "" { var err error hash1, err = hashFromString(prevHash) if err != nil { klog.Exitf("Invalid --prev_hash: %v", err) } } if treeHash != "" { var err error hash2, err = hashFromString(treeHash) if err != nil { klog.Exitf("Invalid --tree_hash: %v", err) } } if (hash1 != nil) != (hash2 != nil) { klog.Exitf("Need both --prev_hash and --tree_hash or neither") } getConsistencyProofBetween(ctx, logClient, prevSize, treeSize, hash1, hash2) } func getConsistencyProofBetween(ctx context.Context, logClient client.CheckLogClient, first, second uint64, prevHash, treeHash []byte) { pf, err := logClient.GetSTHConsistency(ctx, uint64(first), uint64(second)) if err != nil { exitWithDetails(err) } fmt.Printf("Consistency proof from size %d to size %d:\n", first, second) for _, e := range pf { fmt.Printf(" %x\n", e) } if prevHash == nil || treeHash == nil { return } // We have tree hashes so we can verify the proof. if err := proof.VerifyConsistency(rfc6962.DefaultHasher, first, second, pf, prevHash, treeHash); err != nil { klog.Exitf("Failed to VerifyConsistency(%x @size=%d, %x @size=%d): %v", prevHash, first, treeHash, second, err) } fmt.Printf("Verified that hash %x @%d + proof = hash %x @%d\n", prevHash, first, treeHash, second) } func hashFromString(input string) ([]byte, error) { hash, err := hex.DecodeString(input) if err == nil && len(hash) == sha256.Size { return hash, nil } hash, err = base64.StdEncoding.DecodeString(input) if err == nil && len(hash) == sha256.Size { return hash, nil } return nil, fmt.Errorf("hash value %q failed to parse as 32-byte hex or base64", input) } google-certificate-transparency-go-2308f62/client/ctclient/cmd/get_entries.go000066400000000000000000000066221462611535200273340ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "encoding/pem" "fmt" "os" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/spf13/cobra" "k8s.io/klog/v2" ) var ( getFirst int64 getLast int64 chainOut bool textOut bool ) func init() { cmd := cobra.Command{ Use: fmt.Sprintf("get-entries %s --first=idx [--last=idx]", connectionFlags), Aliases: []string{"getentries", "entries"}, Short: "Fetch a range of entries in the log", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runGetEntries(cmd.Context()) }, } cmd.Flags().Int64Var(&getFirst, "first", -1, "First entry to get") cmd.Flags().Int64Var(&getLast, "last", -1, "Last entry to get") cmd.Flags().BoolVar(&chainOut, "chain", false, "Display entire certificate chain") cmd.Flags().BoolVar(&textOut, "text", true, "Display certificates as text") rootCmd.AddCommand(&cmd) } // runGetEntries runs the get-entries command. func runGetEntries(ctx context.Context) { logClient := connect(ctx) if getFirst == -1 { klog.Exit("No -first option supplied") } if getLast == -1 { getLast = getFirst } rsp, err := logClient.GetRawEntries(ctx, getFirst, getLast) if err != nil { exitWithDetails(err) } for i, rawEntry := range rsp.Entries { index := getFirst + int64(i) rle, err := ct.RawLogEntryFromLeaf(index, &rawEntry) if err != nil { fmt.Printf("Index=%d Failed to unmarshal leaf entry: %v", index, err) continue } showRawLogEntry(rle) } } func showRawLogEntry(rle *ct.RawLogEntry) { ts := rle.Leaf.TimestampedEntry when := ct.TimestampToTime(ts.Timestamp) fmt.Printf("Index=%d Timestamp=%d (%v) ", rle.Index, ts.Timestamp, when) switch ts.EntryType { case ct.X509LogEntryType: fmt.Printf("X.509 certificate:\n") showRawCert(*ts.X509Entry) case ct.PrecertLogEntryType: fmt.Printf("pre-certificate from issuer with keyhash %x:\n", ts.PrecertEntry.IssuerKeyHash) showRawCert(rle.Cert) // As-submitted: with signature and poison. default: fmt.Printf("Unhandled log entry type %d\n", ts.EntryType) } if chainOut { for _, c := range rle.Chain { showRawCert(c) } } } func showRawCert(cert ct.ASN1Cert) { if textOut { c, err := x509.ParseCertificate(cert.Data) if err != nil { klog.Errorf("Error parsing certificate: %q", err.Error()) } if c == nil { return } showParsedCert(c) } else { showPEMData(cert.Data) } } func showParsedCert(cert *x509.Certificate) { if textOut { fmt.Printf("%s\n", x509util.CertificateToString(cert)) } else { showPEMData(cert.Raw) } } func showPEMData(data []byte) { if err := pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: data}); err != nil { klog.Errorf("Failed to PEM encode cert: %q", err.Error()) } } google-certificate-transparency-go-2308f62/client/ctclient/cmd/get_inclusion_proof.go000066400000000000000000000127411462611535200310720ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "crypto/sha256" "encoding/pem" "fmt" "os" "regexp" "strconv" "strings" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/x509" "github.com/spf13/cobra" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" "k8s.io/klog/v2" ) var ( leafHash string certChain string timestamp int64 treeSize uint64 ) func init() { cmd := cobra.Command{ Use: fmt.Sprintf("get-inclusion-proof %s {--leaf_hash=hash | --cert_chain=file} [--timestamp=ts] [--size=N]", connectionFlags), Aliases: []string{"getinclusionproof", "inclusion-proof", "inclusion"}, Short: "Fetch and verify the inclusion proof for an entry", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runGetInclusionProof(cmd.Context()) }, } cmd.Flags().StringVar(&leafHash, "leaf_hash", "", "Leaf hash to retrieve (as hex string or base64)") cmd.Flags().StringVar(&certChain, "cert_chain", "", "Name of file containing certificate chain as concatenated PEM files") cmd.Flags().Int64Var(×tamp, "timestamp", 0, "Timestamp to use for inclusion checking") cmd.Flags().Uint64Var(&treeSize, "size", 0, "Tree size to query at") rootCmd.AddCommand(&cmd) } // runGetInclusionProof runs the get-inclusion-proof command. func runGetInclusionProof(ctx context.Context) { logClient := connect(ctx) var hash []byte if len(leafHash) > 0 { var err error hash, err = hashFromString(leafHash) if err != nil { klog.Exitf("Invalid --leaf_hash supplied: %v", err) } } else if len(certChain) > 0 { // Build a leaf hash from the chain and a timestamp. chain, entryTimestamp := chainFromFile(certChain) if timestamp != 0 { entryTimestamp = timestamp // Use user-specified timestamp. } if entryTimestamp == 0 { klog.Exit("No timestamp available to accompany certificate") } var leafEntry *ct.MerkleTreeLeaf cert, err := x509.ParseCertificate(chain[0].Data) if x509.IsFatal(err) { klog.Warningf("Failed to parse leaf certificate: %v", err) leafEntry = ct.CreateX509MerkleTreeLeaf(chain[0], uint64(entryTimestamp)) } else if cert.IsPrecertificate() { leafEntry, err = ct.MerkleTreeLeafFromRawChain(chain, ct.PrecertLogEntryType, uint64(entryTimestamp)) if err != nil { klog.Exitf("Failed to build pre-certificate leaf entry: %v", err) } } else { leafEntry = ct.CreateX509MerkleTreeLeaf(chain[0], uint64(entryTimestamp)) } leafHash, err := ct.LeafHashForLeaf(leafEntry) if err != nil { klog.Exitf("Failed to create hash of leaf: %v", err) } hash = leafHash[:] // Print a warning if this timestamp is still within the MMD window. when := ct.TimestampToTime(uint64(entryTimestamp)) if age := time.Since(when); age < logMMD { klog.Warningf("WARNING: Timestamp (%v) is with MMD window (%v), log may not have incorporated this entry yet.", when, logMMD) } } if len(hash) != sha256.Size { klog.Exit("No leaf hash available") } getInclusionProofForHash(ctx, logClient, hash) } func getInclusionProofForHash(ctx context.Context, logClient client.CheckLogClient, hash []byte) { var sth *ct.SignedTreeHead size := treeSize if size <= 0 { var err error sth, err = logClient.GetSTH(ctx) if err != nil { exitWithDetails(err) } size = sth.TreeSize } // Display the inclusion proof. rsp, err := logClient.GetProofByHash(ctx, hash, size) if err != nil { exitWithDetails(err) } fmt.Printf("Inclusion proof for index %d in tree of size %d:\n", rsp.LeafIndex, size) for _, e := range rsp.AuditPath { fmt.Printf(" %x\n", e) } if sth != nil { // If we retrieved an STH we can verify the proof. if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(rsp.LeafIndex), sth.TreeSize, hash, rsp.AuditPath, sth.SHA256RootHash[:]); err != nil { klog.Exitf("Failed to VerifyInclusion(%d, %d)=%v", rsp.LeafIndex, sth.TreeSize, err) } fmt.Printf("Verified that hash %x + proof = root hash %x\n", hash, sth.SHA256RootHash) } } func chainFromFile(filename string) ([]ct.ASN1Cert, int64) { contents, err := os.ReadFile(filename) if err != nil { klog.Exitf("Failed to read certificate file: %v", err) } rest := contents var chain []ct.ASN1Cert for { var block *pem.Block block, rest = pem.Decode(rest) if block == nil { break } if block.Type == "CERTIFICATE" { chain = append(chain, ct.ASN1Cert{Data: block.Bytes}) } } if len(chain) == 0 { klog.Exitf("No certificates found in %s", certChain) } // Also look for something like a text timestamp for convenience. var timestamp int64 tsRE := regexp.MustCompile(`Timestamp[:=](\d+)`) for _, line := range strings.Split(string(contents), "\n") { x := tsRE.FindStringSubmatch(line) if len(x) > 1 { timestamp, err = strconv.ParseInt(x[1], 10, 64) if err != nil { break } } } return chain, timestamp } google-certificate-transparency-go-2308f62/client/ctclient/cmd/get_roots.go000066400000000000000000000026251462611535200270300ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "fmt" "github.com/spf13/cobra" ) func init() { cmd := cobra.Command{ Use: fmt.Sprintf("get-roots %s", connectionFlags), Aliases: []string{"getroots", "roots"}, Short: "Fetch the root certificates accepted by the log", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runGetRoots(cmd.Context()) }, } // TODO(pavelkalinnikov): Don't share this parameter with get-entries. cmd.Flags().BoolVar(&textOut, "text", true, "Display certificates as text") rootCmd.AddCommand(&cmd) } // runGetRoots runs the get-roots command. func runGetRoots(ctx context.Context) { logClient := connect(ctx) roots, err := logClient.GetAcceptedRoots(ctx) if err != nil { exitWithDetails(err) } for _, root := range roots { showRawCert(root) } } google-certificate-transparency-go-2308f62/client/ctclient/cmd/get_sth.go000066400000000000000000000027471462611535200264650ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/spf13/cobra" ) func init() { rootCmd.AddCommand(&cobra.Command{ Use: fmt.Sprintf("get-sth %s", connectionFlags), Aliases: []string{"sth"}, Short: "Fetch the latest STH of the log", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runGetSTH(cmd.Context()) }, }) } // runGetSTH runs the get-sth command. func runGetSTH(ctx context.Context) { logClient := connect(ctx) sth, err := logClient.GetSTH(ctx) if err != nil { exitWithDetails(err) } // Display the STH. when := ct.TimestampToTime(sth.Timestamp) fmt.Printf("%v (timestamp %d): Got STH for %v log (size=%d) at %v, hash %x\n", when, sth.Timestamp, sth.Version, sth.TreeSize, logClient.BaseURI(), sth.SHA256RootHash) fmt.Printf("%v\n", signatureToString(&sth.TreeHeadSignature)) } google-certificate-transparency-go-2308f62/client/ctclient/cmd/root.go000066400000000000000000000111401462611535200257760ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cmd implements subcommands of ctclient, the command-line utility for // interacting with CT logs. package cmd import ( "context" "crypto/tls" "flag" "fmt" "net/http" "os" "strings" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509util" "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/klog/v2" ) const connectionFlags = "{--log_uri uri | --log_name name [--log_list {file|uri}]} [--pub_key file]" var ( skipHTTPSVerify bool logName string logList string logURI string pubKey string ) func init() { // Add flags added with "flag" package, including klog, to Cobra flag set. pflag.CommandLine.AddGoFlagSet(flag.CommandLine) flags := rootCmd.PersistentFlags() flags.BoolVar(&skipHTTPSVerify, "skip_https_verify", false, "Skip verification of HTTPS transport connection") flags.StringVar(&logName, "log_name", "", "Name of log to retrieve information from --log_list for") flags.StringVar(&logList, "log_list", loglist3.AllLogListURL, "Location of master log list (URL or filename)") flags.StringVar(&logURI, "log_uri", "https://ct.googleapis.com/rocketeer", "CT log base URI") flags.StringVar(&pubKey, "pub_key", "", "Name of file containing log's public key") } // rootCmd represents the base command when called without any subcommands. var rootCmd = &cobra.Command{ Use: "ctclient", Short: "A command line client for Certificate Transparency logs", PersistentPreRun: func(cmd *cobra.Command, _ []string) { flag.Parse() }, } // Execute adds all child commands to the root command and sets flags // appropriately. It needs to be called exactly once by main(). func Execute() { if err := rootCmd.Execute(); err != nil { klog.Fatal(err) } } func signatureToString(signed *ct.DigitallySigned) string { return fmt.Sprintf("Signature: Hash=%v Sign=%v Value=%x", signed.Algorithm.Hash, signed.Algorithm.Signature, signed.Signature) } func exitWithDetails(err error) { if err, ok := err.(client.RspError); ok { klog.Infof("HTTP details: status=%d, body:\n%s", err.StatusCode, err.Body) } klog.Exit(err.Error()) } func connect(ctx context.Context) *client.LogClient { var tlsCfg *tls.Config if skipHTTPSVerify { klog.Warning("Skipping HTTPS connection verification") tlsCfg = &tls.Config{InsecureSkipVerify: skipHTTPSVerify} } httpClient := &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ TLSHandshakeTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, ExpectContinueTimeout: 1 * time.Second, TLSClientConfig: tlsCfg, }, } opts := jsonclient.Options{UserAgent: "ct-go-ctclient/1.0"} if pubKey != "" { pubkey, err := os.ReadFile(pubKey) if err != nil { klog.Exit(err) } opts.PublicKey = string(pubkey) } uri := logURI if logName != "" { llData, err := x509util.ReadFileOrURL(logList, httpClient) if err != nil { klog.Exitf("Failed to read log list: %v", err) } ll, err := loglist3.NewFromJSON(llData) if err != nil { klog.Exitf("Failed to build log list: %v", err) } logs := ll.FindLogByName(logName) if len(logs) == 0 { klog.Exitf("No log with name like %q found in loglist %q", logName, logList) } if len(logs) > 1 { logNames := make([]string, len(logs)) for i, log := range logs { logNames[i] = fmt.Sprintf("%q", log.Description) } klog.Exitf("Multiple logs with name like %q found in loglist: %s", logName, strings.Join(logNames, ",")) } uri = logs[0].URL if opts.PublicKey == "" { opts.PublicKeyDER = logs[0].Key } } klog.V(1).Infof("Use CT log at %s", uri) logClient, err := client.New(uri, httpClient, opts) if err != nil { klog.Exit(err) } return logClient } google-certificate-transparency-go-2308f62/client/ctclient/cmd/upload.go000066400000000000000000000060211462611535200263010ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "fmt" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/spf13/cobra" "k8s.io/klog/v2" ) var logMMD time.Duration func init() { cmd := cobra.Command{ Use: fmt.Sprintf("upload %s --cert_chain=file [--log_mmd=dur]", connectionFlags), Aliases: []string{"add-chain"}, Short: "Submit a certificate (pre-)chain to the log", Args: cobra.MaximumNArgs(0), Run: func(cmd *cobra.Command, _ []string) { runUpload(cmd.Context()) }, } // TODO(pavelkalinnikov): Don't share this parameter with get-inclusion-proof. cmd.Flags().StringVar(&certChain, "cert_chain", "", "Name of file containing certificate chain as concatenated PEM files") cmd.Flags().DurationVar(&logMMD, "log_mmd", 24*time.Hour, "Log's maximum merge delay") rootCmd.AddCommand(&cmd) } // runUpload runs the upload command. func runUpload(ctx context.Context) { logClient := connect(ctx) if certChain == "" { klog.Exitf("No certificate chain file specified with -cert_chain") } chain, _ := chainFromFile(certChain) // Examine the leaf to see if it looks like a pre-certificate. isPrecert := false leaf, err := x509.ParseCertificate(chain[0].Data) if err == nil { count, _ := x509util.OIDInExtensions(x509.OIDExtensionCTPoison, leaf.Extensions) if count > 0 { isPrecert = true fmt.Print("Uploading pre-certificate to log\n") } } var sct *ct.SignedCertificateTimestamp if isPrecert { sct, err = logClient.AddPreChain(ctx, chain) } else { sct, err = logClient.AddChain(ctx, chain) } if err != nil { exitWithDetails(err) } // Calculate the leaf hash. leafEntry := ct.CreateX509MerkleTreeLeaf(chain[0], sct.Timestamp) leafHash, err := ct.LeafHashForLeaf(leafEntry) if err != nil { klog.Exitf("Failed to create hash of leaf: %v", err) } // Display the SCT. when := ct.TimestampToTime(sct.Timestamp) fmt.Printf("Uploaded chain of %d certs to %v log at %v, timestamp: %d (%v)\n", len(chain), sct.SCTVersion, logClient.BaseURI(), sct.Timestamp, when) fmt.Printf("LogID: %x\n", sct.LogID.KeyID[:]) fmt.Printf("LeafHash: %x\n", leafHash) fmt.Printf("Signature: %v\n", signatureToString(&sct.Signature)) age := time.Since(when) if age > logMMD { // SCT's timestamp is old enough that the certificate should be included. getInclusionProofForHash(ctx, logClient, leafHash[:]) } } google-certificate-transparency-go-2308f62/client/ctclient/main.go000066400000000000000000000015131462611535200251770ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ctclient is a command-line utility for interacting with CT logs. package main import ( "github.com/google/certificate-transparency-go/client/ctclient/cmd" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) cmd.Execute() } google-certificate-transparency-go-2308f62/client/getentries.go000066400000000000000000000043661462611535200246300ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "context" "errors" "strconv" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/x509" ) // GetRawEntries exposes the /ct/v1/get-entries result with only the JSON parsing done. func (c *LogClient) GetRawEntries(ctx context.Context, start, end int64) (*ct.GetEntriesResponse, error) { if end < 0 { return nil, errors.New("end should be >= 0") } if end < start { return nil, errors.New("start should be <= end") } params := map[string]string{ "start": strconv.FormatInt(start, 10), "end": strconv.FormatInt(end, 10), } var resp ct.GetEntriesResponse if _, _, err := c.GetAndParse(ctx, ct.GetEntriesPath, params, &resp); err != nil { return nil, err } return &resp, nil } // GetEntries attempts to retrieve the entries in the sequence [start, end] from the CT log server // (RFC6962 s4.6) as parsed [pre-]certificates for convenience, held in a slice of ct.LogEntry structures. // However, this does mean that any certificate parsing failures will cause a failure of the whole // retrieval operation; for more robust retrieval of parsed certificates, use GetRawEntries() and invoke // ct.LogEntryFromLeaf() on each individual entry. func (c *LogClient) GetEntries(ctx context.Context, start, end int64) ([]ct.LogEntry, error) { resp, err := c.GetRawEntries(ctx, start, end) if err != nil { return nil, err } entries := make([]ct.LogEntry, len(resp.Entries)) for i, entry := range resp.Entries { index := start + int64(i) logEntry, err := ct.LogEntryFromLeaf(index, &entry) if x509.IsFatal(err) { return nil, err } entries[i] = *logEntry } return entries, nil } google-certificate-transparency-go-2308f62/client/logclient.go000066400000000000000000000176271462611535200244430ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package client is a CT log client implementation and contains types and code // for interacting with RFC6962-compliant CT Log instances. // See http://tools.ietf.org/html/rfc6962 for details package client import ( "context" "encoding/base64" "fmt" "net/http" "strconv" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/tls" ) // LogClient represents a client for a given CT Log instance type LogClient struct { jsonclient.JSONClient } // CheckLogClient is an interface that allows (just) checking of various log contents. type CheckLogClient interface { BaseURI() string GetSTH(context.Context) (*ct.SignedTreeHead, error) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) } // New constructs a new LogClient instance. // |uri| is the base URI of the CT log instance to interact with, e.g. // https://ct.googleapis.com/pilot // |hc| is the underlying client to be used for HTTP requests to the CT log. // |opts| can be used to provide a custom logger interface and a public key // for signature verification. func New(uri string, hc *http.Client, opts jsonclient.Options) (*LogClient, error) { logClient, err := jsonclient.New(uri, hc, opts) if err != nil { return nil, err } return &LogClient{*logClient}, err } // RspError represents a server error including HTTP information. type RspError = jsonclient.RspError // Attempts to add |chain| to the log, using the api end-point specified by // |path|. If provided context expires before submission is complete an // error will be returned. func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { var resp ct.AddChainResponse var req ct.AddChainRequest for _, link := range chain { req.Chain = append(req.Chain, link.Data) } httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp) if err != nil { return nil, err } var ds ct.DigitallySigned if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil { return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } else if len(rest) > 0 { return nil, RspError{ Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)), StatusCode: httpRsp.StatusCode, Body: body, } } exts, err := base64.StdEncoding.DecodeString(resp.Extensions) if err != nil { return nil, RspError{ Err: fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err), StatusCode: httpRsp.StatusCode, Body: body, } } var logID ct.LogID copy(logID.KeyID[:], resp.ID) sct := &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, Timestamp: resp.Timestamp, Extensions: ct.CTExtensions(exts), Signature: ds, } if err := c.VerifySCTSignature(*sct, ctype, chain); err != nil { return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } return sct, nil } // AddChain adds the (DER represented) X509 |chain| to the log. func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain) } // AddPreChain adds the (DER represented) Precertificate |chain| to the log. func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain) } // GetSTH retrieves the current STH from the log. // Returns a populated SignedTreeHead, or a non-nil error (which may be of type // RspError if a raw http.Response is available). func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { var resp ct.GetSTHResponse httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp) if err != nil { return nil, err } sth, err := resp.ToSignedTreeHead() if err != nil { return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } if err := c.VerifySTHSignature(*sth); err != nil { return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } return sth, nil } // VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is // successful. func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error { if c.Verifier == nil { // Can't verify signatures without a verifier return nil } return c.Verifier.VerifySTHSignature(sth) } // VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain. func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error { if c.Verifier == nil { // Can't verify signatures without a verifier return nil } leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp) if err != nil { return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err) } entry := ct.LogEntry{Leaf: *leaf} return c.Verifier.VerifySCTSignature(sct, entry) } // GetSTHConsistency retrieves the consistency proof between two snapshots. func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) { base10 := 10 params := map[string]string{ "first": strconv.FormatUint(first, base10), "second": strconv.FormatUint(second, base10), } var resp ct.GetSTHConsistencyResponse if _, _, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp); err != nil { return nil, err } return resp.Consistency, nil } // GetProofByHash returns an audit path for the hash of an SCT. func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) { b64Hash := base64.StdEncoding.EncodeToString(hash) base10 := 10 params := map[string]string{ "tree_size": strconv.FormatUint(treeSize, base10), "hash": b64Hash, } var resp ct.GetProofByHashResponse if _, _, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp); err != nil { return nil, err } return &resp, nil } // GetAcceptedRoots retrieves the set of acceptable root certificates for a log. func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) { var resp ct.GetRootsResponse httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp) if err != nil { return nil, err } var roots []ct.ASN1Cert for _, cert64 := range resp.Certificates { cert, err := base64.StdEncoding.DecodeString(cert64) if err != nil { return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } roots = append(roots, ct.ASN1Cert{Data: cert}) } return roots, nil } // GetEntryAndProof returns a log entry and audit path for the index of a leaf. func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) { base10 := 10 params := map[string]string{ "leaf_index": strconv.FormatUint(index, base10), "tree_size": strconv.FormatUint(treeSize, base10), } var resp ct.GetEntryAndProofResponse if _, _, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp); err != nil { return nil, err } return &resp, nil } google-certificate-transparency-go-2308f62/client/logclient_test.go000066400000000000000000001276171462611535200255030ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client_test import ( "bytes" "context" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "math" "net/http" "net/http/httptest" "reflect" "regexp" "strconv" "strings" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) func dh(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic(err) } return b } const ( ValidSTHResponseTreeSize = 3721782 ValidSTHResponseTimestamp = 1396609800587 ValidSTHResponseSHA256RootHash = "SxKOxksguvHPyUaKYKXoZHzXl91Q257+JQ0AUMlFfeo=" ValidSTHResponseTreeHeadSignature = "BAMARjBEAiBUYO2tODlUUw4oWGiVPUHqZadRRyXs9T2rSXchA79VsQIgLASkQv3cu4XdPFCZbgFkIUefniNPCpO3LzzHX53l+wg=" PrecertEntryB64 = "AAAAAAFLSYHwyAABN2DieQ8zpJj5tsFJ/s/KOZOVS1NvvzatRdCoQVt5M30ABHowggR2oAMCAQICEAUyKYw5aj4l/KoZd+gntfMwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHzAdBgNVBAsTFkZPUiBURVNUIFBVUlBPU0VTIE9OTFkxJTAjBgNVBAMTHEdlb1RydXN0IEVWIFNTTCBURVNUIENBIC0gRzQwHhcNMTUwMjAyMDAwMDAwWhcNMTYwMjI3MjM1OTU5WjCBwzETMBEGCysGAQQBgjc8AgEDEwJHQjEbMBkGCysGAQQBgjc8AgECFApDYWxpZm9ybmlhMR4wHAYLKwYBBAGCNzwCAQEMDU1vdW50YWluIFZpZXcxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MR0wGwYDVQQKDBRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEWMBQGA1UEAwwNc2RmZWRzZi50cnVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGdl97zn/gpxl6gmaMlcpizP/Z1RR/cVkGiIjR67kpWIB9MGkBvLxmBXYbewaYRdo59VWyOM6fxtMeNsZzOrlQOl64fBmCy7k+M/yBFuEqdoig0l0RAbs6u0LCNRv2rNUOz2G6nCGJ6YaUpt5Onatxrd2vI1bPU/iHixKqSz9M7RedBIGjgaDor7/rR3y/DILjdvwL/tgPSz3R5gnf9lla1rNRWWbDl12HgLc+VxTCVVVqTGtW/qbSWfARdXxLeLWtTfNk68q2LReVUC9QyeYdtE+N2+2SXeOEN+lYWW5Ab036d7k5GAntMBzLKftZEkYYquvaiSkqu2PSaCSLKT7UCAwEAAaOCAdEwggHNMEcGA1UdEQRAMD6CDWtqYXNkaGYudHJ1c3SCC3NzZGZzLnRydXN0gg1zZGZlZHNmLnRydXN0ghF3d3cuc2RmZWRzZi50cnVzdDAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIFoDArBgNVHR8EJDAiMCCgHqAchhpodHRwOi8vZ20uc3ltY2IuY29tL2dtLmNybDCBoAYDVR0gBIGYMIGVMIGSBgkrBgEEAfAiAQYwgYQwPwYIKwYBBQUHAgEWM2h0dHBzOi8vd3d3Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvcmVwb3NpdG9yeS9sZWdhbDBBBggrBgEFBQcCAjA1DDNodHRwczovL3d3dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL3JlcG9zaXRvcnkvbGVnYWwwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFLFplGGr5ssMTOdZr1pJixgzweFHMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL2dtLnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2dtLnN5bWNiLmNvbS9nbS5jcnQAAA==" PrecertEntryExtraDataB64 = "AAWnMIIFozCCBIugAwIBAgIQBTIpjDlqPiX8qhl36Ce18zANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEfMB0GA1UECxMWRk9SIFRFU1QgUFVSUE9TRVMgT05MWTElMCMGA1UEAxMcR2VvVHJ1c3QgRVYgU1NMIFRFU1QgQ0EgLSBHNDAeFw0xNTAyMDIwMDAwMDBaFw0xNjAyMjcyMzU5NTlaMIHDMRMwEQYLKwYBBAGCNzwCAQMTAkdCMRswGQYLKwYBBAGCNzwCAQIUCkNhbGlmb3JuaWExHjAcBgsrBgEEAYI3PAIBAQwNTW91bnRhaW4gVmlldzELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHTAbBgNVBAoMFFN5bWFudGVjIENvcnBvcmF0aW9uMRYwFAYDVQQDDA1zZGZlZHNmLnRydXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZ2X3vOf+CnGXqCZoyVymLM/9nVFH9xWQaIiNHruSlYgH0waQG8vGYFdht7BphF2jn1VbI4zp/G0x42xnM6uVA6Xrh8GYLLuT4z/IEW4Sp2iKDSXREBuzq7QsI1G/as1Q7PYbqcIYnphpSm3k6dq3Gt3a8jVs9T+IeLEqpLP0ztF50EgaOBoOivv+tHfL8MguN2/Av+2A9LPdHmCd/2WVrWs1FZZsOXXYeAtz5XFMJVVWpMa1b+ptJZ8BF1fEt4ta1N82TryrYtF5VQL1DJ5h20T43b7ZJd44Q36VhZbkBvTfp3uTkYCe0wHMsp+1kSRhiq69qJKSq7Y9JoJIspPtQIDAQABo4IB5jCCAeIwRwYDVR0RBEAwPoINa2phc2RoZi50cnVzdIILc3NkZnMudHJ1c3SCDXNkZmVkc2YudHJ1c3SCEXd3dy5zZGZlZHNmLnRydXN0MAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgWgMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly9nbS5zeW1jYi5jb20vZ20uY3JsMIGgBgNVHSAEgZgwgZUwgZIGCSsGAQQB8CIBBjCBhDA/BggrBgEFBQcCARYzaHR0cHM6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5L2xlZ2FsMEEGCCsGAQUFBwICMDUMM2h0dHBzOi8vd3d3Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvcmVwb3NpdG9yeS9sZWdhbDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUsWmUYavmywxM51mvWkmLGDPB4UcwVwYIKwYBBQUHAQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8vZ20uc3ltY2QuY29tMCYGCCsGAQUFBzAChhpodHRwOi8vZ20uc3ltY2IuY29tL2dtLmNydDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAZZrAobsW5UGOclcvWS0HmhnZKYz8TbLFIdrndp2+cwfETMmKOxoj8L6p50kMHMImKoS7udomH0TH0VKxuN9AnLYyo0MWehMpqHtICONUoCRXWaSOCp4I3Qz9bLgQZzw4nXrCfTscl/NmYh+U3VLYa5YiqbtzLeoDMg2pXQ3/f8z+9g4sR+6+tjYFSSfhvtZSFIHMJN8vCCHZIftNa2NxnFsQvp8V5GYCnZQOrbeqtoCgiZKeSnvDSEIPB+HQF4tHJTSiOd9I4DChzPn89vakUUWeiL4Z4ywf0ap7+1uFc5AlsUusm/VScsaGgJ9WPJqNtFXnnTkDpreL7gC+KetmJQAK7gAEKjCCBCYwggMOoAMCAQICEAPdhZRnA2PpjT8E8BujKHMwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHzAdBgNVBAsTFkZPUiBURVNUIFBVUlBPU0VTIE9OTFkxNjA0BgNVBAMTLUdlb1RydXN0IFRFU1QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMzExMDEwMDAwMDBaFw0yMzEwMzEyMzU5NTlaMG0xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR8wHQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMSUwIwYDVQQDExxHZW9UcnVzdCBFViBTU0wgVEVTVCBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYDAA9HZOyqHErBSXabwfxlE3wUw13CTI64uJPKqLnxHs8Yeya2fueqzRy7U4vmuauj7ehlI/0hc7hpRPA5iwXzEB3H0H5m5KuYBQdbFGKu/JI09XYhFqhekjCbYkeuUk3AUAg16mIbihGvWXo/W09Rg6LPtrL/efQq4wDAEcY0PM/u5aqGRgT6Dg/SnWKEj9ZDTuRwbP8l0A9Vq3PR6Y1Gq8o+L+sIXA6qbt3wsl2A8Zavgi9pbCuVhJPf69I9FNs7cd20JwH30rou4Y+T4CNk4a5F00+QHDcyMhf92qP4+eCjWEtibyzUzeIt74KCUGWPwMbOtSYw7wBC7p4YqEwIDAQABo4GwMIGtMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSxaZRhq+bLDEznWa9aSYsYM8HhRzAfBgNVHSMEGDAWgBRuAQAD/r3QNjPgu2LhMlA2bZMdMTBHBgNVHSAEQDA+MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJKoZIhvcNAQELBQADggEBAJk4Do4je+zfpGR7sLj/63Dtd5iUeJ1eNbLo3cmZesFXMCUSGhn6gfrKRmafjt6rXzpL/DHO7haLmSXGTAuq2xC1Up8tyUjNzyq25ShZEVJW3+ud7PW9W5I/ABKQaASmaDT/8tgGXkRzmfJd7lnV2y9F9KgcZ6fmOSe+EUwx/By7lVMCLrSAEVwpjQBA9pJjZ4V94ZVySu1AyeXqNkZ66Jcpc95ONlwIxmZn8gQe16tlo0rccxZ0ZjP7G3eK/1yyvFJW9yZRXw533HTfJWuVGzx40/CYNCVf97Oy51GEuULYHfNBtXFqGAiNHW2Rk+TdoUY/D89EgVHYGadbt9qbePoAA+0wggPpMIIDUqADAgECAhAgSXp/mbGJ2RfIkXHHcjw7MA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MR8wHQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMTIwMAYDVQQLEylFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgVEVTVDAeFw0wNjExMjcwMDAwMDBaFw0xODA4MjEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR8wHQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMTYwNAYDVQQDEy1HZW9UcnVzdCBURVNUIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJFX7GBMMTPQ1ZFRc2D7SQebbPRk7+Zp5FcQ4Aj9dqaGtlK3TLwTw/1dcCbMjldXP3idcCvI45gXnAl3k5XqExRiaK0BE5wwvnt1Kv8xutRVTPfVwiyLbTRVRdLlL4DojklDnwXVEQAGx6sniBqJyf36dwb/4EHiNZBFe/kj0Ch4jihT4WeXv6b4e0qyL8dUGMtsEA+M0mU+Nc45aD3zAW2xDa6ZnBYdD//8PcUNNS3xp2ilGgIc03JgZj8mFNBONns8dHs8ZJGY9qxRiN0YwgqRhak185ixqXeJaWv7dKo8jAHmju+zz4nC2qNVTSHUe3HNO/14kAlPf3pzDBNzNtAgMBAAGjge0wgeowHQYDVR0OBBYEFG4BAAP+vdA2M+C7YuEyUDZtkx0xMA8GA1UdEwEB/wQFMAMBAf8wRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL3Rlc3QtY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUiknD95Gj0KAaOuclQ8ExX0lIIGEwDQYJKoZIhvcNAQEFBQADgYEABOnm8HMpzjTS2sqXOv5bifuAzrqbrx0xzLaRi2xOW6hcb3Cx9KofdDyAepYWB0cRaV9eqqe1aHxaoMymH+1agaLufssE/jMLHNA9wAyZAr+DgAeaWzz0MWJJrRBEwhDnd/IMir/G3ydEs5NCLmak69wfXRGngoczPQSuMeN73FcAAs4wggLKMIICM6ADAgECAhAdn+8thA4gjKK8vUnAuDgzMA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MR8wHQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMTIwMAYDVQQLEylFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgVEVTVDAeFw05ODA4MjIwMDAwMDBaFw0xODA4MjIyMzU5NTlaMHQxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MR8wHQYDVQQLExZGT1IgVEVTVCBQVVJQT1NFUyBPTkxZMTIwMAYDVQQLEylFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmEaR7a3GQT5R12jnrMmZuFeWdcB9IYlxOfKQRyeEow/RT6vn5cd4ftBUYWaTsYT7tfalWmh3Q8PEExXpwDU6s4E/LOh7skQZteL5S0gq0a/zZeH2ugWQ3ZzQmyNg9sN7SHv7fdtqm3oCSqi/bsfhq6xVpIQjVYbHBogkHLwxrqUCAwEAAaNdMFswDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFIpJw/eRo9CgGjrnJUPBMV9JSCBhMB8GA1UdIwQYMBaAFIpJw/eRo9CgGjrnJUPBMV9JSCBhMA0GCSqGSIb3DQEBBQUAA4GBAHpWthUM2qNJ06MCtfpP1fkXw2IG0T6g69XOLcsPYaL51GmC3x84OpEGhLUycPtKDji3qSzu+Z5L+qkRjs5Sk6rIRIFex35oMn9EemprrkMUWIcOUnWSU25+XXr4Kmq4ItPS8FuUN5XT+coYqJ2UAW7QGaR11Gu7zf+6MrGgdJCk" CertEntryB64 = "AAAAAAFJpuA6vgAAAAZRMIIGTTCCBTWgAwIBAgIMal1BYfXJtoBDJwsMMA0GCSqGSIb3DQEBBQUAMF4xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTQwMgYDVQQDEytHbG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBHMiBURVNUMB4XDTE0MTExMzAxNTgwMVoXDTE2MTExMzAxNTgwMVowggETMRgwFgYDVQQPDA9CdXNpbmVzcyBFbnRpdHkxEjAQBgNVBAUTCTY2NjY2NjY2NjETMBEGCysGAQQBgjc8AgEDEwJERTEpMCcGCysGAQQBgjc8AgEBExhldiBqdXJpc2RpY3Rpb24gbG9jYWxpdHkxJjAkBgsrBgEEAYI3PAIBAhMVZXYganVyaXNkaWN0aW9uIHN0YXRlMQswCQYDVQQGEwJKUDEKMAgGA1UECAwBUzEKMAgGA1UEBwwBTDEVMBMGA1UECRMMZXYgYWRkcmVzcyAzMQwwCgYDVQQLDANPVTExDDAKBgNVBAsMA09VMjEKMAgGA1UECgwBTzEXMBUGA1UEAwwOY3NyY24uc3NsMjQuanAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCNufDWs1lGbf/pW6Q9waVoDu3I88q7xXOiNqEJv25Y34Fse7gVYUerUm7Or/0FdubhwJ6jNDPhFNflA4xpcpjHlX8Bp+EUIyCEfPI0mVu+QnmDQMuZ5qfiz6lQJ3rvbgL02W3c6wr5VBFxsPjxqk8NAkU+bmVLJaE/Kv9DV8roF3070hhVaGWRojCdn/XerYJAME4i6vzFUIWH5ratHQC1PCjluTYmmvvyFLc+29yKSKhsHCPz3OVfzOYFAsCQi8qb2yLBbAs00RtP0n6de8tWxewPxNUlAPsGsK9cQRLkIQIreLMQMMtz6f2S/8ZZGf2PNeYE/K8CW5x34+Xf90mnAgMBAAGjggJSMIICTjAOBgNVHQ8BAf8EBAMCBaAwTAYDVR0gBEUwQzBBBgkrBgEEAaAyAQEwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wSAYDVR0fBEEwPzA9oDugOYY3aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9nc29yZ2FuaXphdGlvbnZhbGNhdGcyLmNybDCBnAYIKwYBBQUHAQEEgY8wgYwwSgYIKwYBBQUHMAKGPmh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzb3JnYW5pemF0aW9udmFsY2F0ZzIuY3J0MD4GCCsGAQUFBzABhjJodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nvcmdhbml6YXRpb252YWxjYXRnMjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGQYDVR0RBBIwEIIOY3NyY24uc3NsMjQuanAwHQYDVR0OBBYEFH+DSykD417/9lFhkIOi79aabXD0MB8GA1UdIwQYMBaAFKswpAbZctACmrLH0/QkG+L8pTICMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsTLGNQt8b9JuFsbHcAAAFJptw0awAABAMARzBFAiBGn03AVTt4Mr1WYzw7nVP6rshN9BS3oFqxstVE0UasPgIhAO6JlBn9T5VUR5j3iD/gk2kv60yQ6E1lFgD3AZFmpDcBMA0GCSqGSIb3DQEBBQUAA4IBAQB9zT4ijWjNwHNMdin9fUDNdC0O0dDZ9JpkOvEtzbxhOUY4t8UZu3yuUwzNw6UDfVzdik0sAavcg02vGZP3oi7iwiM3epTaTmisaaC1DS1HPsd2UeABxfcaI8wt7+dhb9bGSRqn+aK7Frkwzj+Mw3z2pHv7BP1O/324QzzG/bBRRqSjH+ZSEYdfLFESm/BynOLcfOGlr8bqoes6NilsueCRN17fxAjHJ/bVS7pAjaYLRsSWo2TFBK30fuBJapJg/iI8iyPBSDJjXD3/DbqKDIzdlXp38YRDt3gqm2x2NrfWbfQmNQuVlTfpEYiORbLAshjlDQP9z6f3WOjmDdGhmWvAAAA=" CertEntryExtraDataB64 = "AAf9AARpMIIEZTCCA02gAwIBAgILZGRf9tONi09hqe4wDQYJKoZIhvcNAQEFBQAwUTEgMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xGDAWBgNVBAMTD0dsb2JhbFNpZ24gVEVTVDAeFw0xNDEwMjkxMzE2NTJaFw0yMTEyMTUxMDMzMzhaMF4xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTQwMgYDVQQDEytHbG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBHMiBURVNUMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmg5vLsmiO6QfUvg0BBzJ/TZh45pOpuObg0xmnJRdGJhLjkGeB/da2X1+iSq73hRTZnAKeDaOdivdTwHvgjI1Wj6BVIXlUbsmnaA0YNs400tFtQIQDHSr+5a6CWaIXKyIslogUbl17O2mmjLyLyuDFF4kS17CTHMUnSUZyM/W7HMAozdB3m4MO1zLXMAMXne8q1FDzF1eKp7JAmmCZgszAYDQBzzhm8UXFvAkkMIq67DAUYUVt4WPNLA8HdX3K9g5ZPnNOjOkHlJ2dvqqg3x6M8dbqpGI6V8iYYpxY2XvFaSOEQ25CC9huMuVL3i/x5nBIggib/yWeMz/kyrZyMIMxwIDAQABo4IBLzCCASswRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMB0GA1UdDgQWBBSrMKQG2XLQApqyx9P0JBvi/KUyAjASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFGmJRnRiL8rmiLXgBu9l6WJQBY8VMEcGA1UdIARAMD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vY3JsLmdsb2JhbHNpZ24ubmV0L3Jvb3QtcjIuY3JsMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAjuSlZRGuCJKS73kO60LBVM4EzY/SUuIHLn44s5ELOHaOHn8t5Zdw0t2/2nA6SzEgPKfgbqL8VazMID9CdUSCtOXd13jsYMsQdGcKCDTQaIMFzjo9SIEFpkD2ie21eyanobeqC3fmYZVrHbMTLDjqjTPnV8OvBIOiPvTC6VEac2HwHOgCye3BW1m/CoR2wtJBqeXoKgyEdsDk/VF9EiN6/gSmH8dDC1el7PtBgheHSciJ7iUWXUU8+rNm74ibTKeIZPQscYxVXu9Msz/5NcQzuyRhblfIC3E0dRb4j+F/XpFdI2GdlAMrCTsISRjeuuFKkZyKwDgstDIOEm2Ub+fhFwADjjCCA4owggJyoAMCAQICCwQAAAAAAQ+GJuYNMA0GCSqGSIb3DQEBBQUAMFExIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRgwFgYDVQQDEw9HbG9iYWxTaWduIFRFU1QwHhcNMTQxMDI2MTAzMzM4WhcNMjExMjE1MTAzMzM4WjBRMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjEYMBYGA1UEAxMPR2xvYmFsU2lnbiBURVNUMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr05U6MH7Bfyfd8d6uJLkuDdYSkKCmwd0DUTHH9yrrhe7W9msaFxHDXBL3mK7upgRL2KyMZ2VPsk+WBpW/VMFGZpQU36cjXQCxCs31dpfWNVjO7BsfRxpqaPyBNacH8tPIDzdzhmIB8Wka2aTeIRSB8asmvQkgr86H68oDwDleCE7+El1bULkpzEmGhqVoHaS6i+AxljmrxymGN9B2hB2j/v7kz7nTy+Lexg+ujwV7iGq7ydMWtMrQeUXcZjdgboF72U/CT3vIGMOWfHgEob0h71Ka856BFApYZC0LVFD/dSGM7Ss5MlhLARV4LVBqsPxTmG9SeYBA8fLHpAh/eIruwIDAQABo2MwYTAdBgNVHQ4EFgQUaYlGdGIvyuaIteAG72XpYlAFjxUwHwYDVR0jBBgwFoAUaYlGdGIvyuaIteAG72XpYlAFjxUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADoeFcm+Gat4i9MOCAIHQQuWQmfJ2Vfq0vN//OQVHtIYCCo67yb8grNa+/NS/qi5/asxyZfudG3vn5vx4iT107etvKpHBHl3IT4GXhKFEMiCbOd5zfuQ0pWnb0BcqiTFo5SJeVUiTxCt6plshreA3YIOw4A4dJwD8NfWJ+/L/3E4cE+pAVhcxqMf+ucEsAr0YMoSRF8UJc6n2IwgwBD7fxwYxYdS4tCqkHLSsYPEeQYb3mSdIzYAhQwE+u1zT+o+Ff0YRImKemUvEQT9oGDR2iIiM61sDI5Te1x5/MAwBK8YqCcRBBM48d+Oo1rGGI2weLgGXkS61gzSWhQQZ8jV3Y0=" SubmissionCertB64 = "MIIEijCCA3KgAwIBAgICEk0wDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fja2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTUxMDIxMjAxMTUyWhcNMjAxMDE5MjAxMTUyWjAfMR0wGwYDVQQDExRoYXBweSBoYWNrZXIgZmFrZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxUzpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14UjoaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctKFUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsCAwEAAaOCAcIwggG+MBIGA1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0eBDwwOqE4MAaCBC5taWwwCocIAAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFOmkP+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNVHQ4EFgQU+3hPEvlgFYMsnxd/NBmzLjbqQYkwDQYJKoZIhvcNAQELBQADggEBAA0YAeLXOklx4hhCikUUl+BdnFfn1g0W5AiQLVNIOL6PnqXu0wjnhNyhqdwnfhYMnoy4idRh4lB6pz8Gf9pnlLd/DnWSV3gS+/I/mAl1dCkKby6H2V790e6IHmIK2KYm3jm+U++FIdGpBdsQTSdmiX/rAyuxMDM0adMkNBwTfQmZQCz6nGHw1QcSPZMvZpsC8SkvekzxsjF1otOrMUPNPQvtTWrVx8GlR2qfx/4xbQa1v2frNvFBCmO59goz+jnWvfTtj2NjwDZ7vlMBsPm16dbKYC840uvRoZjxqsdc3ChCZjqimFqlNG/xoPA8+dTicZzCXE9ijPIcvW6y1aa3bGw=" AddJSONResp = `{ "sct_version":0, "id":"KHYaGJAn++880NYaAY12sFBXKcenQRvMvfYE9F1CYVM=", "timestamp":1337, "extensions":"", "signature":"BAMARjBEAiAIc21J5ZbdKZHw5wLxCP+MhBEsV5+nfvGyakOIv6FOvAIgWYMZb6Pw///uiNM7QTg2Of1OqmK1GbeGuEl9VJN8v8c=" }` ProofByHashResp = ` { "leaf_index": 3, "audit_path": [ "pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=", "5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=", "7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg=" ] }` GetRootsResp = ` { "certificates":[ "MIIFLjCCAxagAwIBAgIQNgEiBHAkH6lLUWKp42Ob1DANBgkqhkiG9w0BAQ0FADAWMRQwEgYDVQQDEwtlc2lnbml0Lm9yZzAeFw0xNDA2MjAxODM3NTRaFw0zMDA2MjAxODQ3NDZaMBYxFDASBgNVBAMTC2VzaWduaXQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtylZx/zTLxRDsok14XO0Z3PvWMIY4HWro0YLgCF8dYv3tUaNkmN3ghlQvY8UcByH2LMOBGiQAcMHxgEJ53cnWRyc2DjoGhkDkiPdS2JttNEB0B/XTaGvaHwJh2CSgIBbpZpWTaqGywbe7AgJQ81L8h7tZ4E6W8ZM0vt4mnzqkPBT+BmyjTXG/McGhYTQAsmdsYZDBAdB2Y4X1/RAyL0e9MHdSboRofhg+8d5MeC0VEIgHXU/R4f4wz/pSw0FI9xxWJR3UUK/qOWqNsVYZfmCu6+ksDQtezxSTAuymoL094Dwn+hnXb8RS6dEbIQ+b0bIHxxpypcxH7rBMIpQcbZ8JSqNVDZPI9QahKNPQMQiuBE66KlqbnLOj7lGBxsbpU2Dx8QL8W96op6dTGtniFyXqhuYN2UxDMNI+fb1j9G7ENpoqvTVfjxa4RUU6uZ9ZygOiiOZD4P54vEQFteiu4OM+mWOm5Vll9yPXqHPc5oiCfyvCNVzfapqPoGbaCM6oQtcHdAca9VpE2eDTo36zfdFo31YYBOEjWNsfXwp8frNduS/L6gmWYrd91HeEoOVX2ZQKqBLp5ydW72xDSeCIr5kugqdY6whW80ugjLlc9mDd8/LEGQQKnrxzeeWdjiQG/WwcOse9GRktOzH2gvmkJ+vY82z1jhrZP4REoA6T+aYGR8CAwEAAaN4MHYwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPOGsFKraD+/FoPAUXSf77qYfZHRMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFEq/BT//OC3eNeJ4wEfNqJXdZRNpMA0GCSqGSIb3DQEBDQUAA4ICAQBEvh2kzI+1uoUx/emM654QvpM6WtgQSJMubKwKeBY5UNgwwNpwmtswiEKzdZwBiGb1xEehPrAKz0d7aiIIEOonYEohIV6szl0+F56nN16813n1lPsCjdLSA8fjgf28jvlTKcrLRqeyCn4APadh6g7/FRiGcmIxEFPf/VNTUBZ7l4e2zzb06PxCq8oDaOsbAVYXQz8A0KX50KURZrdC2knUg1HX0J/orVpdaQ9UZYVNp2WAbe9vYTCCF5FdtzNU+nJDojpDxF5guMe9bifL3YTvd87YQwsH7+o+UbtHX4lG8VsSfmvvJulNBY6RtzZEpZvyRWIvQahM9qTrzFpsxl4wyPSBDPLDZ6YvVWsXvU4PqLOWTbPdq4BB24P9kFxeYjEe/rDQ8bd1/V/OFZTEM0rxdZDDN9vWnybzl8xL5VmNLDGl1u6JrOVvCzVAWP++L9l5UTusQI/BPSMebz6msd8vhTluD4jQIba1/6zOwfBraFgCIktCT3GEIiyt59x3rdSirLyjzmeQA9NkwoG/GqlFlSdWmQCK/sCL+z050rqjL0kEwIl/D6ncCXfBvhCpCmcrIlZFruyeOlsISZ410T1w/pLK8OXhbCr13Gb7A5jhv1nn811cQaR7XUXhcn6Wq/VV/oQZLunBYvoYOs3dc8wpBabPrrRhkdNmN6Rib6TvMg==" ] }` GetSTHConsistencyResp = `{ "consistency": [ "IqlrapPQKtmCY1jCr8+lpCtscRyjjZAA7nyadtFPRFQ=", "ytf6K2GnSRZ3Au+YkivCb7N1DygfKyZmE4aEs9OXl\/8=" ] }` GetEntryAndProofResp = `{ "leaf_input": "AAAAAAFhw8UTtQAAAAJ1MIICcTCCAhegAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTAgFw0xNjEyMDcxNTEzMzZaGA8wMDAxMDEwMTAwMDAwMFowVjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxFzAVBgNVBAMMDmxlYWYwMS5jc3IucGVtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6zdOUkWcRtWouMXtWLkwKaZwimmgJlyeL264ayNshOFGOpg2gkSliheLQYIy9C3gCFt+BzhS/EdWKCeb7WCLrKOBszCBsDAPBgNVHQ8BAf8EBQMDB/mAMIGLBgNVHQ4EgYMEgYBPRBC+90lR8pRLbTi3ID4j0WRzjoJOT3MGkKko87o8z6gEifk9zCwOiHeIgclTA0ZUTxXMRI5r+nUY0frjRCWZu4uthPlE90iJM+RyjcNTwDJGu2StvLnJ8y4t5fdnwdGssncXiBQMuM7/1eMEwAOfHgTFzJ0UBC2Umztl0hul3zAPBgNVHSMECDAGgAQBAgMEMAoGCCqGSM49BAMCA0gAMEUCIQCrwywGKvyt/BwR+e7yDs78qt4sSEVJltv7Y0W6gOI5awIgQ+IAjejYivLEfqNufFRezCBWHWhbq/HHGdNQtv6EArkAAA==", "extra_data": "RXh0cmEK", "audit_path": [ "pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=", "5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=", "7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg=" ] }` ) func b64(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { panic(err) } return b } // serveHandlerAt returns a test HTTP server that only expects requests at the given path, and invokes // the provided handler for that path. func serveHandlerAt(t *testing.T, path string, handler func(http.ResponseWriter, *http.Request)) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == path { handler(w, r) } else { t.Fatalf("Incorrect URL path: %s", r.URL.Path) } })) } // serveRspAt returns a test HTTP server that returns a canned response body rsp for a given path. func serveRspAt(t *testing.T, path, rsp string) *httptest.Server { t.Helper() return serveHandlerAt(t, path, func(w http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(w, rsp); err != nil { t.Fatal(err) } }) } func sctToJSON(rawSCT []byte) ([]byte, error) { var sct ct.SignedCertificateTimestamp _, err := tls.Unmarshal(rawSCT, &sct) if err != nil { return nil, fmt.Errorf("failed to tls-unmarshal test certificate proof: %v", err) } data, err := json.Marshal(sct) if err != nil { return nil, fmt.Errorf("failed to json-marshal test certificate proof: %v", err) } return data, nil } // serveSCTAt returns a test HTTP server that returns the given SCT as a canned response for // a given path. func serveSCTAt(t *testing.T, path string, rawSCT []byte) *httptest.Server { t.Helper() return serveHandlerAt(t, path, func(w http.ResponseWriter, r *http.Request) { data, err := sctToJSON(rawSCT) if err != nil { t.Fatal(err) } if _, err := w.Write(data); err != nil { t.Fatal(err) } }) } func TestGetEntries(t *testing.T) { ts := serveHandlerAt(t, "/ct/v1/get-entries", func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() numRE := regexp.MustCompile("[0-9]+") if !numRE.MatchString(q["start"][0]) || !numRE.MatchString(q["end"][0]) { t.Fatalf("Invalid parameter: start=%q, end=%q", q["start"][0], q["end"][0]) } _, err := fmt.Fprintf(w, `{"entries":[{"leaf_input": "%s","extra_data": "%s"},{"leaf_input": "%s","extra_data": "%s"}]}`, PrecertEntryB64, PrecertEntryExtraDataB64, CertEntryB64, CertEntryExtraDataB64) if err != nil { t.Fatal(err) } }) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } leaves, err := lc.GetEntries(context.Background(), 0, 1) if err != nil { t.Errorf("GetEntries(0,1)=nil,%v; want 2 leaves,nil", err) } else if len(leaves) != 2 { t.Errorf("GetEntries(0,1)=%d leaves,nil; want 2 leaves,nil", len(leaves)) } } func TestGetEntriesErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string start, end int64 rsp, want string }{ {desc: "eof", start: 1, end: 2, rsp: "", want: "EOF"}, {desc: "negative end", start: 0, end: -1, want: "end should be >= 0"}, {desc: "invalid range", start: 3, end: 2, want: "start should be <= end"}, {desc: "non json input", start: 4, end: 5, rsp: "not-json", want: "invalid"}, {desc: "leaf_input not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"Z29vZA=="}]}`, want: "illegal base64"}, {desc: "extra_data not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"Z29vZA","extra_data":"bogus"}]}`, want: "illegal base64"}, {desc: "both not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"bogus"}]}`, want: "illegal base64"}, {desc: "bad json", start: 6, end: 7, rsp: `{"entries":[{"leaf_input":"bbbb","extra_data":"bbbb"}]}`, want: "failed to unmarshal"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetEntries(ctx, test.start, test.end) if err == nil { t.Errorf("GetEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetEntries(%d, %d)=nil, %q; want nil, %q", test.start, test.end, err, test.want) } if got != nil { t.Errorf("GetEntries(%d, %d)=%+v, _; want nil, _", test.start, test.end, got) } }) } } func TestGetRawEntriesErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string start, end int64 rsp, want string }{ {desc: "empty", start: 1, end: 2, rsp: "", want: "EOF"}, {desc: "negative end", start: 0, end: -1, want: "end should be >= 0"}, {desc: "bad range", start: 3, end: 2, want: "start should be <= end"}, {desc: "invalid json", start: 4, end: 5, rsp: "not-json", want: "invalid"}, {desc: "leaf_input not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"Z29vZA=="}]}`, want: "illegal base64"}, {desc: "extra_data not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"Z29vZA==","extra_data":"bogus"}]}`, want: "illegal base64"}, {desc: "both not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"bogus"}]}`, want: "illegal base64"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetRawEntries(ctx, test.start, test.end) if err == nil { t.Errorf("GetRawEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetRawEntries(%d, %d)=nil, %q; want nil, %q", test.start, test.end, err, test.want) } if got != nil { t.Errorf("GetRawEntries(%d, %d)=%+v, _; want nil, _", test.start, test.end, got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetRawEntries(%d, %d)=nil, .(%T); want nil, .(RspError)", test.start, test.end, err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetRawEntries(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.start, test.end, rspErr.Body, test.rsp) } } }) } } func TestGetSTH(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-sth", fmt.Sprintf(`{"tree_size": %d, "timestamp": %d, "sha256_root_hash": "%s", "tree_head_signature": "%s"}`, ValidSTHResponseTreeSize, int64(ValidSTHResponseTimestamp), ValidSTHResponseSHA256RootHash, ValidSTHResponseTreeHeadSignature)) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } sth, err := lc.GetSTH(context.Background()) if err != nil { t.Fatal(err) } if sth.TreeSize != ValidSTHResponseTreeSize { t.Errorf("GetSTH().TreeSize=%d; want %d", sth.TreeSize, ValidSTHResponseTreeSize) } if sth.Timestamp != ValidSTHResponseTimestamp { t.Errorf("GetSTH().Timestamp=%v; want %v", sth.Timestamp, ValidSTHResponseTimestamp) } if sth.SHA256RootHash.Base64String() != ValidSTHResponseSHA256RootHash { t.Errorf("GetSTH().SHA256RootHash=%v; want %v", sth.SHA256RootHash.Base64String(), ValidSTHResponseSHA256RootHash) } wantRawSignature, err := base64.StdEncoding.DecodeString(ValidSTHResponseTreeHeadSignature) if err != nil { t.Fatalf("Couldn't b64 decode 'correct' STH signature: %v", err) } var wantDS ct.DigitallySigned if _, err := tls.Unmarshal(wantRawSignature, &wantDS); err != nil { t.Fatalf("Couldn't unmarshal DigitallySigned: %v", err) } if sth.TreeHeadSignature.Algorithm.Hash != wantDS.Algorithm.Hash { t.Errorf("GetSTH().TreeHeadSignature.Algorithm.Hash=%v; %v", wantDS.Algorithm.Hash, sth.TreeHeadSignature.Algorithm.Hash) } if sth.TreeHeadSignature.Algorithm.Signature != wantDS.Algorithm.Signature { t.Errorf("GetSTH().TreeHeadSignature.Algorithm.Signature=%v; want %v", wantDS.Algorithm.Signature, sth.TreeHeadSignature.Algorithm.Signature) } if !bytes.Equal(sth.TreeHeadSignature.Signature, wantDS.Signature) { t.Errorf("GetSTH().TreeHeadSignature.Signature=%v; want %v", wantDS.Signature, sth.TreeHeadSignature.Signature) } } func TestGetSTHErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string rsp, want string }{ {desc: "empty", rsp: "", want: "EOF"}, {desc: "bad json", rsp: "not-json", want: "invalid"}, {desc: "root not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bogus","tree_head_signature":"Z29vZA=="}`, want: "illegal base64"}, {desc: "sig not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"Z29vZA==","tree_head_signature":"bogus"}`, want: "illegal base64"}, {desc: "both not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bogus","tree_head_signature":"bogus"}`, want: "illegal base64"}, {desc: "invalid hash len", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bbbb","tree_head_signature":"bbbb"}`, want: "hash is invalid length"}, {desc: "bad tls data", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"tncuLXiPAo711IOxjaYTwLmwbSyyE8hEcRhaOXvFb3g=","tree_head_signature":"Z29vZA=="}`, want: "tls: syntax error"}, {desc: "trailing junk", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"tncuLXiPAo711IOxjaYTwLmwbSyyE8hEcRhaOXvFb3g=","tree_head_signature":"BAMARjBEAiAi5045/h8Yvs1mNlsYskWvuFbu2A6hO2J45KDFfOR1OwIgZ2jq8iFCwKuTbcIgsBB1ibHEupv97CeAQynK0Dw2PT8bbbb="}`, want: "trailing data"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-sth", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetSTH(ctx) if err == nil { t.Errorf("GetSTH()=%+v, nil; want nil, %q", got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetSTH()=nil, %q; want nil, %q", err, test.want) } if got != nil { t.Errorf("GetSTH()=%+v, _; want nil, _", got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetSTH()=nil, .(%T); want nil, .(RspError)", err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetSTH()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp) } } }) } } func TestAddChainRetries(t *testing.T) { if testing.Short() { t.Skip("skipping retry test in short mode") } retryAfter := 0 * time.Second currentFailures := 0 failuresBeforeSuccess := 0 hs := serveHandlerAt(t, "/ct/v1/add-chain", func(w http.ResponseWriter, r *http.Request) { if failuresBeforeSuccess > 0 && currentFailures < failuresBeforeSuccess { currentFailures++ if retryAfter != 0 { if retryAfter > 0 { w.Header().Add("Retry-After", strconv.Itoa(int(retryAfter.Seconds()))) } w.WriteHeader(503) return } w.WriteHeader(408) return } _, err := w.Write([]byte(AddJSONResp)) if err != nil { return } }) defer hs.Close() certBytes, err := base64.StdEncoding.DecodeString(SubmissionCertB64) if err != nil { t.Fatalf("Failed to decode chain array B64: %s", err) } chain := []ct.ASN1Cert{{Data: certBytes}} const leeway = time.Millisecond * 100 const leewayRatio = 0.2 // 20% tests := []struct { desc string deadlineLength time.Duration // -1 indicates no deadline expected time.Duration retryAfter time.Duration // -1 indicates: generate 503 with no Retry-After failuresBeforeSuccess int success bool }{ { desc: "success first time", deadlineLength: -1, expected: 1 * time.Millisecond, retryAfter: 0, failuresBeforeSuccess: 0, success: true, }, { desc: "multiple attempts 503", deadlineLength: -1, expected: 7 * time.Second, // 1 + 2 + 4 retryAfter: -1, failuresBeforeSuccess: 3, success: true, }, { desc: "one retry", deadlineLength: 6 * time.Second, expected: 5 * time.Second, retryAfter: 5 * time.Second, failuresBeforeSuccess: 1, success: true, }, { desc: "fail after one retry", deadlineLength: 5 * time.Second, expected: 5 * time.Second, retryAfter: 10 * time.Second, failuresBeforeSuccess: 1, success: false, }, { desc: "multiple retries", deadlineLength: 10 * time.Second, expected: 5 * time.Second, retryAfter: 1 * time.Second, failuresBeforeSuccess: 5, success: true, }, { desc: "immediate 10 retries", deadlineLength: 1 * time.Second, expected: 10 * time.Millisecond, retryAfter: 0, failuresBeforeSuccess: 10, success: true, }, } for i, test := range tests { t.Run(test.desc, func(t *testing.T) { deadline := context.Background() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } if test.deadlineLength >= 0 { var cancel context.CancelFunc deadline, cancel = context.WithDeadline(context.Background(), time.Now().Add(test.deadlineLength)) defer cancel() } retryAfter = test.retryAfter failuresBeforeSuccess = test.failuresBeforeSuccess currentFailures = 0 started := time.Now() sct, err := lc.AddChain(deadline, chain) took := time.Since(started) delta := math.Abs(float64(took - test.expected)) ratio := delta / float64(test.expected) if delta > float64(leeway) && ratio > leewayRatio { t.Errorf("#%d Submission took an unexpected length of time: %s, expected ~%s", i, took, test.expected) } if test.success && err != nil { t.Errorf("#%d Failed to submit chain: %s", i, err) } else if !test.success && err == nil { t.Errorf("#%d Expected AddChain to fail", i) } if test.success && sct == nil { t.Errorf("#%d Nil SCT returned", i) } }) } } func TestAddChain(t *testing.T) { hs := serveSCTAt(t, "/ct/v1/add-chain", testdata.TestCertProof) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM}) if err != nil { t.Fatalf("Failed to create client: %v", err) } cert, err := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) if x509.IsFatal(err) { t.Fatalf("Failed to parse certificate from PEM: %v", err) } // AddChain will verify the signature because the client has a public key. chain := []ct.ASN1Cert{{Data: cert.Raw}} _, err = lc.AddChain(context.Background(), chain) if err != nil { t.Errorf("AddChain()=nil,%v; want sct,nil", err) } } func TestAddPreChain(t *testing.T) { hs := serveSCTAt(t, "/ct/v1/add-pre-chain", testdata.TestPreCertProof) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM}) if err != nil { t.Fatalf("Failed to create client: %v", err) } cert, err := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM)) if x509.IsFatal(err) { t.Fatalf("Failed to parse pre-certificate from PEM: %v", err) } issuer, err := x509util.CertificateFromPEM([]byte(testdata.CACertPEM)) if x509.IsFatal(err) { t.Fatalf("Failed to parse issuer certificate from PEM: %v", err) } // AddPreChain will verify the signature because the client has a public key. chain := []ct.ASN1Cert{{Data: cert.Raw}, {Data: issuer.Raw}} _, err = lc.AddPreChain(context.Background(), chain) if err != nil { t.Errorf("AddPreChain()=nil,%v; want sct,nil", err) } } func TestGetSTHConsistency(t *testing.T) { hs := serveRspAt(t, "/ct/v1/get-sth-consistency", GetSTHConsistencyResp) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } tests := []struct { first uint64 second uint64 proof [][]byte }{ {1, 3, [][]byte{ b64("IqlrapPQKtmCY1jCr8+lpCtscRyjjZAA7nyadtFPRFQ="), b64("ytf6K2GnSRZ3Au+YkivCb7N1DygfKyZmE4aEs9OXl/8="), }}, } for _, test := range tests { proof, err := lc.GetSTHConsistency(context.Background(), test.first, test.second) if err != nil { t.Errorf("GetSTHConsistency(%d, %d)=nil,%v; want proof,nil", test.first, test.second, err) } else if !reflect.DeepEqual(proof, test.proof) { t.Errorf("GetSTHConsistency(%d, %d)=%v,nil; want %v,nil", test.first, test.second, proof, test.proof) } } } func TestGetSTHConsistencyErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string first, second uint64 rsp, want string }{ {desc: "empty", first: 1, second: 2, rsp: "", want: "EOF"}, {desc: "invalid json", first: 1, second: 2, rsp: "not-json", want: "invalid"}, {desc: "not base64", first: 1, second: 2, rsp: `{"consistency":["bogus"]}`, want: "illegal base64"}, {desc: "bad proof", first: 1, second: 2, rsp: `{"consistency":["2SyPbmCNzn9l7dhWVz1uz6nW7DB7p0EkSsfH9M+qU5E=",]}`, want: "invalid"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-sth-consistency", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetSTHConsistency(ctx, test.first, test.second) if err == nil { t.Errorf("GetSTHConsistency(%d, %d)=%+v, nil; want nil, %q", test.first, test.second, got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetSTHConsistency(%d, %d)=nil, %q; want nil, %q", test.first, test.second, err, test.want) } if got != nil { t.Errorf("GetSTHConsistency(%d, %d)=%+v, _; want nil, _", test.first, test.second, got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetSTHConsistency(%d, %d)=nil, .(%T); want nil, .(RspError)", test.first, test.second, err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetSTHConsistency(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.first, test.second, rspErr.Body, test.rsp) } } }) } } func TestGetProofByHash(t *testing.T) { hs := serveRspAt(t, "/ct/v1/get-proof-by-hash", ProofByHashResp) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } tests := []struct { desc string hash []byte treeSize uint64 }{ {desc: "ok", hash: dh("4a9e8edbe5ce2d2da69d483edb45186675d4be37b649d40923b156a7d1277463"), treeSize: 5}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { resp, err := lc.GetProofByHash(context.Background(), test.hash, test.treeSize) if err != nil { t.Errorf("GetProofByHash(%v, %v)=nil,%v; want proof,nil", test.hash, test.treeSize, err) } else if got := len(resp.AuditPath); got < 1 { t.Errorf("len(GetProofByHash(%v, %v)): %v; want > 1", test.hash, test.treeSize, got) } }) } } func TestGetProofByHashErrors(t *testing.T) { ctx := context.Background() aHash := dh("4a9e8edbe5ce2d2da69d483edb45186675d4be37b649d40923b156a7d1277463") var tests = []struct { desc string rsp, want string }{ {desc: "empty", rsp: "", want: "EOF"}, {desc: "bad json", rsp: "not-json", want: "invalid"}, {desc: "path not base64", rsp: `{"leaf_index": 17, "audit_path":["bogus"]}`, want: "illegal base64"}, {desc: "malformed json", rsp: `{"leaf_index": 17, "audit_path":["Z29vZA==",]}`, want: "invalid"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-proof-by-hash", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetProofByHash(ctx, aHash, 100) if err == nil { t.Errorf("GetProofByHash()=%+v, nil; want nil, %q", got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetProofByHash()=nil, %q; want nil, %q", err, test.want) } if got != nil { t.Errorf("GetProofByHash()=%+v, _; want nil, _", got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetProofByHash()=nil, .(%T); want nil, .(RspError)", err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetProofByHash()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp) } } }) } } func TestGetAcceptedRoots(t *testing.T) { hs := serveRspAt(t, "/ct/v1/get-roots", GetRootsResp) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } certs, err := lc.GetAcceptedRoots(context.Background()) if err != nil { t.Errorf("GetAcceptedRoots()=nil,%q; want roots,nil", err.Error()) } else if len(certs) < 1 { t.Errorf("len(GetAcceptedRoots())=0; want > 1") } } func TestGetAcceptedRootsErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string rsp, want string }{ {desc: "empty", rsp: "", want: "EOF"}, {desc: "not json", rsp: "not-json", want: "invalid"}, {desc: "not base64", rsp: `{"certificates":["bogus"]}`, want: "illegal base64"}, {desc: "malformed json", rsp: `{"certificates":["bbbb",]}`, want: "invalid"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-roots", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetAcceptedRoots(ctx) if err == nil { t.Errorf("GetAcceptedRoots()=%+v, nil; want nil, %q", got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetAcceptedRoots()=nil, %q; want nil, %q", err, test.want) } if got != nil { t.Errorf("GetAcceptedRoots()=%+v, _; want nil, _", got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetAcceptedRoots()=nil, .(%T); want nil, .(RspError)", err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetAcceptedRoots()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp) } } }) } } func TestGetEntryAndProof(t *testing.T) { hs := serveRspAt(t, "/ct/v1/get-entry-and-proof", GetEntryAndProofResp) defer hs.Close() lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } tests := []struct { index uint64 treeSize uint64 }{ {1000, 2000}, } for _, test := range tests { resp, err := lc.GetEntryAndProof(context.Background(), test.index, test.treeSize) if err != nil { t.Errorf("GetEntryAndProof(%v, %v)=nil,%v; want proof,nil", test.index, test.treeSize, err) } else if got := len(resp.AuditPath); got < 1 { t.Errorf("len(GetEntryAndProof(%v, %v)): %v; want > 1", test.index, test.treeSize, got) } } } func TestGetEntryAndProofErrors(t *testing.T) { ctx := context.Background() var tests = []struct { desc string rsp, want string }{ {desc: "empty", rsp: "", want: "EOF"}, {desc: "bad json", rsp: "not-json", want: "invalid"}, {desc: "leaf_input not base64", rsp: `{"leaf_input": "bogus", "extra_data": "Z29vZAo=", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"}, {desc: "extra_data not base64", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "bogus", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"}, {desc: "audit_path no base64", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bogus"]}`, want: "illegal base64"}, {desc: "malformed json", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bbbb",]}`, want: "invalid"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ts := serveRspAt(t, "/ct/v1/get-entry-and-proof", test.rsp) defer ts.Close() lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatalf("Failed to create client: %v", err) } got, err := lc.GetEntryAndProof(ctx, 99, 100) if err == nil { t.Errorf("GetEntryAndProof()=%+v, nil; want nil, %q", got, test.want) } else if !strings.Contains(err.Error(), test.want) { t.Errorf("GetEntryAndProof()=nil, %q; want nil, %q", err, test.want) } if got != nil { t.Errorf("GetEntryAndProof()=%+v, _; want nil, _", got) } if len(test.rsp) > 0 { // Expect the error to include the HTTP response if rspErr, ok := err.(client.RspError); !ok { t.Errorf("GetEntryAndProof()=nil, .(%T); want nil, .(RspError)", err) } else if string(rspErr.Body) != test.rsp { t.Errorf("GetEntryAndProof()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp) } } }) } } google-certificate-transparency-go-2308f62/client/multilog.go000066400000000000000000000165661462611535200243200ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "context" "crypto/sha256" "errors" "fmt" "net/http" "os" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client/configpb" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/x509" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) type interval struct { lower *time.Time // nil => no lower bound upper *time.Time // nil => no upper bound } // TemporalLogConfigFromFile creates a TemporalLogConfig object from the given // filename, which should contain text-protobuf encoded configuration data. func TemporalLogConfigFromFile(filename string) (*configpb.TemporalLogConfig, error) { if len(filename) == 0 { return nil, errors.New("log config filename empty") } cfgBytes, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read log config: %v", err) } var cfg configpb.TemporalLogConfig if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil { if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil { return nil, fmt.Errorf("failed to parse TemporalLogConfig from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr) } } if len(cfg.Shard) == 0 { return nil, errors.New("empty log config found") } return &cfg, nil } // AddLogClient is an interface that allows adding certificates and pre-certificates to a log. // Both LogClient and TemporalLogClient implement this interface, which allows users to // commonize code for adding certs to normal/temporal logs. type AddLogClient interface { AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) } // TemporalLogClient allows [pre-]certificates to be uploaded to a temporal log. type TemporalLogClient struct { Clients []*LogClient intervals []interval } // NewTemporalLogClient builds a new client for interacting with a temporal log. // The provided config should be contiguous and chronological. func NewTemporalLogClient(cfg *configpb.TemporalLogConfig, hc *http.Client) (*TemporalLogClient, error) { if len(cfg.GetShard()) == 0 { return nil, errors.New("empty config") } overall, err := shardInterval(cfg.Shard[0]) if err != nil { return nil, fmt.Errorf("cfg.Shard[0] invalid: %v", err) } intervals := make([]interval, 0, len(cfg.Shard)) intervals = append(intervals, overall) for i := 1; i < len(cfg.Shard); i++ { interval, err := shardInterval(cfg.Shard[i]) if err != nil { return nil, fmt.Errorf("cfg.Shard[%d] invalid: %v", i, err) } if overall.upper == nil { return nil, fmt.Errorf("cfg.Shard[%d] extends an interval with no upper bound", i) } if interval.lower == nil { return nil, fmt.Errorf("cfg.Shard[%d] has no lower bound but extends an interval", i) } if !interval.lower.Equal(*overall.upper) { return nil, fmt.Errorf("cfg.Shard[%d] starts at %v but previous interval ended at %v", i, interval.lower, overall.upper) } overall.upper = interval.upper intervals = append(intervals, interval) } clients := make([]*LogClient, 0, len(cfg.Shard)) for i, shard := range cfg.Shard { opts := jsonclient.Options{UserAgent: "ct-go-multilog/1.0"} opts.PublicKeyDER = shard.GetPublicKeyDer() c, err := New(shard.Uri, hc, opts) if err != nil { return nil, fmt.Errorf("failed to create client for cfg.Shard[%d]: %v", i, err) } clients = append(clients, c) } tlc := TemporalLogClient{ Clients: clients, intervals: intervals, } return &tlc, nil } // GetAcceptedRoots retrieves the set of acceptable root certificates for all // of the shards of a temporal log (i.e. the union). func (tlc *TemporalLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) { type result struct { roots []ct.ASN1Cert err error } results := make(chan result, len(tlc.Clients)) for _, c := range tlc.Clients { go func(c *LogClient) { var r result r.roots, r.err = c.GetAcceptedRoots(ctx) results <- r }(c) } var allRoots []ct.ASN1Cert seen := make(map[[sha256.Size]byte]bool) for range tlc.Clients { r := <-results if r.err != nil { return nil, r.err } for _, root := range r.roots { h := sha256.Sum256(root.Data) if seen[h] { continue } seen[h] = true allRoots = append(allRoots, root) } } return allRoots, nil } // AddChain adds the (DER represented) X509 chain to the appropriate log. func (tlc *TemporalLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return tlc.addChain(ctx, ct.X509LogEntryType, ct.AddChainPath, chain) } // AddPreChain adds the (DER represented) Precertificate chain to the appropriate log. func (tlc *TemporalLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return tlc.addChain(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain) } func (tlc *TemporalLogClient) addChain(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { // Parse the first entry in the chain if len(chain) == 0 { return nil, errors.New("missing chain") } cert, err := x509.ParseCertificate(chain[0].Data) if err != nil { return nil, fmt.Errorf("failed to parse initial chain entry: %v", err) } cidx, err := tlc.IndexByDate(cert.NotAfter) if err != nil { return nil, fmt.Errorf("failed to find log to process cert: %v", err) } return tlc.Clients[cidx].addChainWithRetry(ctx, ctype, path, chain) } // IndexByDate returns the index of the Clients entry that is appropriate for the given // date. func (tlc *TemporalLogClient) IndexByDate(when time.Time) (int, error) { for i, interval := range tlc.intervals { if (interval.lower != nil) && when.Before(*interval.lower) { continue } if (interval.upper != nil) && !when.Before(*interval.upper) { continue } return i, nil } return -1, fmt.Errorf("no log found encompassing date %v", when) } func shardInterval(cfg *configpb.LogShardConfig) (interval, error) { var interval interval if cfg.NotAfterStart != nil { if err := cfg.NotAfterStart.CheckValid(); err != nil { return interval, fmt.Errorf("failed to parse NotAfterStart: %v", err) } t := cfg.NotAfterStart.AsTime() interval.lower = &t } if cfg.NotAfterLimit != nil { if err := cfg.NotAfterLimit.CheckValid(); err != nil { return interval, fmt.Errorf("failed to parse NotAfterLimit: %v", err) } t := cfg.NotAfterLimit.AsTime() interval.upper = &t } if interval.lower != nil && interval.upper != nil && !(*interval.lower).Before(*interval.upper) { return interval, errors.New("inverted interval") } return interval, nil } google-certificate-transparency-go-2308f62/client/multilog_test.go000066400000000000000000000362241462611535200253500ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client_test import ( "context" "encoding/pem" "net/http" "net/http/httptest" "strings" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/client/configpb" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" tspb "google.golang.org/protobuf/types/known/timestamppb" ) func TestNewTemporalLogClient(t *testing.T) { ts0 := tspb.New(time.Date(2010, 9, 19, 11, 00, 00, 00, time.UTC)) ts1 := tspb.New(time.Date(2011, 9, 19, 11, 00, 00, 00, time.UTC)) ts2 := tspb.New(time.Date(2012, 9, 19, 11, 00, 00, 00, time.UTC)) ts25 := tspb.New(time.Date(2013, 3, 19, 11, 00, 00, 00, time.UTC)) ts3 := tspb.New(time.Date(2013, 9, 19, 11, 00, 00, 00, time.UTC)) ts4 := tspb.New(time.Date(2014, 9, 19, 11, 00, 00, 00, time.UTC)) tests := []struct { cfg *configpb.TemporalLogConfig wantErr string }{ { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: nil, NotAfterLimit: nil}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "threeA", NotAfterStart: ts25, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, }, }, wantErr: "previous interval ended at", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil}, }, }, wantErr: "previous interval ended at", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts1}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, }, }, wantErr: "inverted", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: nil}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil}, }, }, wantErr: "no upper bound", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: nil, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil}, }, }, wantErr: "has no lower bound", }, { wantErr: "empty", }, { cfg: &configpb.TemporalLogConfig{Shard: []*configpb.LogShardConfig{}}, wantErr: "empty", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts1}, }, }, wantErr: "inverted", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts2, NotAfterLimit: ts1}, }, }, wantErr: "inverted", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: &tspb.Timestamp{Seconds: -1, Nanos: -1}, NotAfterLimit: ts2}, }, }, wantErr: "failed to parse", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "one", NotAfterStart: ts1, NotAfterLimit: &tspb.Timestamp{Seconds: -1, Nanos: -1}}, }, }, wantErr: "failed to parse", }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ { Uri: "one", NotAfterStart: nil, NotAfterLimit: nil, PublicKeyDer: []byte{0x01, 0x02}, }, }, }, wantErr: "invalid public key", }, } for _, test := range tests { _, err := client.NewTemporalLogClient(test.cfg, nil) if err != nil { if test.wantErr == "" { t.Errorf("NewTemporalLogClient(%+v)=nil,%v; want _,nil", test.cfg, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("NewTemporalLogClient(%+v)=nil,%v; want _,%q", test.cfg, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("NewTemporalLogClient(%+v)=_, nil; want _,%q", test.cfg, test.wantErr) } } } func TestIndexByDate(t *testing.T) { time0 := time.Date(2010, 9, 19, 11, 00, 00, 00, time.UTC) time1 := time.Date(2011, 9, 19, 11, 00, 00, 00, time.UTC) time19 := time.Date(2012, 9, 19, 10, 59, 59, 00, time.UTC) time2 := time.Date(2012, 9, 19, 11, 00, 00, 00, time.UTC) time25 := time.Date(2013, 3, 19, 11, 00, 00, 00, time.UTC) time3 := time.Date(2013, 9, 19, 11, 00, 00, 00, time.UTC) time4 := time.Date(2014, 9, 19, 11, 00, 00, 00, time.UTC) ts0 := tspb.New(time0) ts1 := tspb.New(time1) ts2 := tspb.New(time2) ts3 := tspb.New(time3) ts4 := tspb.New(time4) allCfg := &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "zero", NotAfterStart: nil, NotAfterLimit: ts0}, {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, {Uri: "five", NotAfterStart: ts4, NotAfterLimit: nil}, }, } uptoCfg := &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "zero", NotAfterStart: nil, NotAfterLimit: ts0}, {Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4}, }, } fromCfg := &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "zero", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "two", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "three", NotAfterStart: ts3, NotAfterLimit: ts4}, {Uri: "four", NotAfterStart: ts4, NotAfterLimit: nil}, }, } boundedCfg := &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: "zero", NotAfterStart: ts0, NotAfterLimit: ts1}, {Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts2}, {Uri: "two", NotAfterStart: ts2, NotAfterLimit: ts3}, {Uri: "three", NotAfterStart: ts3, NotAfterLimit: ts4}, }, } tests := []struct { cfg *configpb.TemporalLogConfig when time.Time want int wantErr bool }{ {cfg: allCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), want: 0}, {cfg: allCfg, when: time0, want: 1}, {cfg: allCfg, when: time1, want: 2}, {cfg: allCfg, when: time19, want: 2}, {cfg: allCfg, when: time2, want: 3}, {cfg: allCfg, when: time25, want: 3}, {cfg: allCfg, when: time3, want: 4}, {cfg: allCfg, when: time4, want: 5}, {cfg: allCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), want: 5}, {cfg: uptoCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), want: 0}, {cfg: uptoCfg, when: time0, want: 1}, {cfg: uptoCfg, when: time1, want: 2}, {cfg: uptoCfg, when: time2, want: 3}, {cfg: uptoCfg, when: time25, want: 3}, {cfg: uptoCfg, when: time3, want: 4}, {cfg: uptoCfg, when: time4, wantErr: true}, {cfg: uptoCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true}, {cfg: fromCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true}, {cfg: fromCfg, when: time0, want: 0}, {cfg: fromCfg, when: time1, want: 1}, {cfg: fromCfg, when: time2, want: 2}, {cfg: fromCfg, when: time25, want: 2}, {cfg: fromCfg, when: time3, want: 3}, {cfg: fromCfg, when: time4, want: 4}, {cfg: fromCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), want: 4}, {cfg: boundedCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true}, {cfg: boundedCfg, when: time0, want: 0}, {cfg: boundedCfg, when: time1, want: 1}, {cfg: boundedCfg, when: time2, want: 2}, {cfg: boundedCfg, when: time25, want: 2}, {cfg: boundedCfg, when: time3, want: 3}, {cfg: boundedCfg, when: time4, wantErr: true}, {cfg: boundedCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true}, } for _, test := range tests { tlc, err := client.NewTemporalLogClient(test.cfg, nil) if err != nil { t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err) continue } got, err := tlc.IndexByDate(test.when) if err != nil { if !test.wantErr { t.Errorf("NewTemporalLogClient(%+v).idxByDate()=%d,%v; want %d,nil", test.cfg, got, err, test.want) } continue } if test.wantErr { t.Errorf("NewTemporalLogClient(%+v).idxByDate(%v)=%d, nil; want _, 'no log found'", test.cfg, test.when, got) } if got != test.want { t.Errorf("NewTemporalLogClient(%+v).idxByDate(%v)=%d, nil; want %d, nil", test.cfg, test.when, got, test.want) } } } func TestTemporalAddChain(t *testing.T) { hs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/ct/v1/add-chain": data, _ := sctToJSON(testdata.TestCertProof) if _, err := w.Write(data); err != nil { t.Error(err) } case "/ct/v1/add-pre-chain": data, _ := sctToJSON(testdata.TestPreCertProof) if _, err := w.Write(data); err != nil { t.Error(err) } default: t.Fatalf("Incorrect URL path: %s", r.URL.Path) } })) defer hs.Close() cert, err := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) if err != nil { t.Fatalf("Failed to parse certificate from PEM: %v", err) } certChain := []ct.ASN1Cert{{Data: cert.Raw}} precert, err := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM)) if x509.IsFatal(err) { t.Fatalf("Failed to parse pre-certificate from PEM: %v", err) } issuer, err := x509util.CertificateFromPEM([]byte(testdata.CACertPEM)) if x509.IsFatal(err) { t.Fatalf("Failed to parse issuer certificate from PEM: %v", err) } precertChain := []ct.ASN1Cert{{Data: precert.Raw}, {Data: issuer.Raw}} // Both have Not After = Jun 1 00:00:00 2022 GMT ts1 := tspb.New(time.Date(2022, 5, 19, 11, 00, 00, 00, time.UTC)) ts2 := tspb.New(time.Date(2022, 6, 19, 11, 00, 00, 00, time.UTC)) p, _ := pem.Decode([]byte(testdata.LogPublicKeyPEM)) if p == nil { t.Fatalf("Failed to parse public key from PEM: %v", err) } tests := []struct { cfg *configpb.TemporalLogConfig wantErr bool }{ { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: nil, PublicKeyDer: p.Bytes}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: ts2, PublicKeyDer: p.Bytes}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: ts1, NotAfterLimit: nil, PublicKeyDer: p.Bytes}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: ts1, NotAfterLimit: ts2, PublicKeyDer: p.Bytes}, }, }, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: ts1, PublicKeyDer: p.Bytes}, }, }, wantErr: true, }, { cfg: &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ {Uri: hs.URL, NotAfterStart: ts2, NotAfterLimit: nil, PublicKeyDer: p.Bytes}, }, }, wantErr: true, }, } ctx := context.Background() for _, test := range tests { tlc, err := client.NewTemporalLogClient(test.cfg, nil) if err != nil { t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err) continue } _, err = tlc.AddChain(ctx, certChain) if err != nil { if !test.wantErr { t.Errorf("AddChain()=nil,%v; want sct,nil", err) } } else if test.wantErr { t.Errorf("AddChain()=sct,nil; want nil,_") } _, err = tlc.AddPreChain(ctx, precertChain) if err != nil { if !test.wantErr { t.Errorf("AddPreChain()=nil,%v; want sct,nil", err) } } else if test.wantErr { t.Errorf("AddPreChain()=sct,nil; want nil,_") } } } func TestTemporalAddChainErrors(t *testing.T) { hs := serveSCTAt(t, "/ct/v1/add-chain", testdata.TestCertProof) defer hs.Close() cfg := &configpb.TemporalLogConfig{ Shard: []*configpb.LogShardConfig{ { Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: nil, }, }, } ctx := context.Background() tlc, err := client.NewTemporalLogClient(cfg, nil) if err != nil { t.Fatalf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", cfg, err) } _, err = tlc.AddChain(ctx, nil) if err == nil { t.Errorf("AddChain(nil)=sct,nil; want nil, 'missing chain'") } _, err = tlc.AddChain(ctx, []ct.ASN1Cert{{Data: []byte{0x01, 0x02}}}) if err == nil { t.Errorf("AddChain(nil)=sct,nil; want nil, 'failed to parse'") } } google-certificate-transparency-go-2308f62/cloudbuild.yaml000066400000000000000000000164761462611535200236710ustar00rootroot00000000000000############################################################################# ## The top section of this file is identical in the 3 cloudbuild.*yaml files. ## Make sure any edits you make here are copied over to the other files too ## if appropriate. ## ## TODO(al): consider if it's possible to merge these 3 files and control via ## substitutions. ############################################################################# timeout: 1200s options: machineType: N1_HIGHCPU_32 volumes: - name: go-modules path: /go env: - GOPROXY=https://proxy.golang.org - PROJECT_ROOT=github.com/google/certificate-transparency-go - GOPATH=/go substitutions: _CLUSTER_NAME: trillian-opensource-ci _MASTER_ZONE: us-central1-a # Cloud Build logs sent to GCS bucket logsBucket: 'gs://trillian-cloudbuild-logs' steps: # First build a "ct_testbase" docker image which contains most of the tools we need for the later steps: - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: ['-c', 'docker pull gcr.io/$PROJECT_ID/ct_testbase:latest || exit 0'] - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '--cache-from', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '-f', './integration/Dockerfile', '.' ] # prepare spins up an ephemeral trillian instance for testing use. - name: gcr.io/$PROJECT_ID/ct_testbase entrypoint: 'bash' id: 'prepare' args: - '-exc' - | # Use latest versions of Trillian docker images built by the Trillian CI cloudbuilders. docker pull gcr.io/$PROJECT_ID/log_server:latest docker tag gcr.io/$PROJECT_ID/log_server:latest deployment_trillian-log-server docker pull gcr.io/$PROJECT_ID/log_signer:latest docker tag gcr.io/$PROJECT_ID/log_signer:latest deployment_trillian-log-signer # Bring up an ephemeral trillian instance using the docker-compose config in the Trillian repo: export TRILLIAN_LOCATION="$$(go list -f '{{.Dir}}' github.com/google/trillian)" # We need to fix up Trillian's docker-compose to connect to the CloudBuild network to that tests can use it: echo -e "networks:\n default:\n external:\n name: cloudbuild" >> $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml pull mysql trillian-log-server trillian-log-signer docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml up -d mysql trillian-log-server trillian-log-signer # Install proto related bits and block on Trillian being ready - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci-ready' entrypoint: 'bash' args: - '-ec' - | go install \ github.com/golang/protobuf/proto \ github.com/golang/protobuf/protoc-gen-go \ github.com/golang/mock/mockgen \ go.etcd.io/etcd/v3 go.etcd.io/etcd/etcdctl/v3 \ github.com/fullstorydev/grpcurl/cmd/grpcurl # Generate all protoc and mockgen files go generate -run="protoc" ./... go generate -run="mockgen" ./... # Cache all the modules we'll need too go mod download go test ./... # Wait for trillian logserver to be up until nc -z deployment_trillian-log-server_1 8090; do echo .; sleep 5; done # Reset the CT test database export CT_GO_PATH="$$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go)" export MYSQL_HOST="mysql" export MYSQL_ROOT_PASSWORD="zaphod" export MYSQL_USER_HOST="%" yes | bash "$${CT_GO_PATH}/scripts/resetctdb.sh" --verbose waitFor: ['prepare'] # Run the presubmit tests - name: gcr.io/$PROJECT_ID/ct_testbase id: 'default_test' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'race_detection' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_coverage' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --no-generate --coverage' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_race' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'with_pkcs11_and_race' env: - 'GOFLAGS=-race --tags=pkcs11' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'WITH_PKCS11=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] # Collect and submit codecoverage reports - name: 'gcr.io/cloud-builders/curl' id: 'codecov.io' entrypoint: bash args: ['-c', 'bash <(curl -s https://codecov.io/bash)'] env: - 'VCS_COMMIT_ID=$COMMIT_SHA' - 'VCS_BRANCH_NAME=$BRANCH_NAME' - 'VCS_PULL_REQUEST=$_PR_NUMBER' - 'CI_BUILD_ID=$BUILD_ID' - 'CODECOV_TOKEN=$_CODECOV_TOKEN' # _CODECOV_TOKEN is specified in the cloud build trigger waitFor: ['etcd_with_coverage'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci_complete' entrypoint: /bin/true waitFor: ['codecov.io', 'default_test', 'race_detection', 'etcd_with_coverage', 'etcd_with_race', 'with_pkcs11_and_race'] ############################################################################ ## End of replicated section. ## Below are deployment specific steps for the CD env. ############################################################################ - id: build_ctfe name: gcr.io/cloud-builders/docker args: - build - --file=trillian/examples/deployment/docker/ctfe/Dockerfile - --tag=gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} - --cache-from=gcr.io/${PROJECT_ID}/ctfe - . waitFor: [-] - id: build_envsubst name: gcr.io/cloud-builders/docker args: - build - trillian/examples/deployment/docker/envsubst - -t - envsubst waitFor: ['ci_complete'] - id: envsubst_kubernetes_configs name: envsubst args: - trillian/examples/deployment/kubernetes/ctfe-deployment.yaml - trillian/examples/deployment/kubernetes/ctfe-service.yaml - trillian/examples/deployment/kubernetes/ctfe-ingress.yaml env: - PROJECT_ID=${PROJECT_ID} - IMAGE_TAG=${COMMIT_SHA} waitFor: - build_envsubst - id: update_kubernetes_configs_dryrun name: gcr.io/cloud-builders/kubectl args: - apply - --dry-run=server - -f=trillian/examples/deployment/kubernetes/ctfe-deployment.yaml - -f=trillian/examples/deployment/kubernetes/ctfe-service.yaml - -f=trillian/examples/deployment/kubernetes/ctfe-ingress.yaml env: - CLOUDSDK_COMPUTE_ZONE=${_MASTER_ZONE} - CLOUDSDK_CONTAINER_CLUSTER=${_CLUSTER_NAME} waitFor: - envsubst_kubernetes_configs - build_ctfe images: - gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} - gcr.io/${PROJECT_ID}/ct_testbase:latest google-certificate-transparency-go-2308f62/cloudbuild_master.yaml000066400000000000000000000171671462611535200252420ustar00rootroot00000000000000############################################################################# ## The top section of this file is identical in the 3 cloudbuild.*yaml files. ## Make sure any edits you make here are copied over to the other files too ## if appropriate. ## ## TODO(al): consider if it's possible to merge these 3 files and control via ## substitutions. ############################################################################# timeout: 1200s options: machineType: N1_HIGHCPU_32 volumes: - name: go-modules path: /go env: - GOPROXY=https://proxy.golang.org - PROJECT_ROOT=github.com/google/certificate-transparency-go - GOPATH=/go substitutions: _CLUSTER_NAME: trillian-opensource-ci _MASTER_ZONE: us-central1-a # Cloud Build logs sent to GCS bucket logsBucket: 'gs://trillian-cloudbuild-logs' steps: # First build a "ct_testbase" docker image which contains most of the tools we need for the later steps: - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: ['-c', 'docker pull gcr.io/$PROJECT_ID/ct_testbase:latest || exit 0'] - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '--cache-from', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '-f', './integration/Dockerfile', '.' ] # prepare spins up an ephemeral trillian instance for testing use. - name: gcr.io/$PROJECT_ID/ct_testbase entrypoint: 'bash' id: 'prepare' args: - '-exc' - | # Use latest versions of Trillian docker images built by the Trillian CI cloudbuilders. docker pull gcr.io/$PROJECT_ID/log_server:latest docker tag gcr.io/$PROJECT_ID/log_server:latest deployment_trillian-log-server docker pull gcr.io/$PROJECT_ID/log_signer:latest docker tag gcr.io/$PROJECT_ID/log_signer:latest deployment_trillian-log-signer # Bring up an ephemeral trillian instance using the docker-compose config in the Trillian repo: export TRILLIAN_LOCATION="$$(go list -f '{{.Dir}}' github.com/google/trillian)" # We need to fix up Trillian's docker-compose to connect to the CloudBuild network to that tests can use it: echo -e "networks:\n default:\n external:\n name: cloudbuild" >> $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml pull mysql trillian-log-server trillian-log-signer docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml up -d mysql trillian-log-server trillian-log-signer # Install proto related bits and block on Trillian being ready - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci-ready' entrypoint: 'bash' args: - '-ec' - | go install \ github.com/golang/protobuf/proto \ github.com/golang/protobuf/protoc-gen-go \ github.com/golang/mock/mockgen \ go.etcd.io/etcd/v3 go.etcd.io/etcd/etcdctl/v3 \ github.com/fullstorydev/grpcurl/cmd/grpcurl # Generate all protoc and mockgen files go generate -run="protoc" ./... go generate -run="mockgen" ./... # Cache all the modules we'll need too go mod download go test ./... # Wait for trillian logserver to be up until nc -z deployment_trillian-log-server_1 8090; do echo .; sleep 5; done # Reset the CT test database export CT_GO_PATH="$$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go)" export MYSQL_HOST="mysql" export MYSQL_ROOT_PASSWORD="zaphod" export MYSQL_USER_HOST="%" yes | bash "$${CT_GO_PATH}/scripts/resetctdb.sh" --verbose waitFor: ['prepare'] # Run the presubmit tests - name: gcr.io/$PROJECT_ID/ct_testbase id: 'default_test' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'race_detection' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_coverage' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --no-generate --coverage' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_race' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'with_pkcs11_and_race' env: - 'GOFLAGS=-race --tags=pkcs11' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'WITH_PKCS11=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] # Collect and submit codecoverage reports - name: 'gcr.io/cloud-builders/curl' id: 'codecov.io' entrypoint: bash args: ['-c', 'bash <(curl -s https://codecov.io/bash)'] env: - 'VCS_COMMIT_ID=$COMMIT_SHA' - 'VCS_BRANCH_NAME=$BRANCH_NAME' - 'VCS_PULL_REQUEST=$_PR_NUMBER' - 'CI_BUILD_ID=$BUILD_ID' - 'CODECOV_TOKEN=$_CODECOV_TOKEN' # _CODECOV_TOKEN is specified in the cloud build trigger waitFor: ['etcd_with_coverage'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci_complete' entrypoint: /bin/true waitFor: ['codecov.io', 'default_test', 'race_detection', 'etcd_with_coverage', 'etcd_with_race', 'with_pkcs11_and_race'] ############################################################################ ## End of replicated section. ## Below are deployment specific steps for the CD env. ############################################################################ - id: build_ctfe name: gcr.io/cloud-builders/docker args: - build - --file=trillian/examples/deployment/docker/ctfe/Dockerfile - --tag=gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} - --cache-from=gcr.io/${PROJECT_ID}/ctfe - . waitFor: ["-"] - id: push_ctfe name: gcr.io/cloud-builders/docker args: - push - gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} waitFor: - build_ctfe - id: tag_latest_ctfe name: gcr.io/cloud-builders/gcloud args: - container - images - add-tag - gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} - gcr.io/${PROJECT_ID}/ctfe:latest waitFor: - push_ctfe - id: build_envsubst name: gcr.io/cloud-builders/docker args: - build - trillian/examples/deployment/docker/envsubst - -t - envsubst waitFor: ["-"] - id: envsubst_kubernetes_configs name: envsubst args: - trillian/examples/deployment/kubernetes/ctfe-deployment.yaml - trillian/examples/deployment/kubernetes/ctfe-service.yaml - trillian/examples/deployment/kubernetes/ctfe-ingress.yaml env: - PROJECT_ID=${PROJECT_ID} - IMAGE_TAG=${COMMIT_SHA} waitFor: - build_envsubst - id: update_kubernetes_configs name: gcr.io/cloud-builders/kubectl args: - apply - -f=trillian/examples/deployment/kubernetes/ctfe-deployment.yaml - -f=trillian/examples/deployment/kubernetes/ctfe-service.yaml - -f=trillian/examples/deployment/kubernetes/ctfe-ingress.yaml env: - CLOUDSDK_COMPUTE_ZONE=${_MASTER_ZONE} - CLOUDSDK_CONTAINER_CLUSTER=${_CLUSTER_NAME} waitFor: - envsubst_kubernetes_configs - push_ctfe images: - gcr.io/${PROJECT_ID}/ctfe:${COMMIT_SHA} - gcr.io/${PROJECT_ID}/ct_testbase:latest google-certificate-transparency-go-2308f62/cloudbuild_tag.yaml000066400000000000000000000137521462611535200245160ustar00rootroot00000000000000############################################################################# ## The top section of this file is identical in the 3 cloudbuild.*yaml files. ## Make sure any edits you make here are copied over to the other files too ## if appropriate. ## ## TODO(al): consider if it's possible to merge these 3 files and control via ## substitutions. ############################################################################# timeout: 1200s options: machineType: N1_HIGHCPU_32 volumes: - name: go-modules path: /go env: - GOPROXY=https://proxy.golang.org - PROJECT_ROOT=github.com/google/certificate-transparency-go - GOPATH=/go substitutions: _CLUSTER_NAME: trillian-opensource-ci _MASTER_ZONE: us-central1-a # Cloud Build logs sent to GCS bucket logsBucket: 'gs://trillian-cloudbuild-logs' steps: # First build a "ct_testbase" docker image which contains most of the tools we need for the later steps: - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: ['-c', 'docker pull gcr.io/$PROJECT_ID/ct_testbase:latest || exit 0'] - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '--cache-from', 'gcr.io/$PROJECT_ID/ct_testbase:latest', '-f', './integration/Dockerfile', '.' ] # prepare spins up an ephemeral trillian instance for testing use. - name: gcr.io/$PROJECT_ID/ct_testbase entrypoint: 'bash' id: 'prepare' args: - '-exc' - | # Use latest versions of Trillian docker images built by the Trillian CI cloudbuilders. docker pull gcr.io/$PROJECT_ID/log_server:latest docker tag gcr.io/$PROJECT_ID/log_server:latest deployment_trillian-log-server docker pull gcr.io/$PROJECT_ID/log_signer:latest docker tag gcr.io/$PROJECT_ID/log_signer:latest deployment_trillian-log-signer # Bring up an ephemeral trillian instance using the docker-compose config in the Trillian repo: export TRILLIAN_LOCATION="$$(go list -f '{{.Dir}}' github.com/google/trillian)" # We need to fix up Trillian's docker-compose to connect to the CloudBuild network to that tests can use it: echo -e "networks:\n default:\n external:\n name: cloudbuild" >> $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml pull mysql trillian-log-server trillian-log-signer docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/docker-compose.yml up -d mysql trillian-log-server trillian-log-signer # Install proto related bits and block on Trillian being ready - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci-ready' entrypoint: 'bash' args: - '-ec' - | go install \ github.com/golang/protobuf/proto \ github.com/golang/protobuf/protoc-gen-go \ github.com/golang/mock/mockgen \ go.etcd.io/etcd/v3 go.etcd.io/etcd/etcdctl/v3 \ github.com/fullstorydev/grpcurl/cmd/grpcurl # Generate all protoc and mockgen files go generate -run="protoc" ./... go generate -run="mockgen" ./... # Cache all the modules we'll need too go mod download go test ./... # Wait for trillian logserver to be up until nc -z deployment_trillian-log-server_1 8090; do echo .; sleep 5; done waitFor: ['prepare'] # Run the presubmit tests - name: gcr.io/$PROJECT_ID/ct_testbase id: 'default_test' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --no-generate' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'race_detection' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_coverage' env: - 'GOFLAGS=' - 'PRESUBMIT_OPTS=--no-linters --coverage' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'etcd_with_race' env: - 'GOFLAGS=-race' - 'PRESUBMIT_OPTS=--no-linters' - 'WITH_ETCD=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'with_pkcs11_and_race' env: - 'GOFLAGS=-race --tags=pkcs11' - 'PRESUBMIT_OPTS=--no-linters' - 'WITH_PKCS11=true' - 'TRILLIAN_LOG_SERVERS=deployment_trillian-log-server_1:8090' - 'TRILLIAN_LOG_SERVER_1=deployment_trillian-log-server_1:8090' waitFor: ['ci-ready'] # Collect and submit codecoverage reports - name: 'gcr.io/cloud-builders/curl' id: 'codecov.io' entrypoint: bash args: ['-c', 'bash <(curl -s https://codecov.io/bash)'] env: - 'VCS_COMMIT_ID=$COMMIT_SHA' - 'VCS_BRANCH_NAME=$BRANCH_NAME' - 'VCS_PULL_REQUEST=$_PR_NUMBER' - 'CI_BUILD_ID=$BUILD_ID' - 'CODECOV_TOKEN=$_CODECOV_TOKEN' # _CODECOV_TOKEN is specified in the cloud build trigger waitFor: ['etcd_with_coverage'] - name: gcr.io/$PROJECT_ID/ct_testbase id: 'ci_complete' entrypoint: /bin/true waitFor: ['codecov.io', 'default_test', 'race_detection', 'etcd_with_coverage', 'etcd_with_race', 'with_pkcs11_and_race'] ############################################################################ ## End of replicated section. ## Below are deployment specific steps for the CD env. ############################################################################ - id: build_ctfe name: gcr.io/cloud-builders/docker args: - build - --file=trillian/examples/deployment/docker/ctfe/Dockerfile - --tag=gcr.io/${PROJECT_ID}/ctfe:${TAG_NAME} - --cache-from=gcr.io/${PROJECT_ID}/ctfe - . images: - gcr.io/${PROJECT_ID}/ctfe:${TAG_NAME} - gcr.io/${PROJECT_ID}/ct_testbase:latest google-certificate-transparency-go-2308f62/codecov.yml000066400000000000000000000010171462611535200230050ustar00rootroot00000000000000# Customizations to codecov for c-t-go repo. This will be merged into # the team / default codecov yaml file. # # Validate changes with: # curl --data-binary @codecov.yml https://codecov.io/validate # Exclude code that's for testing, demos or utilities that aren't really # part of production releases. ignore: - "**/mock_*.go" - "**/testonly" - "trillian/integration" coverage: status: project: default: # Allow 1% coverage drop without complaining, to avoid being too noisy. threshold: 1% google-certificate-transparency-go-2308f62/ctpolicy/000077500000000000000000000000001462611535200224675ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/ctpolicy/applepolicy.go000066400000000000000000000031471462611535200253440ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509" ) // AppleCTPolicy implements logic for complying with Apple's CT log policy. type AppleCTPolicy struct{} // LogsByGroup describes submission requirements for embedded SCTs according to // https://support.apple.com/en-us/HT205280. Returns an error if it's not // possible to satisfy the policy with the provided loglist. func (appleP AppleCTPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist3.LogList) (LogPolicyData, error) { var incCount int switch m := lifetimeInMonths(cert); { case m < 15: incCount = 2 case m <= 27: incCount = 3 case m <= 39: incCount = 4 default: incCount = 5 } baseGroup, err := BaseGroupFor(approved, incCount) if err != nil { return nil, err } groups := LogPolicyData{baseGroup.Name: baseGroup} return groups, nil } // Name returns label for the submission policy. func (appleP AppleCTPolicy) Name() string { return "Apple" } google-certificate-transparency-go-2308f62/ctpolicy/applepolicy_test.go000066400000000000000000000047701462611535200264060ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "testing" "github.com/google/certificate-transparency-go/x509" "github.com/kylelemons/godebug/pretty" ) func wantedAppleGroups(count int) LogPolicyData { gi := LogPolicyData{ BaseName: { Name: BaseName, LogURLs: map[string]bool{ "https://ct.googleapis.com/aviator/": true, "https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/rocketeer/": true, "https://ct.googleapis.com/racketeer/": true, "https://ct.googleapis.com/logs/argon2020/": true, "https://log.bob.io": true, }, MinInclusions: count, IsBase: true, LogWeights: map[string]float32{ "https://ct.googleapis.com/aviator/": 1.0, "https://ct.googleapis.com/icarus/": 1.0, "https://ct.googleapis.com/rocketeer/": 1.0, "https://ct.googleapis.com/racketeer/": 1.0, "https://ct.googleapis.com/logs/argon2020/": 1.0, "https://log.bob.io": 1.0, }, }, } return gi } func TestCheckApplePolicy(t *testing.T) { tests := []struct { name string cert *x509.Certificate want LogPolicyData }{ { name: "Short", cert: getTestCertPEMShort(), want: wantedAppleGroups(2), }, { name: "2-year", cert: getTestCertPEM2Years(), want: wantedAppleGroups(3), }, { name: "3-year", cert: getTestCertPEM3Years(), want: wantedAppleGroups(4), }, { name: "Long", cert: getTestCertPEMLongOriginal(), want: wantedAppleGroups(5), }, } var policy AppleCTPolicy sampleLogList := sampleLogList(t) for _, test := range tests { t.Run(test.name, func(t *testing.T) { groups, err := policy.LogsByGroup(test.cert, sampleLogList) if err != nil { t.Errorf("LogsByGroup returned an error: %v", err) } if diff := pretty.Compare(test.want, groups); diff != "" { t.Errorf("LogsByGroup: (-want +got)\n%s", diff) } }) } } google-certificate-transparency-go-2308f62/ctpolicy/chromepolicy.go000066400000000000000000000043261462611535200255200ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509" ) // ChromeCTPolicy implements logic for complying with Chrome's CT log policy type ChromeCTPolicy struct { } // LogsByGroup describes submission requirements for embedded SCTs according to // https://github.com/chromium/ct-policy/blob/master/ct_policy.md#qualifying-certificate. // Returns an error if it's not possible to satisfy the policy with the provided loglist. func (chromeP ChromeCTPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist3.LogList) (LogPolicyData, error) { googGroup := LogGroupInfo{Name: "Google-operated", IsBase: false} googGroup.populate(approved, func(op *loglist3.Operator) bool { return op.GoogleOperated() }) if err := googGroup.setMinInclusions(1); err != nil { return nil, err } nonGoogGroup := LogGroupInfo{Name: "Non-Google-operated", IsBase: false} nonGoogGroup.populate(approved, func(op *loglist3.Operator) bool { return !op.GoogleOperated() }) if err := nonGoogGroup.setMinInclusions(1); err != nil { return nil, err } var incCount int switch m := lifetimeInMonths(cert); { case m < 15: incCount = 2 case m <= 27: incCount = 3 case m <= 39: incCount = 4 default: incCount = 5 } baseGroup, err := BaseGroupFor(approved, incCount) if err != nil { return nil, err } groups := LogPolicyData{ googGroup.Name: &googGroup, nonGoogGroup.Name: &nonGoogGroup, baseGroup.Name: baseGroup, } return groups, nil } // Name returns label for the submission policy. func (chromeP ChromeCTPolicy) Name() string { return "Chrome" } google-certificate-transparency-go-2308f62/ctpolicy/chromepolicy_test.go000066400000000000000000000127061462611535200265600ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "testing" "github.com/google/certificate-transparency-go/x509" "github.com/kylelemons/godebug/pretty" ) func wantedGroups(goog int, nonGoog int, base int, minusBob bool) LogPolicyData { gi := LogPolicyData{ "Google-operated": { Name: "Google-operated", LogURLs: map[string]bool{ "https://ct.googleapis.com/logs/argon2020/": true, "https://ct.googleapis.com/aviator/": true, "https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/rocketeer/": true, "https://ct.googleapis.com/racketeer/": true, }, MinInclusions: goog, IsBase: false, LogWeights: map[string]float32{ "https://ct.googleapis.com/logs/argon2020/": 1.0, "https://ct.googleapis.com/aviator/": 1.0, "https://ct.googleapis.com/icarus/": 1.0, "https://ct.googleapis.com/rocketeer/": 1.0, "https://ct.googleapis.com/racketeer/": 1.0, }, }, "Non-Google-operated": { Name: "Non-Google-operated", LogURLs: map[string]bool{ "https://log.bob.io": true, }, MinInclusions: nonGoog, IsBase: false, LogWeights: map[string]float32{ "https://log.bob.io": 1.0, }, }, BaseName: { Name: BaseName, LogURLs: map[string]bool{ "https://ct.googleapis.com/logs/argon2020/": true, "https://ct.googleapis.com/aviator/": true, "https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/rocketeer/": true, "https://ct.googleapis.com/racketeer/": true, "https://log.bob.io": true, }, MinInclusions: base, IsBase: true, LogWeights: map[string]float32{ "https://ct.googleapis.com/logs/argon2020/": 1.0, "https://ct.googleapis.com/aviator/": 1.0, "https://ct.googleapis.com/icarus/": 1.0, "https://ct.googleapis.com/rocketeer/": 1.0, "https://ct.googleapis.com/racketeer/": 1.0, "https://log.bob.io": 1.0, }, }, } if minusBob { delete(gi[BaseName].LogURLs, "https://log.bob.io") delete(gi[BaseName].LogWeights, "https://log.bob.io") delete(gi["Non-Google-operated"].LogURLs, "https://log.bob.io") delete(gi["Non-Google-operated"].LogWeights, "https://log.bob.io") } return gi } func TestCheckChromePolicy(t *testing.T) { tests := []struct { name string cert *x509.Certificate want LogPolicyData }{ { name: "Short", cert: getTestCertPEMShort(), want: wantedGroups(1, 1, 2, false), }, { name: "2-year", cert: getTestCertPEM2Years(), want: wantedGroups(1, 1, 3, false), }, { name: "3-year", cert: getTestCertPEM3Years(), want: wantedGroups(1, 1, 4, false), }, { name: "Long", cert: getTestCertPEMLongOriginal(), want: wantedGroups(1, 1, 5, false), }, } var policy ChromeCTPolicy sampleLogList := sampleLogList(t) for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := policy.LogsByGroup(test.cert, sampleLogList) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("LogsByGroup: (-want +got)\n%s", diff) } if err != nil { t.Errorf("LogsByGroup returned an error when not expected: %v", err) } }) } } func TestCheckChromePolicyWarnings(t *testing.T) { tests := []struct { name string cert *x509.Certificate want LogPolicyData warning string }{ { name: "Short", cert: getTestCertPEMShort(), want: LogPolicyData{}, warning: "trying to assign 1 minimal inclusion number while only 0 logs are part of group \"Non-Google-operated\"", }, { name: "2-year", cert: getTestCertPEM2Years(), want: LogPolicyData{}, warning: "trying to assign 1 minimal inclusion number while only 0 logs are part of group \"Non-Google-operated\"", }, { name: "3-year", cert: getTestCertPEM3Years(), want: LogPolicyData{}, warning: "trying to assign 1 minimal inclusion number while only 0 logs are part of group \"Non-Google-operated\"", }, { name: "Long", cert: getTestCertPEMLongOriginal(), want: LogPolicyData{}, warning: "trying to assign 1 minimal inclusion number while only 0 logs are part of group \"Non-Google-operated\"", }, } var policy ChromeCTPolicy sampleLogList := sampleLogList(t) // Removing Bob-log. sampleLogList.Operators = sampleLogList.Operators[:1] for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := policy.LogsByGroup(test.cert, sampleLogList) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("LogsByGroup: (-want +got)\n%s", diff) } if err == nil && len(test.warning) > 0 { t.Errorf("LogsByGroup returned no error when expected") } else if err != nil { if err.Error() != test.warning { t.Errorf("LogsByGroup returned error message %q while expected %q", err.Error(), test.warning) } } }) } } google-certificate-transparency-go-2308f62/ctpolicy/ctpolicy.go000066400000000000000000000163761462611535200246610ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package ctpolicy contains structs describing CT policy requirements and corresponding logic. package ctpolicy import ( "fmt" "sync" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509" ) const ( // BaseName is name for the group covering all logs. BaseName = "All-logs" ) // LogGroupInfo holds information on a single group of logs specified by Policy. type LogGroupInfo struct { Name string LogURLs map[string]bool // set of members MinInclusions int // Required number of submissions. IsBase bool // True only for Log-group covering all logs. LogWeights map[string]float32 // weights used for submission, default weight is 1 wMu sync.RWMutex // guards weights } func (group *LogGroupInfo) setMinInclusions(i int) error { if i < 0 { return fmt.Errorf("cannot assign negative minimal inclusions number") } // Assign given number even if it's bigger than group size. group.MinInclusions = i if i > len(group.LogURLs) { return fmt.Errorf("trying to assign %d minimal inclusion number while only %d logs are part of group %q", i, len(group.LogURLs), group.Name) } return nil } func (group *LogGroupInfo) populate(ll *loglist3.LogList, included func(op *loglist3.Operator) bool) { group.LogURLs = make(map[string]bool) group.LogWeights = make(map[string]float32) for _, op := range ll.Operators { if included(op) { for _, l := range op.Logs { group.LogURLs[l.URL] = true group.LogWeights[l.URL] = 1.0 } } } } // satisfyMinimalInclusion returns whether number of positive weights is // bigger or equal to minimal inclusion number. func (group *LogGroupInfo) satisfyMinimalInclusion(weights map[string]float32) bool { nonZeroNum := 0 for logURL, w := range weights { if group.LogURLs[logURL] && w > 0.0 { nonZeroNum++ if nonZeroNum >= group.MinInclusions { return true } } } return false } // SetLogWeights applies suggested weights to the Log-group. Does not reset // weights and returns error when there are not enough positive weights // provided to reach minimal inclusion number. func (group *LogGroupInfo) SetLogWeights(weights map[string]float32) error { for logURL, w := range weights { if w < 0.0 { return fmt.Errorf("trying to assign negative weight %v to Log %q", w, logURL) } } if !group.satisfyMinimalInclusion(weights) { return fmt.Errorf("trying to assign weights %v resulting in inability to reach minimal inclusion number %d", weights, group.MinInclusions) } group.wMu.Lock() defer group.wMu.Unlock() // All group weights initially reset to 0.0 for logURL := range group.LogURLs { group.LogWeights[logURL] = 0.0 } for logURL, w := range weights { if group.LogURLs[logURL] { group.LogWeights[logURL] = w } } return nil } // SetLogWeight tries setting the weight for a single Log of the Log-group. // Does not reset the weight and returns error if weight is non-positive and // its setting will result in inability to reach minimal inclusion number. func (group *LogGroupInfo) SetLogWeight(logURL string, w float32) error { if !group.LogURLs[logURL] { return fmt.Errorf("trying to assign weight to Log %q not belonging to the group", logURL) } if w < 0.0 { return fmt.Errorf("trying to assign negative weight %v to Log %q", w, logURL) } newWeights := make(map[string]float32) for l, wt := range group.LogWeights { newWeights[l] = wt } newWeights[logURL] = w if !group.satisfyMinimalInclusion(newWeights) { return fmt.Errorf("assigning weight %v to Log %q will result in inability to reach minimal inclusion number %d", w, logURL, group.MinInclusions) } group.wMu.Lock() defer group.wMu.Unlock() group.LogWeights = newWeights return nil } // GetSubmissionSession produces list of log-URLs of the Log-group. // Order of the list is weighted random defined by Log-weights within the group func (group *LogGroupInfo) GetSubmissionSession() []string { if len(group.LogURLs) == 0 { return make([]string, 0) } session := make([]string, 0) // modelling weighted random with exclusion unProcessedWeights := make(map[string]float32) for logURL, w := range group.LogWeights { unProcessedWeights[logURL] = w } group.wMu.RLock() defer group.wMu.RUnlock() for range group.LogURLs { sampleLog, err := weightedRandomSample(unProcessedWeights) if err != nil { // session still valid, not covering all Logs return session } session = append(session, sampleLog) delete(unProcessedWeights, sampleLog) } return session } // LogPolicyData contains info on log-partition and submission requirements // for a single cert. Key always matches value Name field. type LogPolicyData map[string]*LogGroupInfo // TotalLogs returns number of logs within set of Log-groups. // Taking possible intersection into account. func (groups LogPolicyData) TotalLogs() int { unifiedLogs := make(map[string]bool) for _, g := range groups { if g.IsBase { return len(g.LogURLs) } for l := range g.LogURLs { unifiedLogs[l] = true } } return len(unifiedLogs) } // CTPolicy interface describes requirements determined for logs in terms of // per-group-submit. type CTPolicy interface { // LogsByGroup provides info on Log-grouping. Returns an error if it's not // possible to satisfy the policy with the provided loglist. LogsByGroup(cert *x509.Certificate, approved *loglist3.LogList) (LogPolicyData, error) Name() string } // BaseGroupFor creates and propagates all-log group. func BaseGroupFor(approved *loglist3.LogList, incCount int) (*LogGroupInfo, error) { baseGroup := LogGroupInfo{Name: BaseName, IsBase: true} baseGroup.populate(approved, func(op *loglist3.Operator) bool { return true }) err := baseGroup.setMinInclusions(incCount) return &baseGroup, err } // lifetimeInMonths calculates and returns cert lifetime expressed in months // flooring incomplete month. func lifetimeInMonths(cert *x509.Certificate) int { startYear, startMonth, startDay := cert.NotBefore.Date() endYear, endMonth, endDay := cert.NotAfter.Date() lifetimeInMonths := (int(endYear)-int(startYear))*12 + (int(endMonth) - int(startMonth)) if endDay < startDay { // partial month lifetimeInMonths-- } return lifetimeInMonths } // GroupSet is set of Log-group names. type GroupSet map[string]bool // GroupByLogs reverses match-map between Logs and Groups. // Returns map from log-URLs to set of Group-names that contain the log. func GroupByLogs(lg LogPolicyData) map[string]GroupSet { result := make(map[string]GroupSet) for groupname, g := range lg { for logURL := range g.LogURLs { if _, seen := result[logURL]; !seen { result[logURL] = make(GroupSet) } result[logURL][groupname] = true } } return result } google-certificate-transparency-go-2308f62/ctpolicy/ctpolicy_test.go000066400000000000000000000131101462611535200256770ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "encoding/json" "reflect" "testing" "time" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) func getTestCertPEMShort() *x509.Certificate { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) cert.NotAfter = time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC) return cert } func getTestCertPEM2Years() *x509.Certificate { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) cert.NotAfter = time.Date(2014, 1, 1, 0, 0, 0, 0, time.UTC) return cert } func getTestCertPEM3Years() *x509.Certificate { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) cert.NotAfter = time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) return cert } func getTestCertPEMLongOriginal() *x509.Certificate { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM)) return cert } func sampleLogList(t *testing.T) *loglist3.LogList { t.Helper() var ll loglist3.LogList err := json.Unmarshal([]byte(testdata.SampleLogList3), &ll) if err != nil { t.Fatalf("Unable to Unmarshal testdata.SampleLogList3 %v", err) } return &ll } func TestLifetimeInMonths(t *testing.T) { tests := []struct { name string notBefore time.Time notAfter time.Time want int }{ { name: "ExactMonths", notBefore: time.Date(2012, 6, 1, 0, 0, 0, 0, time.UTC), notAfter: time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC), want: 7, }, { name: "ExactYears", notBefore: time.Date(2012, 6, 1, 0, 0, 0, 0, time.UTC), notAfter: time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), want: 31, }, { name: "PartialSingleMonth", notBefore: time.Date(2012, 6, 1, 0, 0, 0, 0, time.UTC), notAfter: time.Date(2012, 6, 25, 0, 0, 0, 0, time.UTC), want: 0, }, { name: "PartialMonths", notBefore: time.Date(2012, 6, 25, 0, 0, 0, 0, time.UTC), notAfter: time.Date(2013, 7, 1, 0, 0, 0, 0, time.UTC), want: 12, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { cert := getTestCertPEMLongOriginal() cert.NotBefore = test.notBefore cert.NotAfter = test.notAfter got := lifetimeInMonths(cert) if got != test.want { t.Errorf("lifetimeInMonths(%v, %v)=%d, want %d", test.notBefore, test.notAfter, got, test.want) } }) } } func TestGroupByLogs(t *testing.T) { tests := []struct { name string logGroups LogPolicyData want map[string]GroupSet }{ { name: "BaseGroup", logGroups: LogPolicyData{ BaseName: { Name: BaseName, LogURLs: map[string]bool{ "ct.googleapis.com/aviator/": true, "ct.googleapis.com/icarus/": true, "ct.googleapis.com/rocketeer/": true, "ct.googleapis.com/racketeer/": true, "log.bob.io": true, }, MinInclusions: 2, IsBase: true, }, }, want: map[string]GroupSet{ "ct.googleapis.com/aviator/": { BaseName: true, }, "ct.googleapis.com/icarus/": { BaseName: true, }, "ct.googleapis.com/rocketeer/": { BaseName: true, }, "ct.googleapis.com/racketeer/": { BaseName: true, }, "log.bob.io": { BaseName: true, }, }, }, { name: "ChromeLikeGroups", logGroups: LogPolicyData{ "Google-operated": { Name: "Google-operated", LogURLs: map[string]bool{ "ct.googleapis.com/aviator/": true, "ct.googleapis.com/icarus/": true, "ct.googleapis.com/rocketeer/": true, "ct.googleapis.com/racketeer/": true, }, MinInclusions: 2, IsBase: false, }, "Non-Google-operated": { Name: "Non-Google-operated", LogURLs: map[string]bool{ "log.bob.io": true, }, MinInclusions: 1, IsBase: false, }, BaseName: { Name: BaseName, LogURLs: map[string]bool{ "ct.googleapis.com/aviator/": true, "ct.googleapis.com/icarus/": true, "ct.googleapis.com/rocketeer/": true, "ct.googleapis.com/racketeer/": true, "log.bob.io": true, }, MinInclusions: 2, IsBase: true, }, }, want: map[string]GroupSet{ "ct.googleapis.com/aviator/": { BaseName: true, "Google-operated": true, }, "ct.googleapis.com/icarus/": { BaseName: true, "Google-operated": true, }, "ct.googleapis.com/rocketeer/": { BaseName: true, "Google-operated": true, }, "ct.googleapis.com/racketeer/": { BaseName: true, "Google-operated": true, }, "log.bob.io": { BaseName: true, "Non-Google-operated": true, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := GroupByLogs(test.logGroups) if !reflect.DeepEqual(got, test.want) { t.Errorf("GroupByLogs()=%v, want %v", got, test.want) } }) } } google-certificate-transparency-go-2308f62/ctpolicy/tools.go000066400000000000000000000026721462611535200241650ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "fmt" "math/rand" ) // weightedRandomSample picks an item from the weighted set and returns it. // Follows weight distribution provided and ignores items whose weight is 0. // If it's not possible (e.g. all items have 0 weights), returns error. // Expects all weights to be non-negative, otherwise returns error. func weightedRandomSample(weights map[string]float32) (string, error) { var sum float32 for itemName, w := range weights { if w < 0.0 { return "", fmt.Errorf("weightedRandomSample got negative weight %v for item %v, all weights should be non-negative", w, itemName) } sum += w } r := rand.Float32() * sum for itemName, w := range weights { if w == 0.0 { continue } r -= w if r < 0.0 { return itemName, nil } } return "", fmt.Errorf("weightedRandomSample couldn't pick any item") } google-certificate-transparency-go-2308f62/ctpolicy/tools_test.go000066400000000000000000000046121462611535200252200ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctpolicy import ( "testing" ) func TestWeightedRandomSampleDef(t *testing.T) { tests := []struct { name string weights map[string]float32 wantItem string wantErr bool }{ { name: "OneNegativeWeight", weights: map[string]float32{"a": 0.5, "b": -0.5, "c": 3.0}, wantItem: "", wantErr: true, }, { name: "AllZeroWeights", weights: map[string]float32{"a": 0.0, "b": 0.0, "c": 0.0}, wantItem: "", wantErr: true, }, { name: "OneNonZeroWeights", weights: map[string]float32{"a": 0.0, "b": 4.0, "c": 0.0}, wantItem: "b", wantErr: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { gotItem, err := weightedRandomSample(tc.weights) if gotErr := err != nil; gotErr != tc.wantErr { t.Fatalf("weightedRandomSample(%v) = (_, error: %v), want err? %t", tc.weights, err, tc.wantErr) } if gotItem != tc.wantItem { t.Errorf("weightedRandomSample(%v) = (%q, _) want %q", tc.weights, gotItem, tc.wantItem) } }) } } func TestWeightedRandomSampleInDef(t *testing.T) { tests := []struct { name string weights map[string]float32 wantOneOf []string wantErr bool }{ { name: "TwoWeights", weights: map[string]float32{"a": 0.5, "b": 0.0, "c": 3.0}, wantOneOf: []string{"a", "c"}, wantErr: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { gotItem, err := weightedRandomSample(tc.weights) if gotErr := err != nil; gotErr != tc.wantErr { t.Fatalf("weightedRandomSample(%v) = (_, error: %v), want err? %t", tc.weights, err, tc.wantErr) } for _, i := range tc.wantOneOf { if i == gotItem { return } } t.Errorf("weightedRandomSample(%v) = (%q, _) want any item of %v", tc.weights, gotItem, tc.wantOneOf) }) } } google-certificate-transparency-go-2308f62/ctutil/000077500000000000000000000000001462611535200221455ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/ctutil/ctutil.go000066400000000000000000000217621462611535200240100ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package ctutil contains utilities for Certificate Transparency. package ctutil import ( "bytes" "crypto" "crypto/sha256" "encoding/base64" "errors" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) var emptyHash = [sha256.Size]byte{} // LeafHashB64 does as LeafHash does, but returns the leaf hash base64-encoded. // The base64-encoded leaf hash returned by B64LeafHash can be used with the // get-proof-by-hash API endpoint of Certificate Transparency Logs. func LeafHashB64(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (string, error) { hash, err := LeafHash(chain, sct, embedded) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(hash[:]), nil } // LeafHash calculates the leaf hash of the certificate or precertificate at // chain[0] that sct was issued for. // // sct is required because the SCT timestamp is used to calculate the leaf hash. // Leaf hashes are unique to (pre)certificate-SCT pairs. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to calculate the leaf hash for a normal X.509 // certificate then it is enough to just provide the end entity // certificate in chain. This case assumes that the SCT being provided is // not embedded within the leaf certificate provided, i.e. the certificate // is what was submitted to the Certificate Transparency Log in order to // obtain the SCT. For this case, set embedded to false. // - Precertificate: // If using this function to calculate the leaf hash for a precertificate // then the issuing certificate must also be provided in chain. The // precertificate should be at chain[0], and its issuer at chain[1]. For // this case, set embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If using this function to calculate the leaf hash for a certificate // where the SCT provided is embedded within the certificate you // are providing at chain[0], set embedded to true. LeafHash will // calculate the leaf hash by building the corresponding precertificate. // LeafHash will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. // // Note: LeafHash doesn't check that the provided SCT verifies for the given // chain. It simply calculates what the leaf hash would be for the given // (pre)certificate-SCT pair. func LeafHash(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) ([sha256.Size]byte, error) { leaf, err := createLeaf(chain, sct, embedded) if err != nil { return emptyHash, err } return ct.LeafHashForLeaf(leaf) } // VerifySCT takes the public key of a Certificate Transparency Log, a // certificate chain, and an SCT and verifies whether the SCT is a valid SCT for // the certificate at chain[0], signed by the Log that the public key belongs // to. If the SCT does not verify, an error will be returned. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to verify an SCT for a normal X.509 certificate // then it is enough to just provide the end entity certificate in chain. // This case assumes that the SCT being provided is not embedded within // the leaf certificate provided, i.e. the certificate is what was // submitted to the Certificate Transparency Log in order to obtain the // SCT. For this case, set embedded to false. // - Precertificate: // If using this function to verify an SCT for a precertificate then the // issuing certificate must also be provided in chain. The precertificate // should be at chain[0], and its issuer at chain[1]. For this case, set // embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If the SCT you wish to verify is embedded within the certificate you // are providing at chain[0], set embedded to true. VerifySCT will // verify the provided SCT by building the corresponding precertificate. // VerifySCT will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. func VerifySCT(pubKey crypto.PublicKey, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error { s, err := ct.NewSignatureVerifier(pubKey) if err != nil { return fmt.Errorf("error creating signature verifier: %s", err) } return VerifySCTWithVerifier(s, chain, sct, embedded) } // VerifySCTWithVerifier takes a ct.SignatureVerifier, a certificate chain, and // an SCT and verifies whether the SCT is a valid SCT for the certificate at // chain[0], signed by the Log whose public key was used to set up the // ct.SignatureVerifier. If the SCT does not verify, an error will be returned. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to verify an SCT for a normal X.509 certificate // then it is enough to just provide the end entity certificate in chain. // This case assumes that the SCT being provided is not embedded within // the leaf certificate provided, i.e. the certificate is what was // submitted to the Certificate Transparency Log in order to obtain the // SCT. For this case, set embedded to false. // - Precertificate: // If using this function to verify an SCT for a precertificate then the // issuing certificate must also be provided in chain. The precertificate // should be at chain[0], and its issuer at chain[1]. For this case, set // embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If the SCT you wish to verify is embedded within the certificate you // are providing at chain[0], set embedded to true. VerifySCT will // verify the provided SCT by building the corresponding precertificate. // VerifySCT will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. func VerifySCTWithVerifier(sv *ct.SignatureVerifier, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error { if sv == nil { return errors.New("ct.SignatureVerifier is nil") } leaf, err := createLeaf(chain, sct, embedded) if err != nil { return err } return sv.VerifySCTSignature(*sct, ct.LogEntry{Leaf: *leaf}) } func createLeaf(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (*ct.MerkleTreeLeaf, error) { if len(chain) == 0 { return nil, errors.New("chain is empty") } if sct == nil { return nil, errors.New("sct is nil") } if embedded { sctPresent, err := ContainsSCT(chain[0], sct) if err != nil { return nil, fmt.Errorf("error checking for SCT in leaf certificate: %s", err) } if !sctPresent { return nil, errors.New("SCT provided is not embedded within leaf certificate") } } certType := ct.X509LogEntryType if chain[0].IsPrecertificate() || embedded { certType = ct.PrecertLogEntryType } var leaf *ct.MerkleTreeLeaf var err error if embedded { leaf, err = ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp) } else { leaf, err = ct.MerkleTreeLeafFromChain(chain, certType, sct.Timestamp) } if err != nil { return nil, fmt.Errorf("error creating MerkleTreeLeaf: %s", err) } return leaf, nil } // ContainsSCT checks to see whether the given SCT is embedded within the given // certificate. func ContainsSCT(cert *x509.Certificate, sct *ct.SignedCertificateTimestamp) (bool, error) { if cert == nil || sct == nil { return false, nil } sctBytes, err := tls.Marshal(*sct) if err != nil { return false, fmt.Errorf("error tls.Marshalling SCT: %s", err) } for _, s := range cert.SCTList.SCTList { if bytes.Equal(sctBytes, s.Val) { return true, nil } } return false, nil } google-certificate-transparency-go-2308f62/ctutil/ctutil_test.go000066400000000000000000000223121462611535200250370ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctutil import ( "encoding/base64" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509util" ) func TestLeafHash(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool want string }{ { desc: "cert", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, want: testdata.TestCertB64LeafHash, }, { desc: "precert", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, want: testdata.TestPreCertB64LeafHash, }, { desc: "cert with embedded SCT", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, want: testdata.TestPreCertB64LeafHash, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test LeafHash() wantSl, err := base64.StdEncoding.DecodeString(test.want) if err != nil { t.Fatalf("error base64-decoding leaf hash %q: %s", test.want, err) } var want [32]byte copy(want[:], wantSl) got, err := LeafHash(chain, &sct, test.embedded) if got != want || err != nil { t.Errorf("LeafHash(_,_) = %v, %v, want %v, nil", got, err, want) } // Test LeafHashB64() gotB64, err := LeafHashB64(chain, &sct, test.embedded) if gotB64 != test.want || err != nil { t.Errorf("LeafHashB64(_,_) = %v, %v, want %v, nil", gotB64, err, test.want) } }) } } func TestLeafHashErrors(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool }{ { desc: "empty chain", chainPEM: "", sct: testdata.TestCertProof, }, { desc: "nil SCT", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: nil, }, { desc: "no SCTs embedded in cert, embedded true", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, }, { desc: "cert contains embedded SCTs, but not the SCT provided", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct *ct.SignedCertificateTimestamp if test.sct != nil { sct = &ct.SignedCertificateTimestamp{} if _, err = tls.Unmarshal(test.sct, sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } } // Test LeafHash() got, err := LeafHash(chain, sct, test.embedded) if got != emptyHash || err == nil { t.Errorf("LeafHash(_,_) = %s, %v, want %v, error", got, err, emptyHash) } // Test LeafHashB64() gotB64, err := LeafHashB64(chain, sct, test.embedded) if gotB64 != "" || err == nil { t.Errorf("LeafHashB64(_,_) = %s, %v, want \"\", error", gotB64, err) } }) } } func TestVerifySCT(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool wantErr bool }{ { desc: "cert", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, }, { desc: "precert", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, }, { desc: "invalid SCT", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert with embedded SCT", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, }, { desc: "cert with invalid embedded SCT", chainPEM: testdata.TestInvalidEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test VerifySCT() pk, err := ct.PublicKeyFromB64(testdata.LogPublicKeyB64) if err != nil { t.Errorf("error parsing public key: %s", err) } err = VerifySCT(pk, chain, &sct, test.embedded) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("VerifySCT(_,_,_, %t) = %v, want error? %t", test.embedded, err, test.wantErr) } }) } } func TestVerifySCTWithVerifier(t *testing.T) { // Parse public key pk, err := ct.PublicKeyFromB64(testdata.LogPublicKeyB64) if err != nil { t.Errorf("error parsing public key: %s", err) } // Create signature verifier sv, err := ct.NewSignatureVerifier(pk) if err != nil { t.Errorf("couldn't create signature verifier: %s", err) } tests := []struct { desc string sv *ct.SignatureVerifier chainPEM string sct []byte embedded bool wantErr bool }{ { desc: "nil signature verifier", sv: nil, chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert", sv: sv, chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, }, { desc: "precert", sv: sv, chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, }, { desc: "invalid SCT", sv: sv, chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert with embedded SCT", sv: sv, chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, }, { desc: "cert with invalid embedded SCT", sv: sv, chainPEM: testdata.TestInvalidEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test VerifySCTWithVerifier() err = VerifySCTWithVerifier(test.sv, chain, &sct, test.embedded) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("VerifySCT(_,_,_, %t) = %v, want error? %t", test.embedded, err, test.wantErr) } }) } } func TestContainsSCT(t *testing.T) { tests := []struct { desc string certPEM string sct []byte want bool }{ { desc: "cert doesn't contain any SCTs", certPEM: testdata.TestCertPEM, sct: testdata.TestPreCertProof, want: false, }, { desc: "cert contains SCT but not specified SCT", certPEM: testdata.TestEmbeddedCertPEM, sct: testdata.TestInvalidProof, want: false, }, { desc: "cert contains SCT", certPEM: testdata.TestEmbeddedCertPEM, sct: testdata.TestPreCertProof, want: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse cert cert, err := x509util.CertificateFromPEM([]byte(test.certPEM)) if err != nil { t.Fatalf("error parsing certificate: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test ContainsSCT() got, err := ContainsSCT(cert, &sct) if err != nil { t.Fatalf("ContainsSCT(_,_) = false, %s, want no error", err) } if got != test.want { t.Errorf("ContainsSCT(_,_) = %t, nil, want %t, nil", got, test.want) } }) } } google-certificate-transparency-go-2308f62/ctutil/fuzz_test.go000066400000000000000000000023371462611535200245360ustar00rootroot00000000000000// Copyright 2024 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctutil import ( "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "testing" ) func FuzzVerifySCTTest(f *testing.F) { f.Fuzz(func(t *testing.T, publicKey, certData []byte) { cert, err := x509.ParseCertificate(certData) if err != nil { t.Skip() } scts, err := x509util.ParseSCTsFromCertificate(cert.Raw) if err != nil { t.Skip() } chain, err := x509.ParseCertificates(cert.Raw) if err != nil { t.Skip() } pub, err := x509.ParsePKIXPublicKey(publicKey) if err != nil { t.Skip() } _ = VerifySCT(pub, chain, scts[0], true) }) } google-certificate-transparency-go-2308f62/ctutil/loginfo.go000066400000000000000000000142421462611535200241340ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctutil import ( "context" "crypto/sha256" "fmt" "net/http" "strings" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" ) // LogInfo holds the objects needed to perform per-log verification and // validation of SCTs. type LogInfo struct { Description string Client client.CheckLogClient MMD time.Duration Verifier *ct.SignatureVerifier PublicKey []byte mu sync.RWMutex lastSTH *ct.SignedTreeHead } // NewLogInfo builds a LogInfo object based on a log list entry. func NewLogInfo(log *loglist3.Log, hc *http.Client) (*LogInfo, error) { url := log.URL if !strings.HasPrefix(url, "https://") { url = "https://" + url } lc, err := client.New(url, hc, jsonclient.Options{PublicKeyDER: log.Key, UserAgent: "ct-go-logclient"}) if err != nil { return nil, fmt.Errorf("failed to create client for log %q: %v", log.Description, err) } return newLogInfo(log, lc) } func newLogInfo(log *loglist3.Log, lc client.CheckLogClient) (*LogInfo, error) { logKey, err := x509.ParsePKIXPublicKey(log.Key) if err != nil { return nil, fmt.Errorf("failed to parse public key data for log %q: %v", log.Description, err) } verifier, err := ct.NewSignatureVerifier(logKey) if err != nil { return nil, fmt.Errorf("failed to build verifier log %q: %v", log.Description, err) } mmd := time.Duration(log.MMD) * time.Second return &LogInfo{ Description: log.Description, Client: lc, MMD: mmd, Verifier: verifier, PublicKey: log.Key, }, nil } // LogInfoByHash holds LogInfo objects index by the SHA-256 hash of the log's public key. type LogInfoByHash map[[sha256.Size]byte]*LogInfo // LogInfoByKeyHash builds a map of LogInfo objects indexed by their key hashes. func LogInfoByKeyHash(ll *loglist3.LogList, hc *http.Client) (LogInfoByHash, error) { return logInfoByKeyHash(ll, hc, NewLogInfo) } func logInfoByKeyHash(ll *loglist3.LogList, hc *http.Client, infoFactory func(*loglist3.Log, *http.Client) (*LogInfo, error)) (map[[sha256.Size]byte]*LogInfo, error) { result := make(map[[sha256.Size]byte]*LogInfo) for _, operator := range ll.Operators { for _, log := range operator.Logs { h := sha256.Sum256(log.Key) li, err := infoFactory(log, hc) if err != nil { return nil, err } result[h] = li } } return result, nil } // LastSTH returns the last STH known for the log. func (li *LogInfo) LastSTH() *ct.SignedTreeHead { li.mu.RLock() defer li.mu.RUnlock() return li.lastSTH } // SetSTH sets the last STH known for the log. func (li *LogInfo) SetSTH(sth *ct.SignedTreeHead) { li.mu.Lock() defer li.mu.Unlock() li.lastSTH = sth } // VerifySCTSignature checks the signature in the SCT matches the given leaf (adjusted for the // timestamp in the SCT) and log. func (li *LogInfo) VerifySCTSignature(sct ct.SignedCertificateTimestamp, leaf ct.MerkleTreeLeaf) error { leaf.TimestampedEntry.Timestamp = sct.Timestamp if err := li.Verifier.VerifySCTSignature(sct, ct.LogEntry{Leaf: leaf}); err != nil { return fmt.Errorf("failed to verify SCT signature from log %q: %v", li.Description, err) } return nil } // VerifyInclusionLatest checks that the given Merkle tree leaf, adjusted for the provided timestamp, // is present in the latest known tree size of the log. If no tree size for the log is known, it will // be queried. On success, returns the index of the leaf in the log. func (li *LogInfo) VerifyInclusionLatest(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp uint64) (int64, error) { sth := li.LastSTH() if sth == nil { var err error sth, err = li.Client.GetSTH(ctx) if err != nil { return -1, fmt.Errorf("failed to get current STH for %q log: %v", li.Description, err) } li.SetSTH(sth) } return li.VerifyInclusionAt(ctx, leaf, timestamp, sth.TreeSize, sth.SHA256RootHash[:]) } // VerifyInclusion checks that the given Merkle tree leaf, adjusted for the provided timestamp, // is present in the current tree size of the log. On success, returns the index of the leaf // in the log. func (li *LogInfo) VerifyInclusion(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp uint64) (int64, error) { sth, err := li.Client.GetSTH(ctx) if err != nil { return -1, fmt.Errorf("failed to get current STH for %q log: %v", li.Description, err) } li.SetSTH(sth) return li.VerifyInclusionAt(ctx, leaf, timestamp, sth.TreeSize, sth.SHA256RootHash[:]) } // VerifyInclusionAt checks that the given Merkle tree leaf, adjusted for the provided timestamp, // is present in the given tree size & root hash of the log. On success, returns the index of the // leaf in the log. func (li *LogInfo) VerifyInclusionAt(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp, treeSize uint64, rootHash []byte) (int64, error) { leaf.TimestampedEntry.Timestamp = timestamp leafHash, err := ct.LeafHashForLeaf(&leaf) if err != nil { return -1, fmt.Errorf("failed to create leaf hash: %v", err) } rsp, err := li.Client.GetProofByHash(ctx, leafHash[:], treeSize) if err != nil { return -1, fmt.Errorf("failed to GetProofByHash(sct,size=%d): %v", treeSize, err) } if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(rsp.LeafIndex), treeSize, leafHash[:], rsp.AuditPath, rootHash); err != nil { return -1, fmt.Errorf("failed to verify inclusion proof at size %d: %v", treeSize, err) } return rsp.LeafIndex, nil } google-certificate-transparency-go-2308f62/ctutil/sctcheck/000077500000000000000000000000001462611535200237345ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/ctutil/sctcheck/sctcheck.go000066400000000000000000000215771462611535200260660ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // sctcheck is a utility to show and check embedded SCTs (Signed Certificate // Timestamps) in certificates. package main import ( "bytes" "context" "crypto/tls" "errors" "flag" "fmt" "net" "net/http" "net/url" "os" "strings" "time" "github.com/google/certificate-transparency-go/ctutil" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) var ( logList = flag.String("log_list", loglist3.AllLogListURL, "Location of master CT log list (URL or filename)") deadline = flag.Duration("deadline", 30*time.Second, "Timeout deadline for HTTP requests") checkInclusion = flag.Bool("check_inclusion", true, "Whether to check SCT inclusion in issuing CT log") ) type logInfoFactory func(*loglist3.Log, *http.Client) (*ctutil.LogInfo, error) func main() { klog.InitFlags(nil) flag.Parse() ctx := context.Background() hc := &http.Client{Timeout: *deadline} llData, err := x509util.ReadFileOrURL(*logList, hc) if err != nil { klog.Exitf("Failed to read log list: %v", err) } ll, err := loglist3.NewFromJSON(llData) if err != nil { klog.Exitf("Failed to parse log list: %v", err) } lf := ctutil.NewLogInfo totalInvalid := 0 for _, arg := range flag.Args() { var chain []*x509.Certificate var valid, invalid int if strings.HasPrefix(arg, "https://") { // Get chain served online for TLS connection to site, and check any SCTs // provided alongside on the connection along the way. chain, valid, invalid, err = getAndCheckSiteChain(ctx, lf, arg, ll, hc) if err != nil { klog.Errorf("%s: failed to get cert chain: %v", arg, err) continue } klog.Errorf("Found %d external SCTs for %q, of which %d were validated", valid+invalid, arg, valid) totalInvalid += invalid } else { // Treat the argument as a certificate file to load. data, err := os.ReadFile(arg) if err != nil { klog.Errorf("%s: failed to read data: %v", arg, err) continue } chain, err = x509util.CertificatesFromPEM(data) if err != nil { klog.Errorf("%s: failed to read cert data: %v", arg, err) continue } } if len(chain) == 0 { klog.Errorf("%s: no certificates found", arg) continue } // Check the chain for embedded SCTs. valid, invalid = checkChain(ctx, lf, chain, ll, hc) klog.Errorf("Found %d embedded SCTs for %q, of which %d were validated", valid+invalid, arg, valid) totalInvalid += invalid } if totalInvalid > 0 { os.Exit(1) } } // checkChain iterates over any embedded SCTs in the leaf certificate of the chain // and checks those SCTs. Returns the counts of valid and invalid embedded SCTs found. func checkChain(ctx context.Context, lf logInfoFactory, chain []*x509.Certificate, ll *loglist3.LogList, hc *http.Client) (int, int) { leaf := chain[0] if len(leaf.SCTList.SCTList) == 0 { return 0, 0 } var issuer *x509.Certificate for i := 1; i < len(chain); i++ { c := chain[i] if bytes.Equal(c.RawSubject, leaf.RawIssuer) && c.CheckSignature(leaf.SignatureAlgorithm, leaf.RawTBSCertificate, leaf.Signature) == nil { issuer = c if i > 1 { klog.Warningf("Certificate chain out of order; issuer cert found at index %d", i) } break } } if issuer == nil { klog.Info("No issuer in chain; attempting online retrieval") var err error issuer, err = x509util.GetIssuer(leaf, hc) if err != nil { klog.Errorf("Failed to get issuer online: %v", err) } } // Build a Merkle leaf that corresponds to the embedded SCTs. We can use the same // leaf for all of the SCTs, as long as the timestamp field gets updated. merkleLeaf, err := ct.MerkleTreeLeafForEmbeddedSCT([]*x509.Certificate{leaf, issuer}, 0) if err != nil { klog.Errorf("Failed to build Merkle leaf: %v", err) return 0, len(leaf.SCTList.SCTList) } var valid, invalid int for i, sctData := range leaf.SCTList.SCTList { subject := fmt.Sprintf("embedded SCT[%d]", i) if checkSCT(ctx, lf, subject, merkleLeaf, &sctData, ll, hc) { valid++ } else { invalid++ } } return valid, invalid } // getAndCheckSiteChain retrieves and returns the chain of certificates presented // for an HTTPS site. Along the way it checks any external SCTs that are served // up on the connection alongside the chain. Returns the chain and counts of // valid and invalid external SCTs found. func getAndCheckSiteChain(ctx context.Context, lf logInfoFactory, target string, ll *loglist3.LogList, hc *http.Client) ([]*x509.Certificate, int, int, error) { u, err := url.Parse(target) if err != nil { return nil, 0, 0, fmt.Errorf("failed to parse URL: %v", err) } if u.Scheme != "https" { return nil, 0, 0, errors.New("non-https URL provided") } host := u.Host if !strings.Contains(host, ":") { host += ":443" } klog.Infof("Retrieve certificate chain from TLS connection to %q", host) dialer := net.Dialer{Timeout: hc.Timeout} // Insecure TLS connection here so we can always proceed. conn, err := tls.DialWithDialer(&dialer, "tcp", host, &tls.Config{InsecureSkipVerify: true}) if err != nil { return nil, 0, 0, fmt.Errorf("failed to dial %q: %v", host, err) } defer func() { if err := conn.Close(); err != nil { klog.Errorf("conn.Close()=%q", err) } }() goChain := conn.ConnectionState().PeerCertificates klog.Infof("Found chain of length %d", len(goChain)) // Convert base crypto/x509.Certificates to our forked x509.Certificate type. chain := make([]*x509.Certificate, len(goChain)) for i, goCert := range goChain { cert, err := x509.ParseCertificate(goCert.Raw) if err != nil { return nil, 0, 0, fmt.Errorf("failed to convert Go Certificate [%d]: %v", i, err) } chain[i] = cert } // Check externally-provided SCTs. var valid, invalid int scts := conn.ConnectionState().SignedCertificateTimestamps if len(scts) > 0 { merkleLeaf, err := ct.MerkleTreeLeafFromChain(chain, ct.X509LogEntryType, 0 /* timestamp added later */) if err != nil { klog.Errorf("Failed to build Merkle tree leaf: %v", err) return chain, 0, len(scts), nil } for i, sctData := range scts { subject := fmt.Sprintf("external SCT[%d]", i) if checkSCT(ctx, lf, subject, merkleLeaf, &x509.SerializedSCT{Val: sctData}, ll, hc) { valid++ } else { invalid++ } } } return chain, valid, invalid, nil } // checkSCT performs checks on an SCT and Merkle tree leaf, performing both // signature validation and online log inclusion checking. Returns whether // the SCT is valid. func checkSCT(ctx context.Context, liFactory logInfoFactory, subject string, merkleLeaf *ct.MerkleTreeLeaf, sctData *x509.SerializedSCT, ll *loglist3.LogList, hc *http.Client) bool { sct, err := x509util.ExtractSCT(sctData) if err != nil { klog.Errorf("Failed to deserialize %s data: %v", subject, err) klog.Errorf("Data: %x", sctData.Val) return false } klog.Infof("Examine %s with timestamp: %d (%v) from logID: %x", subject, sct.Timestamp, ct.TimestampToTime(sct.Timestamp), sct.LogID.KeyID[:]) log := ll.FindLogByKeyHash(sct.LogID.KeyID) if log == nil { klog.Warningf("Unknown logID: %x, cannot validate %s", sct.LogID, subject) return false } logInfo, err := liFactory(log, hc) if err != nil { klog.Errorf("Failed to build log info for %q log: %v", log.Description, err) return false } result := true klog.Infof("Validate %s against log %q...", subject, logInfo.Description) if err := logInfo.VerifySCTSignature(*sct, *merkleLeaf); err != nil { klog.Errorf("Failed to verify %s signature from log %q: %v", subject, log.Description, err) result = false } else { klog.Infof("Validate %s against log %q... validated", subject, log.Description) } if *checkInclusion { klog.Infof("Check %s inclusion against log %q...", subject, log.Description) index, err := logInfo.VerifyInclusion(ctx, *merkleLeaf, sct.Timestamp) if err != nil { age := time.Since(ct.TimestampToTime(sct.Timestamp)) if age < logInfo.MMD { klog.Warningf("Failed to verify inclusion proof (%v) but %s timestamp is only %v old, less than log's MMD of %d seconds", err, subject, age, log.MMD) } else { klog.Errorf("Failed to verify inclusion proof for %s: %v", subject, err) } return false } klog.Infof("Check %s inclusion against log %q... included at %d", subject, log.Description, index) } return result } google-certificate-transparency-go-2308f62/ctutil/sctscan/000077500000000000000000000000001462611535200236035ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/ctutil/sctscan/sctscan.go000066400000000000000000000154201462611535200255720ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // sctscan is a utility to scan a CT log and check embedded SCTs (Signed Certificate // Timestamps) in certificates in the log. package main import ( "context" "crypto/sha256" "flag" "net/http" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/ctutil" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) var ( logURI = flag.String("log_uri", "https://ct.googleapis.com/pilot", "CT log base URI") logList = flag.String("log_list", loglist3.AllLogListURL, "Location of master CT log list (URL or filename)") inclusion = flag.Bool("inclusion", false, "Whether to do inclusion checking") deadline = flag.Duration("deadline", 30*time.Second, "Timeout deadline for HTTP requests") batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries") numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers") parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches") startIndex = flag.Int64("start_index", 0, "Log index to start scanning at") ) func main() { klog.InitFlags(nil) flag.Parse() ctx := context.Background() klog.CopyStandardLogTo("WARNING") hc := &http.Client{ Timeout: *deadline, Transport: &http.Transport{ TLSHandshakeTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } logClient, err := client.New(*logURI, hc, jsonclient.Options{UserAgent: "ct-go-sctscan/1.0"}) if err != nil { klog.Exitf("Failed to create log client: %v", err) } llData, err := x509util.ReadFileOrURL(*logList, hc) if err != nil { klog.Exitf("Failed to read log list: %v", err) } ll, err := loglist3.NewFromJSON(llData) if err != nil { klog.Exitf("Failed to parse log list: %v", err) } klog.Warning("Performing validations via direct log queries") logsByHash, err := ctutil.LogInfoByKeyHash(ll, hc) if err != nil { klog.Exitf("Failed to build log info map: %v", err) } scanOpts := scanner.ScannerOptions{ FetcherOptions: scanner.FetcherOptions{ BatchSize: *batchSize, ParallelFetch: *parallelFetch, StartIndex: *startIndex, }, Matcher: EmbeddedSCTMatcher{}, NumWorkers: *numWorkers, } s := scanner.NewScanner(logClient, scanOpts) if err := s.Scan(ctx, func(entry *ct.RawLogEntry) { checkCertWithEmbeddedSCT(ctx, logsByHash, *inclusion, entry) }, func(entry *ct.RawLogEntry) { klog.Errorf("Internal error: found pre-cert! %+v", entry) }); err != nil { klog.Exitf("Scan failed: %v", err) } } // EmbeddedSCTMatcher implements the scanner.Matcher interface by matching just certificates // that have embedded SCTs. type EmbeddedSCTMatcher struct{} // CertificateMatches identifies certificates in the log that have the SCTList extension. func (e EmbeddedSCTMatcher) CertificateMatches(cert *x509.Certificate) bool { return len(cert.SCTList.SCTList) > 0 } // PrecertificateMatches identifies those precertificates in the log that are of // interest: none. func (e EmbeddedSCTMatcher) PrecertificateMatches(*ct.Precertificate) bool { return false } // checkCertWithEmbeddedSCT is the callback that the scanner invokes for each cert found by the matcher. // Here, we only expect to get certificates that have embedded SCT lists. func checkCertWithEmbeddedSCT(ctx context.Context, logsByKey map[[sha256.Size]byte]*ctutil.LogInfo, checkInclusion bool, rawEntry *ct.RawLogEntry) { entry, err := rawEntry.ToLogEntry() if x509.IsFatal(err) { klog.Errorf("[%d] Internal error: failed to parse cert in entry: %v", rawEntry.Index, err) return } leaf := entry.X509Cert if leaf == nil { klog.Errorf("[%d] Internal error: no cert in entry", entry.Index) return } if len(entry.Chain) == 0 { klog.Errorf("[%d] No issuance chain found", entry.Index) return } issuer, err := x509.ParseCertificate(entry.Chain[0].Data) if err != nil { klog.Errorf("[%d] Failed to parse issuer: %v", entry.Index, err) } // Build a Merkle leaf that corresponds to the embedded SCTs. We can use the same // leaf for all of the SCTs, as long as the timestamp field gets updated. merkleLeaf, err := ct.MerkleTreeLeafForEmbeddedSCT([]*x509.Certificate{leaf, issuer}, 0) if err != nil { klog.Errorf("[%d] Failed to build Merkle leaf: %v", entry.Index, err) return } for i, sctData := range leaf.SCTList.SCTList { sct, err := x509util.ExtractSCT(&sctData) if err != nil { klog.Errorf("[%d] Failed to deserialize SCT[%d] data: %v", entry.Index, i, err) continue } logInfo := logsByKey[sct.LogID.KeyID] if logInfo == nil { klog.Infof("[%d] SCT[%d] for unknown logID: %x, cannot validate SCT", entry.Index, i, sct.LogID) continue } if err := logInfo.VerifySCTSignature(*sct, *merkleLeaf); err != nil { klog.Errorf("[%d] Failed to verify SCT[%d] signature from log %q: %v", entry.Index, i, logInfo.Description, err) } else { klog.V(1).Infof("[%d] Verified SCT[%d] against log %q", entry.Index, i, logInfo.Description) } if !checkInclusion { continue } if index, err := logInfo.VerifyInclusionLatest(ctx, *merkleLeaf, sct.Timestamp); err != nil { // Inclusion failure may be OK if the SCT is within the Log's MMD sth := logInfo.LastSTH() if sth != nil { delta := time.Duration(sth.Timestamp-sct.Timestamp) * time.Millisecond if delta < logInfo.MMD { klog.Warningf("[%d] Failed to verify SCT[%d] inclusion proof (%v), but Log's MMD has not passed %d -> %d < %v", entry.Index, i, err, sct.Timestamp, sth.Timestamp, logInfo.MMD) continue } } klog.Errorf("[%d] Failed to verify SCT[%d] inclusion proof: %v", entry.Index, i, err) } else { klog.V(1).Infof("[%d] Checked SCT[%d] inclusion against log %q, at index %d", entry.Index, i, logInfo.Description, index) } } } google-certificate-transparency-go-2308f62/fixchain/000077500000000000000000000000001462611535200224325ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/fixchain/chainfix/000077500000000000000000000000001462611535200242235ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/fixchain/chainfix/chainfix.go000066400000000000000000000073501462611535200263500ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // chainfix is a utility program for fixing the validation chains for certificates. package main import ( "context" "crypto/sha256" "encoding/base64" "encoding/json" "io" "log" "net/http" "os" "sync" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/fixchain" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/x509" "golang.org/x/time/rate" ) // Assumes chains to be stores in a file in JSON encoded with the certificates // in DER format. func processChains(file string, fl *fixchain.FixAndLog) { f, err := os.Open(file) if err != nil { log.Fatalf("Can't open %q: %s", file, err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("Can't close file: %v", err) } }() type Chain struct { Chain [][]byte } dec := json.NewDecoder(f) for { var m Chain if err := dec.Decode(&m); err == io.EOF { break } else if err != nil { log.Fatal(err) } var chain []*x509.Certificate for _, derBytes := range m.Chain { cert, err := x509.ParseCertificate(derBytes) if x509.IsFatal(err) { log.Fatalf("can't parse certificate: %s %#v", err, derBytes) } chain = append(chain, cert) } fl.QueueAllCertsInChain(chain) } } // A simple function to save the FixErrors that are spat out by the FixAndLog to // a directory. contentStore() is the function to alter to store the errors // wherever/however they need to be stored. Both logStringErrors() and // logJSONErrors() use this function as a way of storing the resulting // FixErrors. func contentStore(baseDir string, subDir string, content []byte) { r := sha256.Sum256(content) h := base64.URLEncoding.EncodeToString(r[:]) d := baseDir + "/" + subDir if err := os.MkdirAll(d, 0777); err != nil { log.Fatalf("Can't create directories %q: %v", d, err) } fn := d + "/" + h f, err := os.Create(fn) if err != nil { log.Fatalf("Can't create %q: %s", fn, err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("Can't close file: %v", err) } }() if _, err := f.Write(content); err != nil { log.Fatalf("Can't write to %q: %v", fn, err) } } func logStringErrors(wg *sync.WaitGroup, errors chan *fixchain.FixError, baseDir string) { defer wg.Done() for err := range errors { contentStore(baseDir, err.TypeString(), []byte(err.String())) } } func main() { ctx := context.Background() logURL := os.Args[1] chainsFile := os.Args[2] errDir := os.Args[3] var wg sync.WaitGroup wg.Add(1) errors := make(chan *fixchain.FixError) // Functions to log errors as strings or as JSON are provided. // As-is, this will log errors as strings. go logStringErrors(&wg, errors, errDir) limiter := rate.NewLimiter(rate.Limit(1000), 1) c := &http.Client{} logClient, err := client.New(logURL, c, jsonclient.Options{UserAgent: "ct-go-fixchain/1.0"}) if err != nil { log.Fatalf("failed to create log client: %v", err) } fl := fixchain.NewFixAndLog(ctx, 100, 100, errors, c, logClient, limiter, true) processChains(chainsFile, fl) log.Printf("Wait for fixers and loggers") fl.Wait() close(errors) log.Printf("Wait for errors") wg.Wait() } google-certificate-transparency-go-2308f62/fixchain/chains_test.go000066400000000000000000000433301462611535200252700ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain // Chain 1: const googleLeaf = `-----BEGIN CERTIFICATE----- MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L 05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0 ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5 u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6 z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw== -----END CERTIFICATE----- ` const thawteIntermediate = `-----BEGIN CERTIFICATE----- MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g 5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo 3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv -----END CERTIFICATE----- ` const verisignRoot = `-----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k -----END CERTIFICATE----- ` // Chain 2: const megaLeaf = `-----BEGIN CERTIFICATE----- MIIFOjCCBCKgAwIBAgIQWYE8Dup170kZ+k11Lg51OjANBgkqhkiG9w0BAQUFADBy MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEYMBYGA1UE AxMPRXNzZW50aWFsU1NMIENBMB4XDTEyMTIxNDAwMDAwMFoXDTE0MTIxNDIzNTk1 OVowfzEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMS4wLAYDVQQL EyVIb3N0ZWQgYnkgSW5zdHJhIENvcnBvcmF0aW9uIFB0eS4gTFREMRUwEwYDVQQL EwxFc3NlbnRpYWxTU0wxEzARBgNVBAMTCm1lZ2EuY28ubnowggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDcxMCClae8BQIaJHBUIVttlLvhbK4XhXPk3RQ3 G5XA6tLZMBQ33l3F9knYJ0YErXtr8IdfYoulRQFmKFMJl9GtWyg4cGQi2Rcr5VN5 S5dA1vu4oyJBxE9fPELcK6Yz1vqaf+n6za+mYTiQYKggVdS8/s8hmNuXP9Zk1pIn +q0pGsf8NAcSHMJgLqPQrTDw+zae4V03DvcYfNKjuno88d2226ld7MAmQZ7uRNsI /CnkdelVs+akZsXf0szefSqMJlf08SY32t2jj4Ra7RApVYxOftD9nij/aLfuqOU6 ow6IgIcIG2ZvXLZwK87c5fxL7UAsTTV+M1sVv8jA33V2oKLhAgMBAAGjggG9MIIB uTAfBgNVHSMEGDAWgBTay+qtWwhdzP/8JlTOSeVVxjj0+DAdBgNVHQ4EFgQUmP9l 6zhyrZ06Qj4zogt+6LKFk4AwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAw NAYDVR0lBC0wKwYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDBglghkgB hvhCBAEwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkGCCsGAQUFBwIBFh1o dHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwBAgEwOwYDVR0fBDQw MjAwoC6gLIYqaHR0cDovL2NybC5jb21vZG9jYS5jb20vRXNzZW50aWFsU1NMQ0Eu Y3JsMG4GCCsGAQUFBwEBBGIwYDA4BggrBgEFBQcwAoYsaHR0cDovL2NydC5jb21v ZG9jYS5jb20vRXNzZW50aWFsU1NMQ0FfMi5jcnQwJAYIKwYBBQUHMAGGGGh0dHA6 Ly9vY3NwLmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcggptZWdhLmNvLm56gg53d3cu bWVnYS5jby5uejANBgkqhkiG9w0BAQUFAAOCAQEAcYhrsPSvDuwihMOh0ZmRpbOE Gw6LqKgLNTmaYUPQhzi2cyIjhUhNvugXQQlP5f0lp5j8cixmArafg1dTn4kQGgD3 ivtuhBTgKO1VYB/VRoAt6Lmswg3YqyiS7JiLDZxjoV7KoS5xdiaINfHDUaBBY4ZH j2BUlPniNBjCqXe/HndUTVUewlxbVps9FyCmH+C4o9DWzdGBzDpCkcmo5nM+cp7q ZhTIFTvZfo3zGuBoyu8BzuopCJcFRm3cRiXkpI7iOMUIixO1szkJS6WpL1sKdT73 UXp08U0LBqoqG130FbzEJBBV3ixbvY6BWMHoCWuaoF12KJnC5kHt2RoWAAgMXA== -----END CERTIFICATE----- ` const comodoIntermediate = `-----BEGIN CERTIFICATE----- MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf 5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR 0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU 2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv +Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk -----END CERTIFICATE----- ` const comodoRoot = `-----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI 2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp +2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW /zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== -----END CERTIFICATE----- ` // Chain 3: const testLeaf = `-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIJAJlJNev65WpnMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV BAYTAlVLMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24xEDAOBgNV BAoTB0V4YW1wbGUxFjAUBgNVBAMTDUludGVybWVkaWF0ZTIwHhcNMTYwMjI5MTY1 MjAxWhcNMTYwMzMwMTY1MjAxWjBRMQswCQYDVQQGEwJVSzEQMA4GA1UECBMHRW5n bGFuZDEPMA0GA1UEBxMGTG9uZG9uMRAwDgYDVQQKEwdFeGFtcGxlMQ0wCwYDVQQD EwRMZWFmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApNsqW+/vRclm Kbu/TcE6wfORzL8Nv9mGIsos94x1WrMOoRKMp3Slvczg9fGZsQNKrixW/38ZyUqW KzIh/PKH4L3hgSQFsomZ7C1zZ4yiH+zZTf+fjPRX86q8hf1cOhP0YdMvYMmSfjHG I7UDC9AAz6f8AmvK6CdNQaMlikZpfolWxQUsqhajVrHNv4LhjXFS/Yyi23D2qDpG 0e3kgQ+54OlWb3Gg2hsBZ6ddILukzLgkctYzHLtUv4EdAzol79yHdx+H7GxxF7c5 Ldju1H+b+Xc/dEBSKRCjEuJhXXmNlutfQPE6JzA9sr0wAXJBt/6n2UCEEZld4+XM CtYaCxMrSwIDAQABo4GAMH4wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUppd9scFX d2jUJEeUHA87ttmyOjQwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzAChihodHRw Oi8vd3d3LmV4YW1wbGUuY29tL2ludGVybWVkaWF0ZTIuY3J0MAkGA1UdEQQCMAAw DQYJKoZIhvcNAQELBQADggEBAIrtR+O1H20nUqxnBIS8/efGH7YUBpKGGbs3CmSW 7+IUke1VcDO3gNMjW7A/UxDM+1GM0MYD0/Pmlnk3/Q4TLZDpkAbk6lU5A/PVLqyE 5maPmwA+uIL3So9ivoCbIqbK/38g0Gqvdvq3yafH/60iodBAokr7r5iY/HmMBp4y 8PpQsZpx16XJWm0mkzvUx8OBS0MD/mnoCWE5i3Q9FT+KEEByZEyxj+2aqx3bYPhl bqdbmwN5ZqYKt4lvJHJbjZb4gKKEEaFcBNJjZoSUDWnE+I1ZwmaXzkwwqfFEam1v FSv/+x5C+55ylyX9E+S3UoH7wVswX/iT08hKL4IInmPoYQE= -----END CERTIFICATE----- ` const testIntermediate2 = `-----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIJALmEjiSeHoB7MA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV BAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMQ8wDQYDVQQHDAZMb25kb24xEDAOBgNV BAoMB0V4YW1wbGUxFjAUBgNVBAMMDUludGVybWVkaWF0ZTEwHhcNMTYwMjI5MTY0 NzQ1WhcNMTYwMzMwMTY0NzQ1WjBaMQswCQYDVQQGEwJVSzEQMA4GA1UECBMHRW5n bGFuZDEPMA0GA1UEBxMGTG9uZG9uMRAwDgYDVQQKEwdFeGFtcGxlMRYwFAYDVQQD Ew1JbnRlcm1lZGlhdGUyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ukLNzAPup6wt5e4pGsZ1na6VIj7sx74WaenW2RNp2NrxTQIGPugU2HET8PPau/ch VQNCtKLq8Wx9Kslq/aqn2e00jhxxrQmVc1fPwSnpOudhwbU6hvUkjYVtZ36BnGdp oWw6HNVeemaBYFOtH6RXo/KtD7A+YLbfc5am//iMCdZI2oBeTLEToB3q21p/0PKm pIKGZrPJnFdzSPlVkcDr/Lz8a1UCPYQW4zjPlYZjp9wDWpR7E7Fwla1RSFwBTnu1 xTVJnK7DU/i2A3JV9YLx63f9rBwwQdFBRWxr67GVeU6L7j43k/8H8CKCoOkk9Gf/ NP2pI6ZRh+toX4EzFaRL4QIDAQABo4GAMH4wDAYDVR0TBAUwAwEB/zAdBgNVHQ4E FgQUSxliX4GqBki8rtWTgWYVp/1FXc0wRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUF BzAChihodHRwOi8vd3d3LmV4YW1wbGUuY29tL2ludGVybWVkaWF0ZTEuY3J0MAkG A1UdEQQCMAAwDQYJKoZIhvcNAQELBQADggEBAINOzqDqNRgbWBEliCMQffLlJCAR ypQ6U/jqeUlfZ2VKgPYo2wyloaMgOFKtaHUeOjiiQ1YJEgtP1BlyRmlazRi7iqWI hvTtWom/8hyWG6AyN0tA4yK0+R4+OxMKDrNXU/C9W2p/yoI/fftU4m7QirqAW0ow jzgjd3+M+eiIDDxBIwLLPPJGKXTqFBQ6U0LPTfNJrX1IzeSUTpHO7uoD/OWAlcC5 6LZxbPrgl3qb2bExavvXNDm4WkZwfG3iodi27FRIW8dydePbE/Ism1AADByMdzZ9 boo602kOaxt0kkvfgyUkXdRMlAhlzgTyGJmY69tZmvcWlsh7Rm+R5+fQeMI= -----END CERTIFICATE----- ` const testIntermediate1 = `-----BEGIN CERTIFICATE----- MIIDkDCCAnigAwIBAgIJAK65INis8Pe8MA0GCSqGSIb3DQEBCwUAMD4xCzAJBgNV BAYTAlVLMRAwDgYDVQQIEwdFbmdsYW5kMRAwDgYDVQQKEwdFeGFtcGxlMQswCQYD VQQDEwJDQTAeFw0xNjAyMjkxNjM2MDdaFw0xNjAzMzAxNjM2MDdaMFoxCzAJBgNV BAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMQ8wDQYDVQQHDAZMb25kb24xEDAOBgNV BAoMB0V4YW1wbGUxFjAUBgNVBAMMDUludGVybWVkaWF0ZTEwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDNibsPgy2npVpATi/JrOEsbjmBBRgLDXp8tA5T JTl5YGn4K/9eltl12JvnXh5EgBxl8uTQwgaBX/+IY6BdXOcldfKPIBIO04gU9JtG DnvW0638T2ujmwyDkpKZ4yTqugHo5nteLLDzt1iOMNeAZud0y5Lwxql4JIFI9eat 8C3JUESx9A8ZBNvAbcd38KBIoJAf67V5r2R3maqA/krxczVmUpf+xDuzkcEWDlgX HfTC0mH2y2jsuPx4eepM7h+oPf0n/Nsoln4H+KiLL9l2bt5AtT1qdapQbcU1+GrU I8Kqgvbe0XNlmZ6XYkQrprGcZTGmIsHNMWW+E6oyJni2O5TpAgMBAAGjdTBzMAwG A1UdEwQFMAMBAf8wHQYDVR0OBBYEFL/b9VYR33yOBrw5QONVjHhb8+O5MDkGCCsG AQUFBwEBBC0wKzApBggrBgEFBQcwAoYdaHR0cDovL3d3dy5leGFtcGxlLmNvbS9j YS5jcnQwCQYDVR0RBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvwEKifIQX/Ua9PwF J8i6tf4IfMaP1ra46n9uwwC3YPsQF8MjLys+mcaM899lroaPIRwy/FrRfx4lLsHm EwN3jNgvRPDgUpGF7qsC2HcKUpvAzQoHQy/JN8Gy2StywtzvlaswR8qKR32zKoUc y95NwaGzg4HxK3lNpj0Vorus6VED8GLgbiewAmFVlXd/pWrv8t1zzCyK780zetgD R4xC6VVsV+6V/1MqiuVtYHUWJj6MBAWOXPnHeZB6BDjuQh7MdQr7Bx4EOzZnnzny 4hTZOFVQeV/7jx/NKQoh/oEyGBqNW8R5umynowG/SHFQGNKpyvzQvZr6enPbrHEI pLLzxA== -----END CERTIFICATE----- ` const testRoot = `-----BEGIN CERTIFICATE----- MIIDOTCCAiGgAwIBAgIJAJWqHxvK6nAQMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNV BAYTAlVLMRAwDgYDVQQIEwdFbmdsYW5kMRAwDgYDVQQKEwdFeGFtcGxlMQswCQYD VQQDEwJDQTAeFw0xNjAyMjkxNTU1MDBaFw0xNzAyMjgxNTU1MDBaMD4xCzAJBgNV BAYTAlVLMRAwDgYDVQQIEwdFbmdsYW5kMRAwDgYDVQQKEwdFeGFtcGxlMQswCQYD VQQDEwJDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANKk/zKjG6Z4 Cux2o7v0HmkRuzMmn0jXizpH9+9g0MKCcIfbUK05YpUUX09Ij3u4t9zrbola+drt 0u5DV9FujtD3cjwOOxvSyGWzzqoI2uh5pOFDkTaEtBzvLEB9T1wsrvmLXy0IMEhu QL8B33SY+7T5cuBPSIX0uipRJDz+1k+ESwfXhXz4uS1Y1K7GD+BxlPVrh71WxA5r f+hHsDE9f2LvCTI156dJHtBVSzAFxq/Kl2r+fofFCKUyBo9wNgL3j4t/jxbkEwA+ YDpqZ1YFYSFD/3tn/vEF0KmdmW9L3zrCli1WWtAOkl7oVCgPiXs9mfjZqYj4+0la hJhae79jS+sCAwEAAaM6MDgwHQYDVR0OBBYEFP+7oT2emG1zF5bhXfVyrUWCymEj MAwGA1UdEwQFMAMBAf8wCQYDVR0RBAIwADANBgkqhkiG9w0BAQUFAAOCAQEAfbyn lejnhbuiR5s7ENw1aMkod3FnZhhAIhCHncqsAJ1XPqaVvZxgrY3Rrxudp9rA9Gwn ZJoPqLOLhcWzLkSpPQ1w43HOk19Ok9UGRsSpkHlPbTac3wzcjKEbBpPONgoin80/ ZGMvti8uvkZH8qqWsvmRrq4pDEK1h2eeF0ayF349evdKyFB2yfWV5dUrBXin28vf AcQIk20/eb9bZ3KMPCa3dGjzDaQDAuutjS/XkRRkmMnp3q3ZQKlE8Fc+bzoP274Q X2+I8S85sfOxJOR+CF6CY7IND2BlnUFCOXvIk2V8tX1lbP3Lv6Ukuz+AlQ7Whn5T qiVjrghWwV/u6h3HSA== -----END CERTIFICATE----- ` // Chain 4: Contains a loop: C signed by B signed by A signed by B etc const testC = `-----BEGIN CERTIFICATE----- MIIDUjCCAjqgAwIBAgIJANYuoGKAZHTEMA0GCSqGSIb3DQEBBQUAMD0xCzAJBgNV BAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMRAwDgYDVQQKDAdFeGFtcGxlMQowCAYD VQQDDAFCMB4XDTE2MDMwMTE2MjE1NFoXDTE3MDMwMTE2MjE1NFowPTELMAkGA1UE BhMCVUsxEDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB0V4YW1wbGUxCjAIBgNV BAMMAUMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6Z4S+7d7dkUDH fOY1AALL2MAg2PqjleuCk4fvsRz5zyVSAMH7R1MAI5atP5RrEwWdMxlXQO1QCaFf j5P8uNzvCyVaryHkmObSHzgC1+NKpGXNOf1c8VV4XHZn4oLg8vtN1uMjO4Sv3tc6 MaAqja9EkgIqO6HoN5bhnyCpNJfyZDZEYtKamZqxLo0nABQ3kSlZd53ThqNaI26y u3yKlWC/WHctYbiFMBx3GVzTIGjKHlHlYDs+nqF1KtV1Z46pQsIDXsWb/AJ/Je9c JsE0JaPS86SRiQTf6/uyNNPLW1qteAe3yEl3Kc0hMunxDNbmRbKR8NO9xrHT5jFT e9FhFhsBAgMBAAGjVTBTMAwGA1UdEwQFMAMBAf8wOAYIKwYBBQUHAQEELDAqMCgG CCsGAQUFBzAChhxodHRwOi8vd3d3LmV4YW1wbGUuY29tL2IuY3J0MAkGA1UdEQQC MAAwDQYJKoZIhvcNAQEFBQADggEBAI3DU3KzKfFEsHp1Ie+kPJeHZWOxk5Ti5L+G M2OoIc7ht8qO1ty1PE0XgaZ+wjpiknWRj+RcDrETwB7iINatQhmvWkEhH6yl2GqJ IbucsNouTCYxfP1K9kT4/XuXh0Jg7Q1cjHpQfcAazfr68XRLhcZv5cQmDBDwjUq8 OM1Xmp+BmYO+7XDM3g2j9d65xblgDFdWS4wM2pp63C3ywkuetXw/Yj2Pok/j+97D kGA8psFaxN/4vtHVzJg6YX80bNvURFNMoTjulxqhLvP8UqZidI7kU+V/JBRBZGvH ANWsKuJ5uadPBSat63pkIVTFsKwms1Vnfw39QDQi18HOp0zBzR0= -----END CERTIFICATE----- ` const testB = `-----BEGIN CERTIFICATE----- MIIDUjCCAjqgAwIBAgIJAJWqHxvK6nASMA0GCSqGSIb3DQEBBQUAMD0xCzAJBgNV BAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMRAwDgYDVQQKDAdFeGFtcGxlMQowCAYD VQQDDAFBMB4XDTE2MDMwMTE1MTMyMFoXDTE3MDMwMTE1MTMyMFowPTELMAkGA1UE BhMCVUsxEDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB0V4YW1wbGUxCjAIBgNV BAMMAUIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgQutw5xEEqBqn 1aS/Ye2xSJOV3RtOeZ8aEDOOrzaU/VL39Kk46+5tb8KbTDyri8HUfGe9JlKYPaIG lxs6QBq0watILRgypzgKLkqmBprTQIzXpgWPkUwh2Hn6cR/8YMSbObxTXZmm6ELd GG/dn2LfchzB94/JEVs4FZpVXRlfL7Wp8L/eCzmOMuN8eSyXEJWCSiWezjclh14V znSZ3pmtxsdiBJcBVscVfsYyAJ/Zznpej5orbO7zn9kxxRNrVTlcW5twgnl07A6w Tav2yWdNnFJZJYRYha4x9dsz5ZutCsiYQ9ZJs7MxYVRkGtn+cf92PqICbr25XY8O RwPZU/FjAgMBAAGjVTBTMAwGA1UdEwQFMAMBAf8wOAYIKwYBBQUHAQEELDAqMCgG CCsGAQUFBzAChhxodHRwOi8vd3d3LmV4YW1wbGUuY29tL2EuY3J0MAkGA1UdEQQC MAAwDQYJKoZIhvcNAQEFBQADggEBAGZn7nEQUpBVu1iNg5lkvl+EWrk03s6LQt/8 wWb3cKVMkWcO2dzeETukzUoAdJTBrhH+jg5Hvb53jnXzs3j1uNvfppPH5PFNxTYU YH8v6SmALBoFJXVTvZAqYRnKsTfBgatkbpEDMKjoq6JnOIKU8YMfnByBDfID9jUL r7Qf2YDuK9YwnZThKb8RFIX3KPnQpxBbdUQeC6jXZD2IU6Id0TFFX8sqBOJkDTZK FoqCxbUl6DWRY1hiZcM1qMTHJmy2vp13BDjNk5qeVB6QgpRFKG7C7zJ/PHjs8E13 /0MIQerhu36y6wh/2UIdDEY1Ga1Wb7bbX/LYcKybY6BosF14wxM= -----END CERTIFICATE----- ` const testA = `-----BEGIN CERTIFICATE----- MIIDUjCCAjqgAwIBAgIJANYuoGKAZHS+MA0GCSqGSIb3DQEBBQUAMD0xCzAJBgNV BAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMRAwDgYDVQQKDAdFeGFtcGxlMQowCAYD VQQDDAFCMB4XDTE2MDMwMTE1MjIwMVoXDTE3MDMwMTE1MjIwMVowPTELMAkGA1UE BhMCVUsxEDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB0V4YW1wbGUxCjAIBgNV BAMMAUEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP8EKj+C3KlGhw +e3s6P239N0gW96FPbAYeoyV8Hbz3A97MI+mAeRmhS85aX1Tg3jJwX3J+lLDtCeC jUQZ4IJUtR99JZaBni7rXB7zFgW6ppoIV1Oy0dMfgm4YVdyv3ww2335BsENtE7P1 p6g9qCa9RItYMLQVrV2TeFqTyPCyVaGfmzhCrBKcIXenBQGSz4SoI6sZgyn6t7n5 Uav4HvU0dd3jqXYuWA+TIaxokRva5EB/K+PsUKtSxYQILAlcpwwwWARvinfJv0Np W7gBtjOX0CQgTpY91JBrGpxpEa0J5qRnrudtRK8FEJVic9Sn35b+w2j/0T2KbSXu GfcA5z3PAgMBAAGjVTBTMAwGA1UdEwQFMAMBAf8wOAYIKwYBBQUHAQEELDAqMCgG CCsGAQUFBzAChhxodHRwOi8vd3d3LmV4YW1wbGUuY29tL2IuY3J0MAkGA1UdEQQC MAAwDQYJKoZIhvcNAQEFBQADggEBAHjwlz+C9Qn+Ggc1G7TcoIOuA4/yD8KqueIF GvUYXGgyXtV4cTQ5yTppWy8yhR2ZOCU7llOX0aoS3Oo3fKN7tcQGqz4n5LoPir4z 1A/h8aplp/Fd6xyNdIcvjCH0lvbSgXr/ZwC+Y5uTBZ4q9mYa3VfyQwvf4WnLEYCV vxsEcab0f5Z9As8rEFb44Dgn5Qj9TMbJ5OqkGocX/fEe+fgSWzgYQQsgQ62r8EOY QeqTQVAp7z9430uwhPAVKFWT7gQcF9+zAtA93Zwvc3L5b84iwazEpy95EeyCMWzb 4ymNu6ExYn88Tin40xYDjaX7mswccu3drC2icN0AfYEYl0rcD9Q= -----END CERTIFICATE----- ` google-certificate-transparency-go-2308f62/fixchain/containers.go000066400000000000000000000033051462611535200251270ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "sync" "github.com/google/certificate-transparency-go/x509" ) type dedupedChain struct { certs []*x509.Certificate } func (d *dedupedChain) addCert(cert *x509.Certificate) { // Check that the certificate isn't being added twice. for _, c := range d.certs { if c.Equal(cert) { return } } d.certs = append(d.certs, cert) } func (d *dedupedChain) addCertToFront(cert *x509.Certificate) { // Check that the certificate isn't being added twice. for _, c := range d.certs { if c.Equal(cert) { return } } d.certs = append([]*x509.Certificate{cert}, d.certs...) } func newDedupedChain(chain []*x509.Certificate) *dedupedChain { d := &dedupedChain{} for _, cert := range chain { d.addCert(cert) } return d } type lockedMap struct { m map[[hashSize]byte]bool sync.RWMutex } func newLockedMap() *lockedMap { return &lockedMap{m: make(map[[hashSize]byte]bool)} } func (m *lockedMap) get(hash [hashSize]byte) bool { m.RLock() defer m.RUnlock() return m.m[hash] } func (m *lockedMap) set(hash [hashSize]byte, b bool) { m.Lock() defer m.Unlock() m.m[hash] = b } google-certificate-transparency-go-2308f62/fixchain/fix.go000066400000000000000000000176561462611535200235660ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package fixchain holds code to help fix the validation chains for certificates. package fixchain import ( "encoding/pem" "net/http" "github.com/google/certificate-transparency-go/x509" ) // Fix attempts to fix the certificate chain for the certificate that is passed // to it, with respect to the given roots. Fix returns a list of successfully // constructed chains, and a list of errors it encountered along the way. The // presence of FixErrors does not mean the fix was unsuccessful. Callers should // check for returned chains to determine success. func Fix(cert *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool, client *http.Client) ([][]*x509.Certificate, []*FixError) { fix := &toFix{ cert: cert, chain: newDedupedChain(chain), roots: roots, cache: newURLCache(client, false), } return fix.handleChain() } const maxChainLength = 20 type toFix struct { cert *x509.Certificate chain *dedupedChain roots *x509.CertPool opts *x509.VerifyOptions cache *urlCache } func (fix *toFix) handleChain() ([][]*x509.Certificate, []*FixError) { intermediates := x509.NewCertPool() for _, c := range fix.chain.certs { intermediates.AddCert(c) } fix.opts = &x509.VerifyOptions{ Intermediates: intermediates, Roots: fix.roots, DisableTimeChecks: true, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } var retferrs []*FixError chains, ferrs := fix.constructChain() if ferrs != nil { retferrs = append(retferrs, ferrs...) chains, ferrs = fix.fixChain() if ferrs != nil { retferrs = append(retferrs, ferrs...) } } return chains, retferrs } func (fix *toFix) constructChain() ([][]*x509.Certificate, []*FixError) { chains, err := fix.cert.Verify(*fix.opts) if err != nil { return chains, []*FixError{ { Type: VerifyFailed, Cert: fix.cert, Chain: fix.chain.certs, Error: err, }, } } return chains, nil } // toFix.fixChain() tries to fix the certificate chain in the toFix struct for // the cert in the toFix struct wrt the roots in the toFix struct. // toFix.fixChain() uses the opts provided in the toFix struct to verify the // chain, and uses the cache in the toFix struct to go and get any potentially // missing intermediate certs. // toFix.fixChain() returns a slice of valid and verified chains for this cert // to the roots in the toFix struct, and a slice of the errors encountered // during the fixing process. func (fix *toFix) fixChain() ([][]*x509.Certificate, []*FixError) { var retferrs []*FixError // Ensure the leaf certificate is included as part of the certificate chain. dchain := *fix.chain dchain.addCertToFront(fix.cert) explored := make([]bool, len(dchain.certs)) lookup := make(map[[hashSize]byte]int) for i, cert := range dchain.certs { lookup[hash(cert)] = i } // For each certificate in the given certificate chain... for i, cert := range dchain.certs { // If the chains from this certificate have already been built and // added to the pool of intermediates, skip. if explored[i] { continue } seen := make(map[[hashSize]byte]bool) // Build all the chains possible that begin from this certificate, // and add each certificate found along the way to the pool of // intermediates against which to verify fix.cert. If the addition of // these intermediates causes chains for fix.cert to be verified, // fix.augmentIntermediates() will return those chains. chains, ferrs := fix.augmentIntermediates(cert, 1, seen) if ferrs != nil { retferrs = append(retferrs, ferrs...) } // If adding certs from the chains stemming from this cert resulted in // successful verification of chains for fix.cert to fix.root, return // the chains. if chains != nil { return chains, retferrs } // Mark any seen certs that match certs in the original chain as already // explored. for certHash := range seen { index, ok := lookup[certHash] if ok { explored[index] = true } } } return nil, append(retferrs, &FixError{ Type: FixFailed, Cert: fix.cert, Chain: fix.chain.certs, }) } // TODO(katjoyce): Extend fixing algorithm to build all of the chains for // toFix.cert and log all of the resulting intermediates. // toFix.augmentIntermediates() builds all possible chains that stem from the // given cert, and adds every certificate it finds in these chains to the pool // of intermediate certs in toFix.opts. Every time a new certificate is added // to this pool, it tries to re-verify toFix.cert wrt toFix.roots. // If this verification is ever successful, toFix.augmentIntermediates() returns // the verified chains for toFix.cert wrt toFix.roots. Also returned are any // errors that were encountered along the way. // // toFix.augmentIntermediates() builds all possible chains from cert by using a // recursive algorithm on the urls in the AIA information of each certificate // discovered. length represents the position of the current given cert in the // larger chain, and is used to impose a max length to which chains can be // explored. seen is a slice in which all certs that are encountered during the // search are noted down. func (fix *toFix) augmentIntermediates(cert *x509.Certificate, length int, seen map[[hashSize]byte]bool) ([][]*x509.Certificate, []*FixError) { // If this cert takes the chain past maxChainLength, or if this cert has // already been explored, return. if length > maxChainLength || seen[hash(cert)] { return nil, nil } // Mark this cert as already explored. seen[hash(cert)] = true // Add this cert to the pool of intermediates. If this results in successful // verification of one or more chains for fix.cert, return the chains. fix.opts.Intermediates.AddCert(cert) chains, err := fix.cert.Verify(*fix.opts) if err == nil { return chains, nil } // For each url in the AIA information of cert, get the corresponding // certificates and recursively build the chains from those certificates, // adding every cert to the pool of intermediates, running the verifier at // every cert addition, and returning verified chains of fix.cert as soon // as they are found. var retferrs []*FixError for _, url := range cert.IssuingCertificateURL { icerts, ferr := fix.getIntermediates(url) if ferr != nil { retferrs = append(retferrs, ferr) } for _, icert := range icerts { chains, ferrs := fix.augmentIntermediates(icert, length+1, seen) if ferrs != nil { retferrs = append(retferrs, ferrs...) } if chains != nil { return chains, retferrs } } } return nil, retferrs } // Get the certs that correspond to the given url. func (fix *toFix) getIntermediates(url string) ([]*x509.Certificate, *FixError) { var icerts []*x509.Certificate // PKCS#7 additions as (at time of writing) there is no standard Go PKCS#7 // implementation r := urlReplacement(url) if r != nil { return r, nil } body, err := fix.cache.getURL(url) if err != nil { return nil, &FixError{ Type: CannotFetchURL, Cert: fix.cert, Chain: fix.chain.certs, URL: url, Error: err, } } icert, err := x509.ParseCertificate(body) if x509.IsFatal(err) { s, _ := pem.Decode(body) if s != nil { icert, err = x509.ParseCertificate(s.Bytes) } } if x509.IsFatal(err) { return nil, &FixError{ Type: ParseFailure, Cert: fix.cert, Chain: fix.chain.certs, URL: url, Bad: body, Error: err, } } icerts = append(icerts, icert) return icerts, nil } google-certificate-transparency-go-2308f62/fixchain/fix_and_log.go000066400000000000000000000106231462611535200252340ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "context" "log" "net/http" "sync" "sync/atomic" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/x509" ) // FixAndLog contains a Fixer and a Logger, for all your fix-then-log-chain needs! type FixAndLog struct { fixer *Fixer chains chan []*x509.Certificate logger *Logger wg sync.WaitGroup // Number of whole chains queued - before checking cache & adding chains for intermediate certs. queued uint32 // Cache of chains that QueueAllCertsInChain() has already been called on. done *lockedMap // Number of whole chains submitted to the FixAndLog using QueueAllCertsInChain() (before adding chains for intermediate certs) that had previously been processed. alreadyDone uint32 // Number of chains queued total, including chains from intermediate certs. // Note that in each chain there is len(chain) certs. So when calling QueueAllCertsInChain(chain), len(chain) chains will actually be added to the queue. chainsQueued uint32 // Number of chains whose leaf cert has already been posted to the log with a valid chain. alreadyPosted uint32 // Number of chains sent on to the Fixer to begin fixing & logging! chainsSent uint32 } // QueueAllCertsInChain adds every cert in the chain and the chain to the queue // to be fixed and logged. func (fl *FixAndLog) QueueAllCertsInChain(chain []*x509.Certificate) { if chain != nil { atomic.AddUint32(&fl.queued, 1) atomic.AddUint32(&fl.chainsQueued, uint32(len(chain))) dchain := newDedupedChain(chain) // Caching check h := hashBag(dchain.certs) if fl.done.get(h) { atomic.AddUint32(&fl.alreadyDone, 1) return } fl.done.set(h, true) for _, cert := range dchain.certs { if fl.logger.IsPosted(cert) { atomic.AddUint32(&fl.alreadyPosted, 1) continue } fl.fixer.QueueChain(cert, dchain.certs, fl.logger.RootCerts()) atomic.AddUint32(&fl.chainsSent, 1) } } } // QueueChain queues the given chain to be fixed wrt the roots of the logger // contained in fl, and then logged to the Certificate Transparency log // represented by the logger. Note: chain is expected to be in the order of // cert --> root. func (fl *FixAndLog) QueueChain(chain []*x509.Certificate) { if chain != nil { if fl.logger.IsPosted(chain[0]) { atomic.AddUint32(&fl.alreadyPosted, 1) return } fl.fixer.QueueChain(chain[0], chain, fl.logger.RootCerts()) atomic.AddUint32(&fl.chainsSent, 1) } } // Wait waits for the all of the queued chains to complete being fixed and // logged. func (fl *FixAndLog) Wait() { fl.fixer.Wait() close(fl.chains) fl.wg.Wait() fl.logger.Wait() } // NewFixAndLog creates an object that will asynchronously fix any chains that // are added to its queue, and then log them to the Certificate Transparency log // found at the given url. Any errors encountered along the way are pushed to // the given errors channel. func NewFixAndLog(ctx context.Context, fixerWorkerCount int, loggerWorkerCount int, errors chan<- *FixError, client *http.Client, logClient client.AddLogClient, limiter Limiter, logStats bool) *FixAndLog { chains := make(chan []*x509.Certificate) fl := &FixAndLog{ fixer: NewFixer(fixerWorkerCount, chains, errors, client, logStats), chains: chains, logger: NewLogger(ctx, loggerWorkerCount, errors, logClient, limiter, logStats), done: newLockedMap(), } fl.wg.Add(1) go func() { for chain := range chains { fl.logger.QueueChain(chain) } fl.wg.Done() }() if logStats { t := time.NewTicker(time.Second) go func() { for range t.C { log.Printf("fix-then-log: %d whole chains queued, %d whole chains already done, %d total chains queued, %d chains don't need posting (cache hits), %d chains sent to fixer", fl.queued, fl.alreadyDone, fl.chainsQueued, fl.alreadyPosted, fl.chainsSent) } }() } return fl } google-certificate-transparency-go-2308f62/fixchain/fix_and_log_test.go000066400000000000000000000243431462611535200262770ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "context" "net/http" "strings" "sync" "testing" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/x509" ) var newFixAndLogTests = []fixAndLogTest{ // Tests that add chains to the FixAndLog one at a time using QueueChain() { // Full chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // Chain without the root successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // Chain to wrong root results in error. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, comodoIntermediate, comodoRoot}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Chain without correct root containing loop results in error. url: "https://ct.googleapis.com/pilot", chain: []string{testC, testB, testA}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Incomplete chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, expectedErrs: []errorType{VerifyFailed}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf}, function: "QueueChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, { // Garbled chain (with a leaf that has no chain to our roots) results in an error. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Garbled chain (with a leaf that has a chain to our roots) successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf, megaLeaf, googleLeaf, thawteIntermediate, comodoRoot}, function: "QueueChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, // Tests that add chains to the FixAndLog using QueueAllCertsInChain() { // Full chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, {"VeriSign"}, }, }, { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, }, }, { // Chain to wrong root results errors. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, comodoIntermediate, comodoRoot}, function: "QueueAllCertsInChain", expectedErrs: []errorType{ VerifyFailed, FixFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, { // Chain without correct root containing loop results in error. url: "https://ct.googleapis.com/pilot", chain: []string{testC, testB, testA}, function: "QueueAllCertsInChain", expectedErrs: []errorType{ VerifyFailed, FixFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, { // Incomplete chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, expectedErrs: []errorType{VerifyFailed}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, { // Garbled chain (with a leaf that has no chain to our roots) url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, {"VeriSign"}, }, expectedErrs: []errorType{ VerifyFailed, FixFailed, }, }, { // Garbled chain (with a leaf that has a chain to our roots) url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf, megaLeaf, googleLeaf, thawteIntermediate, comodoRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, }, expectedErrs: []errorType{ VerifyFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, } func TestNewFixAndLog(t *testing.T) { // Test that expected chains are logged when adding a chain using QueueChain() ctx := context.Background() for i, test := range newFixAndLogTests { seen := make([]bool, len(test.expLoggedChains)) errors := make(chan *FixError) c := &http.Client{Transport: &testRoundTripper{t: t, test: &test, testIndex: i, seen: seen}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } fl := NewFixAndLog(ctx, 1, 1, errors, c, logClient, newNilLimiter(), false) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() testErrors(t, i, test.expectedErrs, errors) }() switch test.function { case "QueueChain": fl.QueueChain(extractTestChain(t, i, test.chain)) case "QueueAllCertsInChain": fl.QueueAllCertsInChain(extractTestChain(t, i, test.chain)) } fl.Wait() close(errors) wg.Wait() // Check that no chains that were expected to be logged were not. for j, val := range seen { if !val { t.Errorf("#%d: Expected chain was not logged: %s", i, strings.Join(test.expLoggedChains[j], " -> ")) } } } } var fixAndLogQueueTests = []fixAndLogTest{ { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, expectedCert: "Google", expectedChain: []string{"Google", "Thawte", "VeriSign"}, expectedRoots: []string{verisignRoot, testRoot}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, googleLeaf, thawteIntermediate, verisignRoot}, expectedCert: "Google", expectedChain: []string{"Google", "Thawte", "VeriSign"}, expectedRoots: []string{verisignRoot, testRoot}, }, { // Test passing a nil chain to FixAndLog.QueueChain() url: "https://ct.googleapis.com/pilot", }, } func testQueueAllCertsInChain(t *testing.T, i int, test *fixAndLogTest, fl *FixAndLog) { defer fl.wg.Done() seen := make([]bool, len(test.expectedChain)) NextToFix: for fix := range fl.fixer.toFix { // Check fix.chain is the chain that's expected. matchTestChain(t, i, test.expectedChain, fix.chain.certs) //Check fix.roots are the roots that are expected for the given url. matchTestRoots(t, i, test.expectedRoots, fix.roots) for j, expCert := range test.expectedChain { if seen[j] { continue } if strings.Contains(nameToKey(&fix.cert.Subject), expCert) { seen[j] = true continue NextToFix } } t.Errorf("#%d: Queued certificate %s was not expected", i, nameToKey(&fix.cert.Subject)) } for j, val := range seen { if !val { t.Errorf("#%d: Expected certificate %s was not queued", i, test.expectedChain[j]) } } } func TestQueueAllCertsInChain(t *testing.T) { ctx := context.Background() for i, test := range fixAndLogQueueTests { f := &Fixer{toFix: make(chan *toFix)} c := &http.Client{Transport: &testRoundTripper{}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ ctx: ctx, client: logClient, postCertCache: newLockedMap(), } fl := &FixAndLog{fixer: f, chains: make(chan []*x509.Certificate), logger: l, done: newLockedMap()} fl.wg.Add(1) go testQueueAllCertsInChain(t, i, &test, fl) fl.QueueAllCertsInChain(extractTestChain(t, i, test.chain)) fl.Wait() } } func testFixAndLogQueueChain(t *testing.T, i int, test *fixAndLogTest, fl *FixAndLog) { defer fl.wg.Done() fix, ok := <-fl.fixer.toFix if ok { // Check fix.cert is the cert that's expected. if !strings.Contains(nameToKey(&fix.cert.Subject), test.expectedCert) { t.Errorf("#%d: Expected cert does not match queued cert", i) } // Check fix.chain is the chain that's expected. matchTestChain(t, i, test.expectedChain, fix.chain.certs) //Check fix.roots are the roots that are expected for the given url. matchTestRoots(t, i, test.expectedRoots, fix.roots) } } func TestFixAndLogQueueChain(t *testing.T) { ctx := context.Background() for i, test := range fixAndLogQueueTests { f := &Fixer{toFix: make(chan *toFix)} c := &http.Client{Transport: &testRoundTripper{}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ ctx: ctx, client: logClient, postCertCache: newLockedMap(), } fl := &FixAndLog{fixer: f, chains: make(chan []*x509.Certificate), logger: l, done: newLockedMap()} fl.wg.Add(1) go testFixAndLogQueueChain(t, i, &test, fl) fl.QueueChain(extractTestChain(t, i, test.chain)) fl.Wait() } } google-certificate-transparency-go-2308f62/fixchain/fix_error.go000066400000000000000000000113521462611535200247620ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "bytes" "encoding/json" "encoding/pem" "errors" "fmt" "github.com/google/certificate-transparency-go/x509" ) type errorType int // FixError types const ( None errorType = iota ParseFailure CannotFetchURL FixFailed LogPostFailed // Posting to log failed VerifyFailed ) // FixError is the struct with which errors in the fixing process are reported type FixError struct { Type errorType Cert *x509.Certificate // The supplied leaf certificate Chain []*x509.Certificate // The supplied chain URL string // URL, if a URL is involved Bad []byte // The offending certificate bytes, if applicable Error error // The error } // Equal tests whether this FixError is equal to another given FixError func (e FixError) Equal(f *FixError) bool { if f == nil || e.Type != f.Type || e.URL != f.URL || !bytes.Equal(e.Bad, f.Bad) { return false } // Check Cert equality if e.Cert != nil { if f.Cert == nil || !e.Cert.Equal(f.Cert) { return false } } else if f.Cert != nil { return false } // Check Chain equality if len(e.Chain) != len(f.Chain) { return false } for i := range e.Chain { if !e.Chain[i].Equal(f.Chain[i]) { return false } } // Check Error equality if e.Error != nil { if f.Error == nil || e.Error.Error() != f.Error.Error() { return false } } else if f.Error != nil { return false } return true } // TypeString returns a string describing e.Type func (e FixError) TypeString() string { switch e.Type { case None: return "None" case ParseFailure: return "ParseFailure" case CannotFetchURL: return "CannotFetchURL" case FixFailed: return "FixFailed" case LogPostFailed: return "LogPostFailed" case VerifyFailed: return "VerifyFailed" default: return fmt.Sprintf("Type %d", e.Type) } } // String converts an error to a (mostly) human readable string func (e FixError) String() string { s := e.TypeString() + "\n" if e.Error != nil { s += "Error: " + e.Error.Error() + "\n" } if e.URL != "" { s += "URL: " + e.URL + "\n" } if e.Bad != nil { s += "Bad: " + dumpPEM(e.Bad) } if e.Cert != nil { s += "Cert: " + dumpPEM(e.Cert.Raw) } if e.Chain != nil { s += "Chain: " + dumpChainPEM(e.Chain) } return s } // MarshalJSON converts a FixError to JSON func (e FixError) MarshalJSON() ([]byte, error) { var m struct { Type string Cert []byte Chain [][]byte URL string Bad []byte Error string Code int } m.Type = e.TypeString() if e.Cert != nil { m.Cert = e.Cert.Raw } for _, c := range e.Chain { m.Chain = append(m.Chain, c.Raw) } m.URL = e.URL m.Bad = e.Bad if e.Error != nil { m.Error = e.Error.Error() } return json.Marshal(m) } // UnmarshalJSON converts the JSON representation of a FixError back to a FixError func UnmarshalJSON(b []byte) (*FixError, error) { var u struct { Type string Cert []byte Chain [][]byte URL string Bad []byte Error string Code int } err := json.Unmarshal(b, &u) if err != nil { return nil, err } ferr := &FixError{} switch u.Type { case "None": ferr.Type = None case "ParseFailure": ferr.Type = ParseFailure case "CannotFetchURL": ferr.Type = CannotFetchURL case "FixFailed": ferr.Type = FixFailed case "LogPostFailed": ferr.Type = LogPostFailed case "VerifyFailed": ferr.Type = VerifyFailed default: return nil, errors.New("cannot parse FixError Type") } if u.Cert != nil { cert, err := x509.ParseCertificate(u.Cert) if x509.IsFatal(err) { return nil, fmt.Errorf("cannot parse FixError Cert: %s", err) } ferr.Cert = cert } for _, c := range u.Chain { cert, err := x509.ParseCertificate(c) if x509.IsFatal(err) { return nil, fmt.Errorf("cannot parse FixError Chain: %s", err) } ferr.Chain = append(ferr.Chain, cert) } ferr.URL = u.URL ferr.Bad = u.Bad if u.Error != "" { ferr.Error = errors.New(u.Error) } return ferr, nil } func dumpChainPEM(chain []*x509.Certificate) string { var p string for _, cert := range chain { p += dumpPEM(cert.Raw) } return p } func dumpPEM(cert []byte) string { b := pem.Block{Type: "CERTIFICATE", Bytes: cert} return string(pem.EncodeToMemory(&b)) } google-certificate-transparency-go-2308f62/fixchain/fix_error_test.go000066400000000000000000000210071462611535200260170ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "errors" "fmt" "testing" "github.com/google/certificate-transparency-go/x509" ) func TestEqual(t *testing.T) { equalTests := []struct { e *FixError f *FixError expEqual bool }{ { &FixError{}, &FixError{}, true, }, { &FixError{Type: LogPostFailed}, &FixError{}, false, }, { &FixError{Type: ParseFailure}, &FixError{Type: LogPostFailed}, false, }, { &FixError{Cert: GetTestCertificateFromPEM(t, googleLeaf)}, &FixError{}, false, }, { &FixError{Cert: GetTestCertificateFromPEM(t, googleLeaf)}, &FixError{Cert: GetTestCertificateFromPEM(t, megaLeaf)}, false, }, { &FixError{ Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, }, &FixError{}, false, }, { // Chains with only one cert different. &FixError{ Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, }, &FixError{ Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, comodoRoot), }, }, false, }, { // Completely different chains. &FixError{ Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, }, &FixError{ Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, megaLeaf), GetTestCertificateFromPEM(t, comodoIntermediate), GetTestCertificateFromPEM(t, comodoRoot), }, }, false, }, { &FixError{URL: "https://www.test.com"}, &FixError{}, false, }, { &FixError{URL: "https://www.test.com"}, &FixError{URL: "https://www.test1.com"}, false, }, { &FixError{Bad: []byte(googleLeaf)}, &FixError{}, false, }, { &FixError{Bad: []byte(googleLeaf)}, &FixError{Bad: []byte(megaLeaf)}, false, }, { &FixError{Error: errors.New("error1")}, &FixError{}, false, }, { &FixError{Error: errors.New("error1")}, &FixError{Error: errors.New("error2")}, false, }, { &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, &FixError{}, false, }, { &FixError{}, &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, false, }, { &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, true, }, { // nil test &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, nil, false, }, } for i, test := range equalTests { if test.e.Equal(test.f) != test.expEqual { t.Errorf("#%d: expected FixError.Equal() to return %t, returned %t", i, test.expEqual, !test.expEqual) } } } func TestTypeString(t *testing.T) { typeStringTests := []struct { ferr FixError expected string }{ { FixError{Type: None}, "None", }, { FixError{Type: ParseFailure}, "ParseFailure", }, { FixError{Type: CannotFetchURL}, "CannotFetchURL", }, { FixError{Type: FixFailed}, "FixFailed", }, { FixError{Type: LogPostFailed}, "LogPostFailed", }, { FixError{Type: VerifyFailed}, "VerifyFailed", }, { FixError{}, "None", }, } for i, test := range typeStringTests { if got, want := test.ferr.TypeString(), test.expected; got != want { t.Errorf("#%d: TypeString() returned %s, expected %s.", i, got, want) } } } func TestString(t *testing.T) { stringTests := []struct { ferr *FixError str string }{ { &FixError{Type: None}, "None\n", }, { &FixError{ Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Error: errors.New("log post failed"), }, "LogPostFailed\n" + "Error: log post failed\n" + "URL: https://www.test.com\n" + "Cert: " + googleLeaf + "Chain: " + googleLeaf + thawteIntermediate + verisignRoot, }, } for i, test := range stringTests { if got, want := test.ferr.String(), test.str; got != want { t.Errorf("#%d: String() returned %s, expected %s.", i, got, want) } } } func TestMarshalJSON(t *testing.T) { marshalJSONTests := []*FixError{ {}, { Type: LogPostFailed, Cert: GetTestCertificateFromPEM(t, googleLeaf), Chain: []*x509.Certificate{ GetTestCertificateFromPEM(t, googleLeaf), GetTestCertificateFromPEM(t, thawteIntermediate), GetTestCertificateFromPEM(t, verisignRoot), }, URL: "https://www.test.com", Bad: GetTestCertificateFromPEM(t, googleLeaf).Raw, Error: errors.New("log post failed"), }, } for i, test := range marshalJSONTests { b, err := test.MarshalJSON() if err != nil { t.Errorf("#%d: Error marshaling json: %s", i, err.Error()) } ferr, err := UnmarshalJSON(b) if err != nil { t.Errorf("#%d: Error unmarshaling json: %s", i, err.Error()) } if !test.Equal(ferr) { t.Errorf("#%d: Original FixError does not match marshaled-then-unmarshaled FixError", i) } } } func TestDumpPEM(t *testing.T) { dumpPEMTests := []string{googleLeaf} for i, test := range dumpPEMTests { cert := GetTestCertificateFromPEM(t, test) p := dumpPEM(cert.Raw) certFromPEM := GetTestCertificateFromPEM(t, p) if !cert.Equal(certFromPEM) { t.Errorf("#%d: cert from output of dumpPEM() does not match original", i) } } } func TestDumpChainPEM(t *testing.T) { dumpChainPEMTests := []struct { chain []string expected string }{ { []string{googleLeaf, thawteIntermediate}, fmt.Sprintf("%s%s", googleLeaf, thawteIntermediate), }, } for i, test := range dumpChainPEMTests { chain := extractTestChain(t, i, test.chain) if got := dumpChainPEM(chain); got != test.expected { t.Errorf("#%d: dumpChainPEM() returned %s, expected %s", i, got, test.expected) } } } google-certificate-transparency-go-2308f62/fixchain/fix_test.go000066400000000000000000000135331462611535200246130ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "net/http" "testing" "github.com/google/certificate-transparency-go/x509" ) var constructChainTests = []fixTest{ // constructChain() { // Correct chain returns chain cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "constructChain", expectedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { cert: testLeaf, chain: []string{testIntermediate2, testIntermediate1, testRoot}, roots: []string{testRoot}, function: "constructChain", expectedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, }, { // No roots results in an error cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, function: "constructChain", expectedErrs: []errorType{VerifyFailed}, }, { // Incomplete chain results in an error cert: googleLeaf, roots: []string{verisignRoot}, function: "constructChain", expectedErrs: []errorType{VerifyFailed}, }, { // The wrong intermediate and root results in an error cert: megaLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "constructChain", expectedErrs: []errorType{VerifyFailed}, }, { // The wrong root results in an error cert: megaLeaf, chain: []string{comodoIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "constructChain", expectedErrs: []errorType{VerifyFailed}, }, } var fixChainTests = []fixTest{ // fixChain() { // Correct chain returns chain cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "fixChain", expectedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // No roots results in an error cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, function: "fixChain", expectedErrs: []errorType{FixFailed}, }, { // No roots where chain that will be built contains a loop results in error cert: testC, chain: []string{testB, testA}, function: "fixChain", expectedErrs: []errorType{FixFailed}, }, { // Incomplete chain returns fixed chain cert: googleLeaf, roots: []string{verisignRoot}, function: "fixChain", expectedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { cert: testLeaf, chain: []string{testIntermediate2}, roots: []string{testRoot}, function: "fixChain", expectedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, }, { cert: testLeaf, chain: []string{testIntermediate1}, roots: []string{testRoot}, function: "fixChain", expectedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, }, { cert: testLeaf, roots: []string{testRoot}, function: "fixChain", expectedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, }, { // The wrong intermediate and root results in an error cert: megaLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "fixChain", expectedErrs: []errorType{FixFailed}, }, { // The wrong root results in an error cert: megaLeaf, chain: []string{comodoIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "fixChain", expectedErrs: []errorType{FixFailed}, }, // TODO(katjoyce): Add test where cert has multiple URLs in AIA extension. } func setUpFix(t *testing.T, i int, ft *fixTest) *toFix { // Create & populate toFix to test from fixTest info fix := &toFix{ cert: GetTestCertificateFromPEM(t, ft.cert), chain: newDedupedChain(extractTestChain(t, i, ft.chain)), roots: extractTestRoots(t, i, ft.roots), cache: newURLCache(&http.Client{Transport: &testRoundTripper{}}, false), } intermediates := x509.NewCertPool() for j, cert := range ft.chain { ok := intermediates.AppendCertsFromPEM([]byte(cert)) if !ok { t.Errorf("#%d: Failed to parse intermediate #%d", i, j) } } fix.opts = &x509.VerifyOptions{ Intermediates: intermediates, Roots: fix.roots, DisableTimeChecks: true, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } return fix } func testFixChainFunctions(t *testing.T, i int, ft *fixTest) { fix := setUpFix(t, i, ft) var chains [][]*x509.Certificate var ferrs []*FixError switch ft.function { case "constructChain": chains, ferrs = fix.constructChain() case "fixChain": chains, ferrs = fix.fixChain() case "handleChain": chains, ferrs = fix.handleChain() } matchTestChainList(t, i, ft.expectedChains, chains) matchTestErrorList(t, i, ft.expectedErrs, ferrs) } func TestFixChainFunctions(t *testing.T) { var allTests []fixTest allTests = append(allTests, constructChainTests...) allTests = append(allTests, fixChainTests...) allTests = append(allTests, handleChainTests...) for i, ft := range allTests { testFixChainFunctions(t, i, &ft) } } func TestFix(t *testing.T) { for i, test := range handleChainTests { chains, ferrs := Fix(GetTestCertificateFromPEM(t, test.cert), extractTestChain(t, i, test.chain), extractTestRoots(t, i, test.roots), &http.Client{Transport: &testRoundTripper{}}) matchTestChainList(t, i, test.expectedChains, chains) matchTestErrorList(t, i, test.expectedErrs, ferrs) } } google-certificate-transparency-go-2308f62/fixchain/fixchain_test.go000066400000000000000000000067461462611535200256260ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain type fixTest struct { cert string chain []string roots []string function string expectedChains [][]string expectedErrs []errorType } var handleChainTests = []fixTest{ // handleChain() { // Correct chain returns chain cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "handleChain", expectedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // No roots results in an error cert: googleLeaf, chain: []string{thawteIntermediate, verisignRoot}, function: "handleChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // No roots where chain that will be built contains a loop results in error cert: testC, chain: []string{testB, testA}, function: "handleChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Incomplete chain returns a fixed chain cert: googleLeaf, roots: []string{verisignRoot}, function: "handleChain", expectedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, expectedErrs: []errorType{VerifyFailed}, }, { cert: testLeaf, roots: []string{testRoot}, function: "handleChain", expectedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, { // The wrong intermediate and root results in an error cert: megaLeaf, chain: []string{thawteIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "handleChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // The wrong root results in an error cert: megaLeaf, chain: []string{comodoIntermediate, verisignRoot}, roots: []string{verisignRoot}, function: "handleChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, } type postTest struct { url string chain []string urlScheme string urlHost string urlPath string ferr *FixError expectedErrs []errorType } var postTests = []postTest{ { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, urlScheme: "https", urlHost: "ct.googleapis.com", urlPath: "/pilot/ct/v1/add-chain", ferr: &FixError{Type: None}, }, { // Empty chain url: "https://ct.googleapis.com/pilot", urlScheme: "https", urlHost: "ct.googleapis.com", urlPath: "/pilot/ct/v1/add-chain", ferr: &FixError{Type: None}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, ferr: &FixError{Type: LogPostFailed}, expectedErrs: []errorType{LogPostFailed}, }, } type fixAndLogTest struct { url string chain []string // Expected items that will be queued to be fixed then logged expectedCert string expectedChain []string expectedRoots []string function string expLoggedChains [][]string expectedErrs []errorType } google-certificate-transparency-go-2308f62/fixchain/fixer.go000066400000000000000000000173441462611535200241070ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "bytes" "log" "net/http" "sort" "sync" "sync/atomic" "time" "github.com/google/certificate-transparency-go/x509" ) // Fixer contains methods to asynchronously fix certificate chains and // properties to store information about each attempt that is made to fix a // certificate chain. type Fixer struct { toFix chan *toFix chains chan<- []*x509.Certificate // Chains successfully fixed by the fixer errors chan<- *FixError active uint32 reconstructed uint32 notReconstructed uint32 fixed uint32 notFixed uint32 validChainsProduced uint32 validChainsOut uint32 wg sync.WaitGroup cache *urlCache } // QueueChain adds the given cert and chain to the queue to be fixed by the // fixer, with respect to the given roots. Note: chain is expected to be in the // order of cert --> root. func (f *Fixer) QueueChain(cert *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool) { f.toFix <- &toFix{ cert: cert, chain: newDedupedChain(chain), roots: roots, cache: f.cache, } } // Wait for all the fixer workers to finish. func (f *Fixer) Wait() { close(f.toFix) f.wg.Wait() } func (f *Fixer) updateCounters(chains [][]*x509.Certificate, ferrs []*FixError) { atomic.AddUint32(&f.validChainsProduced, uint32(len(chains))) var verifyFailed bool var fixFailed bool for _, ferr := range ferrs { switch ferr.Type { case VerifyFailed: verifyFailed = true case FixFailed: fixFailed = true } } // No errors --> reconstructed // VerifyFailed --> notReconstructed // VerifyFailed but no FixFailed --> fixed // VerifyFailed and FixFailed --> notFixed if verifyFailed { atomic.AddUint32(&f.notReconstructed, 1) // FixFailed error will only be present if a VerifyFailed error is, as // fixChain() is only called if constructChain() fails. if fixFailed { atomic.AddUint32(&f.notFixed, 1) return } atomic.AddUint32(&f.fixed, 1) return } atomic.AddUint32(&f.reconstructed, 1) } // chainSlice contains chains of certificates. Applying Sort will sort in // order of the first certificate in the chain, i.e. their leaf certificate. // If two chains have equal leaf certificates, they will be sorted by the // second certificate in the chain, and so on. By this logic, a chain that // is a subchain of another chain beginning at the leaf of the other chain, // will come before the other chain after sorting. // // Example: // // Before sorting: // A -> B -> C // D // A -> C // A -> B // // After sorting: // A -> B // A -> B -> C // A -> C // D type chainSlice struct { chains [][]*x509.Certificate } func min(a, b int) int { if a < b { return a } return b } // Len implements sort.Sort(data Interface) for chainSlice. func (c chainSlice) Len() int { return len(c.chains) } // Less implements sort.Sort interface for chainSlice. func (c chainSlice) Less(i, j int) bool { chi := c.chains[i] chj := c.chains[j] for k := 0; k < min(len(chi), len(chj)); k++ { if !chi[k].Equal(chj[k]) { return bytes.Compare(chi[k].Raw, chj[k].Raw) < 0 } } return len(chi) < len(chj) } // Swap implements sort.Sort interface for chainSlice. func (c chainSlice) Swap(i, j int) { t := c.chains[i] c.chains[i] = c.chains[j] c.chains[j] = t } // removeSuperChains will remove super chains from the list of chains passed to // it. A super chain is considered to be a chain whose first x certificates are // included in the list somewhere else as a whole chain. Put another way, if // there exists a chain A in the list, and another chain B that is A with some // additional certificates chained onto the end, B is a super chain of A // (and A is a subchain of B). // // Examples: // 1. A -> B -> C is a super chain of A -> B, and both are super chains of A. // 2. Z -> A -> B is not a super chain of A -> B, as A -> B is not at the // beginning of Z -> A -> B. // 3. Calling removeSuperChains on: // A -> B -> C // A -> C // A -> B // A -> C -> D // will return: // A -> B // A -> C // 4. Calling removeSuperChains on: // A -> B -> C // A -> C // A -> B // A -> C -> D // A // will return: // A func removeSuperChains(chains [][]*x509.Certificate) [][]*x509.Certificate { // Sort the list of chains using the sorting algorithm described above. // This will result in chains and their super chains being grouped together // in the list, with the shortest chain listed first in the group (i.e. a // chain, and then all its super chains - if any - listed directly after // that chain). c := chainSlice{chains: chains} sort.Sort(c) var retChains [][]*x509.Certificate NextChain: // Start at the beginning of the list. for i := 0; i < len(c.chains); { // Add the chain to the list of chains to be returned. retChains = append(retChains, c.chains[i]) // Step past any super chains of the chain just added to the return list, // without adding them to the return list. We do not want super chains // of other chains in our return list. Due to the initial sort of the // list, any super chains of a chain will come directly after said chain. for j := i + 1; j < len(c.chains); j++ { for k := range c.chains[i] { // When a chain that is not a super chain of the chain most // recently added to the return list is found, move to that // chain and start over. if !c.chains[i][k].Equal(c.chains[j][k]) { i = j continue NextChain } } } break } return retChains } func (f *Fixer) fixServer() { defer f.wg.Done() for fix := range f.toFix { atomic.AddUint32(&f.active, 1) chains, ferrs := fix.handleChain() f.updateCounters(chains, ferrs) for _, ferr := range ferrs { f.errors <- ferr } // If handleChain() outputs valid chains that are subchains of other // valid chains, (where the subchains start at the leaf) // e.g. A -> B -> C and A -> B -> C -> D, only forward on the shorter // of the chains. for _, chain := range removeSuperChains(chains) { f.chains <- chain atomic.AddUint32(&f.validChainsOut, 1) } atomic.AddUint32(&f.active, ^uint32(0)) } } func (f *Fixer) newFixServerPool(workerCount int) { for i := 0; i < workerCount; i++ { f.wg.Add(1) go f.fixServer() } } func (f *Fixer) logStats() { t := time.NewTicker(time.Second) go func() { for range t.C { log.Printf("fixers: %d active, %d reconstructed, "+ "%d not reconstructed, %d fixed, %d not fixed, "+ "%d valid chains produced, %d valid chains sent", f.active, f.reconstructed, f.notReconstructed, f.fixed, f.notFixed, f.validChainsProduced, f.validChainsOut) } }() } // NewFixer creates a new asynchronous fixer and starts up a pool of // workerCount workers. Errors are pushed to the errors channel, and fixed // chains are pushed to the chains channel. client is used to try to get any // missing certificates that are needed when attempting to fix chains. func NewFixer(workerCount int, chains chan<- []*x509.Certificate, errors chan<- *FixError, client *http.Client, logStats bool) *Fixer { f := &Fixer{ toFix: make(chan *toFix), chains: chains, errors: errors, cache: newURLCache(client, logStats), } f.newFixServerPool(workerCount) if logStats { f.logStats() } return f } google-certificate-transparency-go-2308f62/fixchain/fixer_test.go000066400000000000000000000172341462611535200251440ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "net/http" "sync" "testing" "github.com/google/certificate-transparency-go/x509" ) // NewFixer() test func TestNewFixer(t *testing.T) { chains := make(chan []*x509.Certificate) errors := make(chan *FixError) var expectedChains [][]string var expectedErrs []errorType for _, test := range handleChainTests { expectedChains = append(expectedChains, test.expectedChains...) expectedErrs = append(expectedErrs, test.expectedErrs...) } var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() testChains(t, 0, expectedChains, chains) }() go func() { defer wg.Done() testErrors(t, 0, expectedErrs, errors) }() f := NewFixer(10, chains, errors, &http.Client{Transport: &testRoundTripper{}}, false) for _, test := range handleChainTests { f.QueueChain(GetTestCertificateFromPEM(t, test.cert), extractTestChain(t, 0, test.chain), extractTestRoots(t, 0, test.roots)) } f.Wait() close(chains) close(errors) wg.Wait() } // Fixer.fixServer() test func TestFixServer(t *testing.T) { cache := &urlCache{cache: newLockedCache(), client: &http.Client{Transport: &testRoundTripper{}}} f := &Fixer{cache: cache} var wg sync.WaitGroup fixServerTests := handleChainTests // Pass chains to be fixed one at a time to fixServer and check the chain // and errors produced are correct. for i, fst := range fixServerTests { chains := make(chan []*x509.Certificate) errors := make(chan *FixError) f.toFix = make(chan *toFix) f.chains = chains f.errors = errors wg.Add(2) go func() { defer wg.Done() testChains(t, i, fst.expectedChains, chains) }() go func() { defer wg.Done() testErrors(t, i, fst.expectedErrs, errors) }() f.wg.Add(1) go f.fixServer() f.QueueChain(GetTestCertificateFromPEM(t, fst.cert), extractTestChain(t, i, fst.chain), extractTestRoots(t, i, fst.roots)) f.Wait() close(chains) close(errors) wg.Wait() } // Pass multiple chains to be fixed to fixServer and check the chain and // errors produced are correct. chains := make(chan []*x509.Certificate) errors := make(chan *FixError) f.toFix = make(chan *toFix) f.chains = chains f.errors = errors var expectedChains [][]string var expectedErrs []errorType for _, fst := range fixServerTests { expectedChains = append(expectedChains, fst.expectedChains...) expectedErrs = append(expectedErrs, fst.expectedErrs...) } i := len(fixServerTests) wg.Add(2) go func() { defer wg.Done() testChains(t, i, expectedChains, chains) }() go func() { defer wg.Done() testErrors(t, i, expectedErrs, errors) }() f.wg.Add(1) go f.fixServer() for _, fst := range fixServerTests { f.QueueChain(GetTestCertificateFromPEM(t, fst.cert), extractTestChain(t, i, fst.chain), extractTestRoots(t, i, fst.roots)) } f.Wait() close(chains) close(errors) wg.Wait() } func TestRemoveSuperChains(t *testing.T) { superChainsTests := []struct { chains [][]string expectedChains [][]string }{ { chains: [][]string{ {googleLeaf, thawteIntermediate}, {googleLeaf}, }, expectedChains: [][]string{ {"Google"}, }, }, { chains: [][]string{ {googleLeaf, verisignRoot}, {googleLeaf, thawteIntermediate}, {googleLeaf}, }, expectedChains: [][]string{ {"Google"}, }, }, { chains: [][]string{ {googleLeaf, thawteIntermediate, verisignRoot}, {googleLeaf, thawteIntermediate}, {googleLeaf}, }, expectedChains: [][]string{ {"Google"}, }, }, { chains: [][]string{ {googleLeaf, thawteIntermediate, verisignRoot}, {googleLeaf}, }, expectedChains: [][]string{ {"Google"}, }, }, { chains: [][]string{ {googleLeaf, thawteIntermediate, verisignRoot}, {googleLeaf, verisignRoot}, {googleLeaf, thawteIntermediate}, }, expectedChains: [][]string{ {"Google", "Thawte"}, {"Google", "VeriSign"}, }, }, { chains: [][]string{ {testLeaf, testIntermediate2}, {googleLeaf, thawteIntermediate, verisignRoot}, {testLeaf, testIntermediate2, testIntermediate1, testRoot}, {googleLeaf, verisignRoot}, {testLeaf, testIntermediate2, testIntermediate1}, {googleLeaf, thawteIntermediate}, {testLeaf, googleLeaf, thawteIntermediate, verisignRoot}, }, expectedChains: [][]string{ {"Google", "Thawte"}, {"Google", "VeriSign"}, {"Leaf", "Intermediate2"}, {"Leaf", "Google", "Thawte", "VeriSign"}, }, }, } for i, test := range superChainsTests { var chains [][]*x509.Certificate for _, chain := range test.chains { chains = append(chains, extractTestChain(t, i, chain)) } matchTestChainList(t, i, test.expectedChains, removeSuperChains(chains)) } } // Fixer.updateCounters() tests func TestUpdateCounters(t *testing.T) { counterTests := []struct { errors []errorType reconstructed uint32 notReconstructed uint32 fixed uint32 notFixed uint32 }{ {[]errorType{}, 1, 0, 0, 0}, {[]errorType{VerifyFailed}, 0, 1, 1, 0}, {[]errorType{VerifyFailed, FixFailed}, 0, 1, 0, 1}, {[]errorType{ParseFailure}, 1, 0, 0, 0}, {[]errorType{ParseFailure, VerifyFailed}, 0, 1, 1, 0}, {[]errorType{ParseFailure, VerifyFailed, FixFailed}, 0, 1, 0, 1}, } for i, test := range counterTests { f := &Fixer{} var ferrs []*FixError for _, err := range test.errors { ferrs = append(ferrs, &FixError{Type: err}) } f.updateCounters(nil, ferrs) if f.reconstructed != test.reconstructed { t.Errorf("#%d: Incorrect value for reconstructed, wanted %d, got %d", i, test.reconstructed, f.reconstructed) } if f.notReconstructed != test.notReconstructed { t.Errorf("#%d: Incorrect value for notReconstructed, wanted %d, got %d", i, test.notReconstructed, f.notReconstructed) } if f.fixed != test.fixed { t.Errorf("#%d: Incorrect value for fixed, wanted %d, got %d", i, test.fixed, f.fixed) } if f.notFixed != test.notFixed { t.Errorf("#%d: Incorrect value for notFixed, wanted %d, got %d", i, test.notFixed, f.notFixed) } } } // Fixer.QueueChain() tests type fixerQueueTest struct { cert string chain []string roots []string dchain []string } var fixerQueueTests = []fixerQueueTest{ { cert: googleLeaf, chain: []string{verisignRoot, thawteIntermediate}, roots: []string{verisignRoot}, dchain: []string{"VeriSign", "Thawte"}, }, { cert: googleLeaf, chain: []string{verisignRoot, verisignRoot, thawteIntermediate}, roots: []string{verisignRoot}, dchain: []string{"VeriSign", "Thawte"}, }, { cert: googleLeaf, roots: []string{verisignRoot}, dchain: []string{}, }, } func testFixerQueueChain(t *testing.T, i int, qt *fixerQueueTest, f *Fixer) { defer f.wg.Done() fix := <-f.toFix // Check the deduped chain matchTestChain(t, i, qt.dchain, fix.chain.certs) } func TestFixerQueueChain(t *testing.T) { ch := make(chan *toFix) defer close(ch) f := &Fixer{toFix: ch} for i, qt := range fixerQueueTests { f.wg.Add(1) go testFixerQueueChain(t, i, &qt, f) chain := extractTestChain(t, i, qt.chain) roots := extractTestRoots(t, i, qt.roots) f.QueueChain(GetTestCertificateFromPEM(t, qt.cert), chain, roots) f.wg.Wait() } } google-certificate-transparency-go-2308f62/fixchain/functions_test.go000066400000000000000000000137331462611535200260370ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "bytes" "context" "encoding/json" "fmt" "log" "strings" "testing" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" "github.com/google/certificate-transparency-go/x509util" ) type nilLimiter struct{} func (l *nilLimiter) Wait(ctx context.Context) error { return nil } func newNilLimiter() *nilLimiter { return &nilLimiter{} } type bytesReadCloser struct { *bytes.Reader } func (rc bytesReadCloser) Close() error { return nil } // GetTestCertificateFromPEM returns an x509.Certificate from a certificate in // PEM format for testing purposes. Any errors in the PEM decoding process are // reported to the testing framework. func GetTestCertificateFromPEM(t *testing.T, pemBytes string) *x509.Certificate { cert, err := x509util.CertificateFromPEM([]byte(pemBytes)) if x509.IsFatal(err) { t.Errorf("Failed to parse leaf: %s", err) } return cert } func nameToKey(name *pkix.Name) string { return fmt.Sprintf("%s/%s/%s/%s", strings.Join(name.Country, ","), strings.Join(name.Organization, ","), strings.Join(name.OrganizationalUnit, ","), name.CommonName) } func chainToDebugString(chain []*x509.Certificate) string { var chainStr string for _, cert := range chain { if len(chainStr) > 0 { chainStr += " -> " } chainStr += nameToKey(&cert.Subject) } return chainStr } func matchTestChainList(t *testing.T, i int, want [][]string, got [][]*x509.Certificate) { if len(want) != len(got) { t.Errorf("#%d: Wanted %d chains, got back %d", i, len(want), len(got)) } seen := make([]bool, len(want)) NextOutputChain: for _, chain := range got { TryNextExpected: for j, expChain := range want { if seen[j] { continue } if len(chain) != len(expChain) { continue } for k, cert := range chain { if !strings.Contains(nameToKey(&cert.Subject), expChain[k]) { continue TryNextExpected } } seen[j] = true continue NextOutputChain } t.Errorf("#%d: No expected chain matched output chain %s", i, chainToDebugString(chain)) } for j, val := range seen { if !val { t.Errorf("#%d: No output chain matched expected chain %s", i, strings.Join(want[j], " -> ")) } } } func matchTestErrorList(t *testing.T, i int, want []errorType, got []*FixError) { if len(want) != len(got) { t.Errorf("#%d: Wanted %d errors, got back %d", i, len(want), len(got)) } seen := make([]bool, len(want)) NextOutputErr: for _, err := range got { for j, expErr := range want { if seen[j] { continue } if err.Type == expErr { seen[j] = true continue NextOutputErr } } t.Errorf("#%d: No expected error matched output error %s", i, err.TypeString()) } for j, val := range seen { if !val { t.Errorf("#%d: No output error matched expected error %s", i, FixError{Type: want[j]}.TypeString()) } } } func matchTestChain(t *testing.T, i int, want []string, got []*x509.Certificate) { if len(got) != len(want) { t.Errorf("#%d: Expected a chain of length %d, got one of length %d", i, len(want), len(got)) return } if want != nil { for j, cert := range got { if !strings.Contains(nameToKey(&cert.Subject), want[j]) { t.Errorf("#%d: Chain does not match expected chain at position %d", i, j) } } } } func matchTestRoots(t *testing.T, i int, want []string, got *x509.CertPool) { if len(got.Subjects()) != len(want) { t.Errorf("#%d: received %d roots, expected %d", i, len(got.Subjects()), len(want)) } testRoots := extractTestChain(t, i, want) seen := make([]bool, len(testRoots)) NextRoot: for _, rootSub := range got.Subjects() { for j, testRoot := range testRoots { if seen[j] { continue } if bytes.Equal(rootSub, testRoot.RawSubject) { seen[j] = true continue NextRoot } } t.Errorf("#%d: No expected root matches one of the output roots", i) } for j, val := range seen { if !val { t.Errorf("#%d: No output root matches expected root %s", i, nameToKey(&testRoots[j].Subject)) } } } func extractTestChain(t *testing.T, _ int, testChain []string) []*x509.Certificate { var chain []*x509.Certificate for _, cert := range testChain { chain = append(chain, GetTestCertificateFromPEM(t, cert)) } return chain } func extractTestRoots(t *testing.T, i int, testRoots []string) *x509.CertPool { roots := x509.NewCertPool() for j, cert := range testRoots { ok := roots.AppendCertsFromPEM([]byte(cert)) if !ok { t.Errorf("#%d: Failed to parse root #%d", i, j) } } return roots } func testChains(t *testing.T, i int, expectedChains [][]string, chains chan []*x509.Certificate) { var allChains [][]*x509.Certificate for chain := range chains { allChains = append(allChains, chain) } matchTestChainList(t, i, expectedChains, allChains) } func testErrors(t *testing.T, i int, expectedErrs []errorType, errors chan *FixError) { var allFerrs []*FixError for ferr := range errors { allFerrs = append(allFerrs, ferr) } matchTestErrorList(t, i, expectedErrs, allFerrs) } func stringRootsToJSON(roots []string) []byte { type Roots struct { Certs [][]byte `json:"certificates"` } var r Roots for _, root := range roots { cert, err := x509util.CertificateFromPEM([]byte(root)) if err != nil { log.Fatalf("Failed to parse certificate: %s", err) } r.Certs = append(r.Certs, cert.Raw) } b, err := json.Marshal(r) if err != nil { log.Fatalf("Can't marshal JSON: %s", err) } return b } google-certificate-transparency-go-2308f62/fixchain/hash.go000066400000000000000000000052161462611535200237100ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "crypto/sha256" "sort" "github.com/google/certificate-transparency-go/x509" ) const hashSize = sha256.Size var newHash = sha256.New func hash(c *x509.Certificate) (hash [hashSize]byte) { copy(hash[:], newHash().Sum(c.Raw)) return } func hashChain(ch []*x509.Certificate) (hash [hashSize]byte) { h := newHash() for _, c := range ch { h.Write(newHash().Sum(c.Raw)) } copy(hash[:], h.Sum(nil)) return } // hashBag hashes all of the certs in the chain, irrespective of their order. // Chains containing the same certs in the same order with no duplicates will // result in the same hash. Chains containing the same certs in different orders // with no duplicates will result in the same hash. Chains containing the same // certs (either in the same order or in different orders) that contain exactly // the same duplicated certs, will result in the same hash. If chains contain // the same certs (either in the same order or in different orders) and some // certs are duplicated, but the specific certs that are duplicated differ // and/or the number of times they are duplicated differ, these chains will // result in different hashes. func hashBag(chain []*x509.Certificate) [hashSize]byte { b := bag{certs: make([]*x509.Certificate, len(chain))} copy(b.certs, chain) sort.Sort(b) return hashChain(b.certs) } // bag is a collection of certificates that can contain duplicates. // Applying sort will order them by their raw representation. type bag struct { certs []*x509.Certificate } // Len implements sort.Sort(data Interface) for bag. func (b bag) Len() int { return len(b.certs) } // Less implements sort.Sort(data Interface) for bag. func (b bag) Less(i, j int) bool { ci := b.certs[i].Raw cj := b.certs[j].Raw if len(ci) != len(cj) { return len(ci) < len(cj) } for n := range ci { if ci[n] < cj[n] { return true } if ci[n] > cj[n] { return false } } return false } // Swap implements sort.Sort(data Interface) for bag. func (b bag) Swap(i, j int) { t := b.certs[i] b.certs[i] = b.certs[j] b.certs[j] = t } google-certificate-transparency-go-2308f62/fixchain/hash_test.go000066400000000000000000000030411462611535200247410ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "testing" ) func TestHashBag(t *testing.T) { hashBagTests := []struct { certList1 []string certList2 []string expEqual bool errMsg string }{ { []string{googleLeaf}, []string{thawteIntermediate}, false, "hash match between Bags containing different certs", }, { []string{googleLeaf, thawteIntermediate}, []string{thawteIntermediate, googleLeaf}, true, "hash mismatch between Bags containing the same certs", }, { []string{googleLeaf, thawteIntermediate}, []string{thawteIntermediate, googleLeaf, thawteIntermediate}, false, "hash match between Bags containing the same certs, but one with duplicates", }, } for i, test := range hashBagTests { certList1 := extractTestChain(t, i, test.certList1) certList2 := extractTestChain(t, i, test.certList2) if (hashBag(certList1) == hashBag(certList2)) != test.expEqual { t.Errorf("#%d: %s", i, test.errMsg) } } } google-certificate-transparency-go-2308f62/fixchain/logger.go000066400000000000000000000150201462611535200242360ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "context" "fmt" "log" "sync" "sync/atomic" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/x509" ) // Limiter is an interface to allow different rate limiters to be used with the // Logger. type Limiter interface { Wait(context.Context) error } // Logger contains methods to asynchronously log certificate chains to a // Certificate Transparency log and properties to store information about each // attempt that is made to post a certificate chain to said log. type Logger struct { ctx context.Context client client.AddLogClient roots *x509.CertPool toPost chan *toPost errors chan<- *FixError active uint32 queued uint32 // How many chains have been queued to be posted. posted uint32 // How many chains have been posted. reposted uint32 // How many chains for an already-posted cert have been queued. chainReposted uint32 // How many chains have been queued again. // Note that wg counts the number of active requests, not // active servers, because we can't close it to signal the // end, because of retries. wg sync.WaitGroup limiter Limiter postCertCache *lockedMap postChainCache *lockedMap } // IsPosted tells the caller whether a chain for the given certificate has // already been successfully posted to the log by this Logger. func (l *Logger) IsPosted(cert *x509.Certificate) bool { return l.postCertCache.get(hash(cert)) } // QueueChain adds the given chain to the queue to be posted to the log. func (l *Logger) QueueChain(chain []*x509.Certificate) { if chain == nil { return } atomic.AddUint32(&l.queued, 1) // Has a chain for the cert this chain if for already been successfully //posted to the log by this Logger? h := hash(chain[0]) // Chains are cert -> root if l.postCertCache.get(h) { atomic.AddUint32(&l.reposted, 1) return // Don't post chain for a cert that has already had a chain posted. } // If we assume all chains for the same cert are equally // likely to succeed, then we could mark the cert as posted // here. However, bugs might cause a log to refuse one chain // and accept another, so try each unique chain. // Has this Logger already tried to post this chain? h = hashChain(chain) if l.postChainCache.get(h) { atomic.AddUint32(&l.chainReposted, 1) return } l.postChainCache.set(h, true) l.postToLog(&toPost{chain: chain}) } // Wait for all of the active requests to finish being processed. func (l *Logger) Wait() { l.wg.Wait() } // RootCerts returns the root certificates that the log accepts. func (l *Logger) RootCerts() *x509.CertPool { if l.roots == nil { // Retry if unable to get roots. for i := 0; i < 10; i++ { roots, err := l.getRoots() if err == nil { l.roots = roots return l.roots } log.Println(err) } log.Fatalf("Can't get roots for log") } return l.roots } func (l *Logger) getRoots() (*x509.CertPool, error) { roots, err := l.client.GetAcceptedRoots(l.ctx) if err != nil { return nil, fmt.Errorf("failed to get roots: %s", err) } ret := x509.NewCertPool() for _, root := range roots { r, err := x509.ParseCertificate(root.Data) if x509.IsFatal(err) { return nil, fmt.Errorf("can't parse certificate: %s %#v", err, root.Data) } ret.AddCert(r) } return ret, nil } type toPost struct { chain []*x509.Certificate } // postToLog() is used during the initial queueing of chains to avoid spinning // up an excessive number of goroutines, and unnecessarily using up memory. If // asyncPostToLog() was called instead, then every time a new chain was queued, // a new goroutine would be created, each holding their own chain - regardless // of whether there were postServers available to process them or not. If a // large number of chains were queued in a short period of time, this could // lead to a large number of these additional goroutines being created, // resulting in excessive memory usage. func (l *Logger) postToLog(p *toPost) { l.wg.Add(1) // Add to the wg as we are adding a new active request to the logger queue. l.toPost <- p } func (l *Logger) postChain(p *toPost) { h := hash(p.chain[0]) if l.postCertCache.get(h) { atomic.AddUint32(&l.reposted, 1) return } derChain := make([]ct.ASN1Cert, 0, len(p.chain)) for _, cert := range p.chain { derChain = append(derChain, ct.ASN1Cert{Data: cert.Raw}) } if err := l.limiter.Wait(l.ctx); err != nil { log.Println(err) } atomic.AddUint32(&l.posted, 1) _, err := l.client.AddChain(l.ctx, derChain) if err != nil { l.errors <- &FixError{ Type: LogPostFailed, Chain: p.chain, Error: fmt.Errorf("add-chain failed: %s", err), } return } // If the post was successful, cache. l.postCertCache.set(h, true) } func (l *Logger) postServer() { for { c := <-l.toPost atomic.AddUint32(&l.active, 1) l.postChain(c) atomic.AddUint32(&l.active, ^uint32(0)) l.wg.Done() } } func (l *Logger) logStats() { t := time.NewTicker(time.Second) go func() { for range t.C { log.Printf("posters: %d active, %d posted, %d queued, %d certs requeued, %d chains requeued", l.active, l.posted, l.queued, l.reposted, l.chainReposted) } }() } // NewLogger creates a new asynchronous logger to log chains to the // Certificate Transparency log at the given url. It starts up a pool of // workerCount workers. Errors are pushed to the errors channel. client is // used to post the chains to the log. func NewLogger(ctx context.Context, workerCount int, errors chan<- *FixError, client client.AddLogClient, limiter Limiter, logStats bool) *Logger { l := &Logger{ ctx: ctx, client: client, errors: errors, toPost: make(chan *toPost), postCertCache: newLockedMap(), postChainCache: newLockedMap(), limiter: limiter, } l.RootCerts() // Start post server pool. for i := 0; i < workerCount; i++ { go l.postServer() } if logStats { l.logStats() } return l } google-certificate-transparency-go-2308f62/fixchain/logger_test.go000066400000000000000000000153201462611535200253000ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "context" "net/http" "sync" "testing" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" ) // NewLogger() test func TestNewLogger(t *testing.T) { ctx := context.Background() // Test single chain posts. for i, test := range postTests { errors := make(chan *FixError) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() testErrors(t, i, test.expectedErrs, errors) }() c := &http.Client{Transport: &postTestRoundTripper{t: t, test: &test, testIndex: i}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := NewLogger(ctx, 1, errors, logClient, newNilLimiter(), false) l.QueueChain(extractTestChain(t, i, test.chain)) l.Wait() close(l.errors) wg.Wait() // Check logger caching. if test.chain != nil { if test.ferr.Type == None && !l.postCertCache.get(hash(GetTestCertificateFromPEM(t, test.chain[0]))) { t.Errorf("#%d: leaf certificate not cached", i) } if !l.postChainCache.get(hashChain(extractTestChain(t, i, test.chain))) { t.Errorf("#%d: chain not cached", i) } } } } // NewLogger() test func TestNewLoggerCaching(t *testing.T) { // Test logging multiple chains by looking at caching. ctx := context.Background() newLoggerTest := struct { url string chains [][]string expectedErrs []errorType }{ "https://ct.googleapis.com/pilot", [][]string{ {googleLeaf, thawteIntermediate, verisignRoot}, {googleLeaf, thawteIntermediate, verisignRoot}, {googleLeaf, thawteIntermediate}, {testLeaf, testIntermediate2, testIntermediate1, testRoot}, }, []errorType{}, } errors := make(chan *FixError) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() testErrors(t, 0, newLoggerTest.expectedErrs, errors) }() c := &http.Client{Transport: &newLoggerTestRoundTripper{}} logClient, err := client.New(newLoggerTest.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := NewLogger(ctx, 5, errors, logClient, newNilLimiter(), false) for _, chain := range newLoggerTest.chains { l.QueueChain(extractTestChain(t, 0, chain)) } l.Wait() close(l.errors) wg.Wait() // Check logger caching. seen := make(map[[hashSize]byte]bool) for i, chain := range newLoggerTest.chains { leafHash := hash(GetTestCertificateFromPEM(t, chain[0])) if !l.postCertCache.get(leafHash) { t.Errorf("Chain %d: leaf certificate not cached", i) } if !seen[leafHash] && !l.postChainCache.get(hashChain(extractTestChain(t, 0, chain))) { t.Errorf("Chain %d: chain not cached", i) } seen[leafHash] = true } } // Logger.postServer() test func TestPostServer(t *testing.T) { ctx := context.Background() for i, test := range postTests { errors := make(chan *FixError) c := &http.Client{Transport: &postTestRoundTripper{t: t, test: &test, testIndex: i}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ ctx: ctx, client: logClient, toPost: make(chan *toPost), errors: errors, limiter: newNilLimiter(), postCertCache: newLockedMap(), postChainCache: newLockedMap(), } var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() testErrors(t, i, test.expectedErrs, errors) }() go l.postServer() l.QueueChain(extractTestChain(t, i, test.chain)) l.Wait() close(l.errors) wg.Wait() } } // Logger.IsPosted() test func TestIsPosted(t *testing.T) { isPostedTests := []struct { cert string expected bool }{ { googleLeaf, true, }, { megaLeaf, true, }, { testLeaf, false, }, { testC, false, }, } l := &Logger{postCertCache: newLockedMap()} l.postCertCache.set(hash(GetTestCertificateFromPEM(t, googleLeaf)), true) l.postCertCache.set(hash(GetTestCertificateFromPEM(t, megaLeaf)), true) l.postCertCache.set(hash(GetTestCertificateFromPEM(t, testLeaf)), false) for i, test := range isPostedTests { if l.IsPosted(GetTestCertificateFromPEM(t, test.cert)) != test.expected { t.Errorf("#%d: received %t, expected %t", i, !test.expected, test.expected) } } } // Logger.QueueChain() tests type loggerQueueTest struct { chain []string expectedChain []string } var loggerQueueTests = []loggerQueueTest{ { chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, expectedChain: []string{"Google", "Thawte", "VeriSign"}, }, { // Add the same chain a second time to test chain caching. // Note that if chain caching isn't working correctly, the test will hang. chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, }, } func testLoggerQueueChain(t *testing.T, i int, qt *loggerQueueTest, l *Logger) { defer l.wg.Done() if qt.expectedChain != nil { post := <-l.toPost matchTestChain(t, i, qt.expectedChain, post.chain) l.wg.Done() // Required as logger wg is incremented internally every time a toPost is added to the queue. } } func TestLoggerQueueChain(t *testing.T) { ch := make(chan *toPost) defer close(ch) l := &Logger{toPost: ch, postCertCache: newLockedMap(), postChainCache: newLockedMap()} for i, qt := range loggerQueueTests { l.wg.Add(1) go testLoggerQueueChain(t, i, &qt, l) chain := extractTestChain(t, i, qt.chain) l.QueueChain(chain) l.wg.Wait() } } // Logger.RootCerts() test func TestRootCerts(t *testing.T) { ctx := context.Background() rootCertsTests := []struct { url string expectedRoots []string }{ { "https://ct.googleapis.com/pilot", []string{verisignRoot, comodoRoot}, // These are not the actual roots for the pilot CT log, this is just for testing purposes. }, } for i, test := range rootCertsTests { c := &http.Client{Transport: &rootCertsTestRoundTripper{}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ctx: ctx, client: logClient} roots := l.RootCerts() matchTestRoots(t, i, test.expectedRoots, roots) } } google-certificate-transparency-go-2308f62/fixchain/replacements.go000066400000000000000000000333001462611535200254420ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "encoding/pem" "log" "github.com/google/certificate-transparency-go/x509" ) // Go has no PKCS#7 implementation. Rather than fix that, manually // replace the few PKCS#7 URLs we know of. var replacements = map[string][]string{ "http://gca.nat.gov.tw/repository/Certs/IssuedToThisCA.p7b": { // subject=/C=TW/O=\xE8\xA1\x8C\xE6\x94\xBF\xE9\x99\xA2/OU=\xE6\x94\xBF\xE5\xBA\x9C\xE6\x86\x91\xE8\xAD\x89\xE7\xAE\xA1\xE7\x90\x86\xE4\xB8\xAD\xE5\xBF\x83 // issuer=/C=TW/O=Government Root Certification Authority `-----BEGIN CERTIFICATE----- MIIFJTCCAw2gAwIBAgIQCI3SljuLYpwZTjIA2nfOLDANBgkqhkiG9w0BAQsFADA/ MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj YXRpb24gQXV0aG9yaXR5MB4XDTEzMDEzMTAzMjIzNFoXDTMzMDEzMTAzMjIzNFow RDELMAkGA1UEBhMCVFcxEjAQBgNVBAoMCeihjOaUv+mZojEhMB8GA1UECwwY5pS/ 5bqc5oaR6K2J566h55CG5Lit5b+DMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAtX7xPZUtp5iBGQvqYghJUoLeyCJJoaTcc1bcOGHij64WUBYpDu8KKEQK R1y3zjcDXrLcZX483tmNs92DXSNuBHlx+we1aFyuLpKQVCji97ys3KeMxAEcaXqo 3cZu8nY3g//zkvX80G4RoCyDR86Z420R3mb0GlVw/9TEK8+oduZqAArEdfionpbE K5zZ/8qaaHafgqMBQGuzfccDKLoWRcTzu3S0IvOpVU6pcB0rJOtc4F7c16tQdXfo a8sjfcveKKbUQF6AklwugRufHdLqEVpOiGRcDPaHtT6SHJ7D/t+A/rAXMPidcksQ rea/E+5+lehqEMHSA/gSLa9Ph+/gDQIDAQABo4IBFjCCARIwHwYDVR0jBBgwFoAU 1Wcd4Jx6LJzLxZjnHQcmKobsdM0wHQYDVR0OBBYEFNEYZ8NX/hKakWtfXzHqPsKE h/u9MA4GA1UdDwEB/wQEAwIBBjAUBgNVHSAEDTALMAkGB2CGdmUAAwMwEgYDVR0T AQH/BAgwBgEB/wIBADA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vZ3JjYS5uYXQu Z292LnR3L3JlcG9zaXRvcnkvQ1JMMi9DQS5jcmwwVgYIKwYBBQUHAQEESjBIMEYG CCsGAQUFBzAChjpodHRwOi8vZ3JjYS5uYXQuZ292LnR3L3JlcG9zaXRvcnkvQ2Vy dHMvSXNzdWVkVG9UaGlzQ0EucDdiMA0GCSqGSIb3DQEBCwUAA4ICAQBuDj29K1o3 rfT72lhocx0vr18PUI5OEVfiMn+cwE8al5UdPgYAMQL4YIdA1rmL5QResEaC03d7 jFKF1fnGf7rd0k5O47iAa7THDQFtVOks1djLfNecn1l4pdLODWGRNy+DbbqAl87d at2HSP5OEOl3nt8TxUVRsJx9TDx1IZC+RhUTT8ryalhlJ9UbxORjqbL3C7mMhviY B9aA4aV6AFa1oAsI+LeXIB9xxmk8V8kzX1VhJ00buIAIjScIhvI39zoeF7z39hzy Gw9+Av/AnbC4npDvvaLxIhs75LD1Tuh5WY4lk0+/PzdhrK5R0+YaOEoEvpiZljeZ QuXJVZ08Re6Omb5XYKZ9hjtp+wAIH97k7spxSOFmP76WBy/5o22vxosfvybTxuM3 GFih8XlhoL6UYQ2e29WHW9Mj5yDN00TRp9CYWw7p5sS09PQitGKqYx7AYhJnNBy5 mz4uHLm4nQVI/3jhDb9Xgr+3UHMjz4LM8TQVh2YEDYBYkgH35WhK6pY852yAvIat usT8CveFOCjr2uJBXaBgmBr4I5/1oJypzZLmP65VtxSMtA5cmgooVRGAe+QrYufJ lGZiIUjmkqpNzh5q6oShkzqJPpqRviug2oZXQb9q9Qgj4zkr8KA5NkYVG+KNWR5V LD9SyuP1AJcZmxUKQtDEZJCJtISXfybg0A== -----END CERTIFICATE-----`, // subject=/C=TW/O=\xE8\xA1\x8C\xE6\x94\xBF\xE9\x99\xA2/OU=\xE6\x94\xBF\xE5\xBA\x9C\xE6\x86\x91\xE8\xAD\x89\xE7\xAE\xA1\xE7\x90\x86\xE4\xB8\xAD\xE5\xBF\x83 //issuer=/C=TW/O=Government Root Certification Authority `-----BEGIN CERTIFICATE----- MIIFJTCCAw2gAwIBAgIRAP+94tm8qUrtFSYcQfB4flUwDQYJKoZIhvcNAQEFBQAw PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0wMzAzMDMwNjUxMjNaFw0yMzAzMDMwNjUxMjNa MEQxCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAnooYzmlL/pmaIxITAfBgNVBAsMGOaU v+W6nOaGkeitieeuoeeQhuS4reW/gzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAJXm+CDDPby1h9VhQFkpeEceeJcodEfbO8og0FgZj/cwtbnJbFgpl1KD GvbBO+GvpxLI18c/T4bMEpDlpugcvlF8tIXT5M35lVTByx7NtT5myohevmppbsnK 1wRC1WwYxWHG3tIRZQdX/mKV0tE1LMDHylTeJWbA+fTPmapiTy7vopSFSmx7zEAI 9x+6p3bWfZ+A/MQh4SC0gUM6sZhlNH13z3VCaK670xHoTryQi5Yv+SuHJz8tIt31 xty/nwrpS8eM2lzonL0zDrHf/wkTIpy+ZU+J3J9gi1l1DTUyEL1thj5udyAnAYBy aDqT5t3Af7X1DNthPknckzOHz3bOH+0CAwEAAaOCARUwggERMB8GA1UdIwQYMBaA FMzM78wpYKQ7sZK2PPoyYo+sJRU7MB0GA1UdDgQWBBTk3BdvIqrO+MghGtKrzlOO TtoYfDAOBgNVHQ8BAf8EBAMCAQYwFAYDVR0gBA0wCzAJBgdghnZlAAMDMBIGA1Ud EwEB/wQIMAYBAf8CAQAwPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2dyY2EubmF0 Lmdvdi50dy9yZXBvc2l0b3J5L0NSTC9DQS5jcmwwVgYIKwYBBQUHAQEESjBIMEYG CCsGAQUFBzAChjpodHRwOi8vZ3JjYS5uYXQuZ292LnR3L3JlcG9zaXRvcnkvQ2Vy dHMvSXNzdWVkVG9UaGlzQ0EucDdiMA0GCSqGSIb3DQEBBQUAA4ICAQBrJPxFGTC3 I2z8AR23BfDjfrQ0Tr8D5ggx5GaPXSe2Re3nHW/mj3TEnpBFMDulZVo8PY8nPx7I OibTWRAYVkEk33HrAph+FoCAfLnv5BGIwa9KH2FyguebNv6djFnzJf0E6uBOWtN/ Tkl1NOh+Q1PuJ3gGoWt8YVr5UVu+Y84LD6lLf294Y6j76sAlsa4z0HDbVx28qiTP aPrDkWYIEb5hSW174uazQjiU4yFZMCzwUyGNCAjCoPVPqZ3FIdHdn21KbXJ0Oq87 7bCKtMy/SRUBCeVhDr7DbmELdDgHPjSXqLknMPnrI605jryzpw2nNPxOT/uO9e7n Cxg1S6wtfVtYUx491RGni1FjARZPh3xGizY+O6UxojdEP3jId4A+LJgn/SrijKTw m2kiQ3Q7ovMuMKGJdLHeiIOIcyVs7F+Fld2kkfKl7ztQ3mCanuQhvSLaaRSyAiMm Cfvc6LJHttAQv/8nFQgTvDAWSkGmba7PgBMeACkQE/5e3qd8Hr20gs7df0O1UCgD ST3oZmF0B2MwtBU4IDNRMmrDlx+ZvfE50guQd1jPSSeNrHyycgILnzLMjwMIEaRf P34C7KkB1WrJ+IUNL7Wsp4WxEaL8whKzJnaBNMPCDHz4tUuanBtDSuSu2oWYLTNx 58KYKSxTbOGbBt1cRf10CCR1/3YGE9C7rg== -----END CERTIFICATE-----`}, "http://crt.usertrust.com/AddTrustExternalCARoot.p7c": { // subject=/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root // issuer=/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root `-----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE-----`, // subject=/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root // issuer=/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC `-----BEGIN CERTIFICATE----- MIIEezCCA2OgAwIBAgIQftGpq77jb0bNa04pNJBW8zANBgkqhkiG9w0BAQUFADCB kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMG8xCzAJBgNVBAYT AlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0 ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC39xoz5vIABC05 4E5b7R+8bA/Ntfojts7emxEzl6QpTH2Tn71KvJPtAxrjj8/lbVBa1pcplFqAsEl6 2y6V/bjKvzc4LR4+kUGtcFbH8E8/6DKedMrIkFTpxl8PeJ2aQDwOrGGqXhSPnoeh alDc15pOrwWzpnGUnHGzUGAKxxOdOAeGAqjpqGkmGJCrTLBPI6s6T4TY386f4Wlv u9dC12tE5Met7m1BX3JacQg3s3llpFmglDf3AC8NwpJy2tA4ctsUqEXEXSp9t7TW xO6szRNEt8kr3UMAJfphuWlqWCMRt6czj1Z1WfXNKddGtworZbbTQm8Vsrh7++/p XVPVNFonAgMBAAGjge0wgeowHwYDVR0jBBgwFoAUUzLRs89/+uDxoF2FTpLSnkUd tE8wHQYDVR0OBBYEFK29mHo0tCb3+sQmVO8DveAky1QaMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MBEGA1UdIAQKMAgwBgYEVR0gADA9BgNVHR8ENjA0 MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVz ZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBADwlhyhsmL2dQhxeHmQPVn+W PPO582kaafSkCNQgTbHyYyfhnwFDN7CxeudxyHoh7qg1wZ3mvGizRoCaPQRyPC9I /eHMQncOsgU5pAD4NcKseMD9xxO8iyBNWjWvlMoysMZ50ZguO8JSRcGbtyYLywQa 9m6SROF8nMESeKYZAeLvYPt6V/MyKAa1uh2RGyhdZGpfU5wO1erMRb19RguvU0nG zIAYW1utsWITYE45WVHEpobL8Q1t3t0xC1+jB6D7PkaqSXMEfYoLsC9GYo7hvVBl KLHIdkr0IgMMVdT8DIdWfgtl74frfPclt80nTNs8CSlpF46LsEfo2mC3p2lm+ws= -----END CERTIFICATE-----`}, "http://grca.nat.gov.tw/repository/Certs/IssuedToThisCA.p7b": { // subject=/C=TW/O=Government Root Certification Authority // issuer=/C=TW/O=Government Root Certification Authority `-----BEGIN CERTIFICATE----- MIIGSTCCBDGgAwIBAgIQMlyJOyY4kQwld2TzSNCtpTANBgkqhkiG9w0BAQsFADA/ MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj YXRpb24gQXV0aG9yaXR5MB4XDTEyMDkyODA5MDcxMloXDTMyMTIwNTEzMjMzM1ow PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjggE/MIIBOzAf BgNVHSMEGDAWgBTVZx3gnHosnMvFmOcdByYqhux0zTAdBgNVHQ4EFgQUzMzvzClg pDuxkrY8+jJij6wlFTswDgYDVR0PAQH/BAQDAgEGMEAGA1UdIAQ5MDcwCQYHYIZ2 ZQADATAJBgdghnZlAAMCMAkGB2CGdmUAAwMwCQYHYIZ2ZQADBDAJBgdghnZlAAMA MA8GA1UdEwEB/wQFMAMBAf8wPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2dyY2Eu bmF0Lmdvdi50dy9yZXBvc2l0b3J5L0NSTDIvQ0EuY3JsMFYGCCsGAQUFBwEBBEow SDBGBggrBgEFBQcwAoY6aHR0cDovL2dyY2EubmF0Lmdvdi50dy9yZXBvc2l0b3J5 L0NlcnRzL0lzc3VlZFRvVGhpc0NBLnA3YjANBgkqhkiG9w0BAQsFAAOCAgEAQYW8 MPfAEZJTO5RgynxIFZVVN1cQCFU6/yF0WS66bEXKVWhz42TXQ9+vXX4R2CPyo1Xx 5Qx+kzOK4jb6LUuAqOYHw5R2QpXox5qjraCoAg9r+cFA3SrzhBe7Mhx+ktCaDaAS ++wxSUJm7Gu8S87grPQT1GKxy7wnCbtOmqmtixhXFu98tAcb5JtWoexD23DdKHlH tv4Ptn7qhIMd3RflM/fXx3UuiwhtCFWDda7PobuLDXOC9zn96R6Q43EpSgZAq6cU tKrUc3YB0mRmyRzekCNBcLtgthvK6gFTHBNIotZAkzCEWd6GUVP0V96qxcNP63HB GmSd5t5INJ4K//iJUVwqnM8Qiugs7eSu2ov9E2korPeUx2IwW7yJogC4GKsvOqY7 ijyWzfq+V9UeHaKBOyVH2BNUFd8yD1nxfUU7pPXTXH/Wlwi3Riuj1N3GegrbmiKN ILXwLBvwVDyDyksSaOUFLc8ELdzorAFC4/wpV8SFjz0cDg0SQ8KTfrhqnE/49T32 ePMmkuPG6RscGUmt/CHIBELrVuUg9fBsqHhXsEcLwPxpCa3zoz+65l3xC96C7dOs nILgAleXRJeNnNWztqPMjsmK3bskVfr7JZ/mUfTlkujH33gYl49q+04CcYgeP1zq Yu1iuTWLXNZzI2QG/3rs8Q9ZYXwyJReCoxC5d+I= -----END CERTIFICATE-----`, // subject=/C=TW/O=Government Root Certification Authority // issuer=/C=TW/O=Government Root Certification Authority `-----BEGIN CERTIFICATE----- MIIGSTCCBDGgAwIBAgIRAP7mJmeBQUNcX66p8ttLpm8wDQYJKoZIhvcNAQEFBQAw PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xMjA5MjgwOTEzMjlaFw0zMjEyMDUxMzIzMzNa MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlm aWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQC2/5c8gb4BWCQnr44BK9ZykjAyG1+bfNTUf+ihYHMwVxAA+lCWJP5Q5ow6ldFX eYTVZ1MMKoI+GFy4MCYa1l7GLbIEUQ7v3wxjR+vEEghRK5lxXtVpe+FdyXcdIOxW juVhYC386RyA3/pqg7sFtR4jEpyCygrzFB0g5AaPQySZn7YKk1pzGxY5vgW28Yyl ZJKPBeRcdvc5w88tvQ7Yy6gOMZvJRg9nU0MEj8iyyIOAX7ryD6uBNaIgIZfOD4k0 eA/PH07p+4woPN405+2f0mb1xcoxeNLOUNFggmOd4Ez3B66DNJ1JSUPUfr0t4urH cWWACOQ2nnlwCjyHKenkkpTqBpIpJ3jmrdc96QoLXvTg1oadLXLLi2RW5vSueKWg OTNYPNyoj420ai39iHPplVBzBN8RiD5C1gJ0+yzEb7xs1uCAb9GGpTJXA9ZN9E4K mSJ2fkpAgvjJ5E7LUy3Hsbbi08J1J265DnGyNPy/HE7CPfg26QrMWJqhGIZO4uGq s3NZbl6dtMIIr69c/aQCb/+4DbvVq9dunxpPkUDwH0ZVbaCSw4nNt7H/HLPLo5wK 4/7NqrwB7N1UypHdTxOHpPaY7/1J1lcqPKZc9mA3v9g+fk5oKiMyOr5u5CI9ByTP isubXVGzMNJxbc5Gim18SjNE2hIvNkvy6fFRCW3bapcOFwIDAQABo4IBPjCCATow HwYDVR0jBBgwFoAUzMzvzClgpDuxkrY8+jJij6wlFTswHQYDVR0OBBYEFNVnHeCc eiycy8WY5x0HJiqG7HTNMA4GA1UdDwEB/wQEAwIBBjBABgNVHSAEOTA3MAkGB2CG dmUAAwEwCQYHYIZ2ZQADAjAJBgdghnZlAAMDMAkGB2CGdmUAAwQwCQYHYIZ2ZQAD ADAPBgNVHRMBAf8EBTADAQH/MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9ncmNh Lm5hdC5nb3YudHcvcmVwb3NpdG9yeS9DUkwvQ0EuY3JsMFYGCCsGAQUFBwEBBEow SDBGBggrBgEFBQcwAoY6aHR0cDovL2dyY2EubmF0Lmdvdi50dy9yZXBvc2l0b3J5 L0NlcnRzL0lzc3VlZFRvVGhpc0NBLnA3YjANBgkqhkiG9w0BAQUFAAOCAgEAY5IV CK/CMXYyelsy8bSDPzGiiDG9ZcpXvJdDgV2gnU701Q7uJ52tGOk2pg0CL5WjChVz Vwmk34jXaBKAQZRF7ruOC3cU2HYfx3IKk8z+CWmVu4PMTmlR5VyPiZoqTfWuiFUP 7p+krL/b97HWZN2G0ein+++/fcdJnTRyxKDBJIpiwXoNjTgN0QYfSRMFAATng3c7 clDtSDYiJlnM0iecu2f1xOWSXwpN9zZQ9KiBKsPS8D5WKBBwLvqq4pwxEHbWJael UwOzdfW5+P8hzEeuZ7g3BoeoxuPPJACZjgvYUf6Lp1N9HitiwlBYwt1Sk/hggT4r ykRCghvY2BN0J5Aago8WAtiPH393yLM0PUAPl260C6H4qJCfft+v1LPFRzimukO0 8jZDtJinIKjN4ZNiBO4/wpvpBpAsRZQkbwvMeQKqOhEduH+deDg1LgTidtWapK6D H4OADQnWfsH96MWrA2OQAU/3n7SGuwDsT3I8oYwXCZ4Za0FMJIcftZuA8soU7bHo Tvmiar3DZrvPZE6uq0dHboxVt/4Qsogv+3PMRkqV6X8lk18hzkClEvToQh4xUW2R wnXMUCSjca4A59fi12K6chOo3hv0gIe9OQAkSGWrlAOfCERTio8fW8dC+/or/ZgX ha+uQ1DoDj7b4KImmT4M6idBYze1/LoKcnizOQs= -----END CERTIFICATE-----`}, } func urlReplacement(url string) []*x509.Certificate { cs, ok := replacements[url] if !ok { return nil } var r []*x509.Certificate for _, c := range cs { s, _ := pem.Decode([]byte(c)) if s == nil { log.Fatalf("Can't decode built-in: %s", c) return nil } cert, err := x509.ParseCertificate(s.Bytes) if x509.IsFatal(err) { log.Fatalf("Can't parse built-in: %s\n%s", c, err) return nil } if cert == nil { log.Fatalf("Parse didn't produce a cert: %s", c) return nil } r = append(r, cert) } return r } google-certificate-transparency-go-2308f62/fixchain/roundtrip_test.go000066400000000000000000000232651462611535200260560ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "bytes" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net/http" "strings" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) type testRoundTripper struct { t *testing.T test *fixAndLogTest testIndex int seen []bool } func (rt testRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { url := fmt.Sprintf("%s://%s%s", request.URL.Scheme, request.URL.Host, request.URL.Path) switch url { case "https://ct.googleapis.com/pilot/ct/v1/get-roots": b := stringRootsToJSON([]string{verisignRoot, testRoot}) return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(b)}, ContentLength: int64(len(b)), Request: request, }, nil case "https://ct.googleapis.com/pilot/ct/v1/add-chain": body, err := io.ReadAll(request.Body) if err := request.Body.Close(); err != nil { errStr := fmt.Sprintf("#%d: Could not close request body: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } if err != nil { errStr := fmt.Sprintf("#%d: Could not read request body: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } type Chain struct { Chain [][]byte } var chainBytes Chain err = json.Unmarshal(body, &chainBytes) if err != nil { errStr := fmt.Sprintf("#%d: Could not unmarshal json: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } var chain []*x509.Certificate for _, certBytes := range chainBytes.Chain { cert, err := x509.ParseCertificate(certBytes) if x509.IsFatal(err) { errStr := fmt.Sprintf("#%d: Could not parse certificate: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } chain = append(chain, cert) } TryNextExpected: for i, expChain := range rt.test.expLoggedChains { if rt.seen[i] || len(chain) != len(expChain) { continue } for j, cert := range chain { if !strings.Contains(nameToKey(&cert.Subject), expChain[j]) { continue TryNextExpected } } rt.seen[i] = true goto Return } rt.t.Errorf("#%d: Logged chain was not expected: %s", rt.testIndex, chainToDebugString(chain)) Return: return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(validAddChainRsp())}, ContentLength: 0, Request: request, }, nil default: var cert string switch url { case "http://www.thawte.com/repository/Thawte_SGC_CA.crt": cert = thawteIntermediate case "http://crt.comodoca.com/EssentialSSLCA_2.crt": cert = comodoIntermediate case "http://crt.comodoca.com/ComodoUTNSGCCA.crt": cert = comodoRoot case "http://www.example.com/intermediate2.crt": cert = testIntermediate2 case "http://www.example.com/intermediate1.crt": cert = testIntermediate1 case "http://www.example.com/ca.crt": cert = testRoot case "http://www.example.com/a.crt": cert = testA case "http://www.example.com/b.crt": cert = testB default: return nil, fmt.Errorf("can't reach url %s", url) } return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader([]byte(cert))}, ContentLength: int64(len([]byte(cert))), Request: request, }, nil } } // The round tripper used during testing of PostChainToLog() is used to check // that the http requests sent by PostChainToLog() contain the right information // for a Certificate Transparency log to be able to log the given chain // (assuming the chain is valid). type postTestRoundTripper struct { t *testing.T test *postTest testIndex int } func (rt postTestRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { if strings.Contains(request.URL.Path, "/ct/v1/get-roots") { b := stringRootsToJSON([]string{verisignRoot}) return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(b)}, ContentLength: int64(len(b)), Request: request, }, nil } // For tests that are checking the correct FixError type is returned: if rt.test.ferr.Type == LogPostFailed { return &http.Response{ Status: "501 Not Implemented", StatusCode: 501, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader([]byte(""))}, ContentLength: 0, Request: request, }, nil } // For tests to check request sent to log looks right: // Check method used if request.Method != "POST" { rt.t.Errorf("#%d: expected request method to be POST, received %s", rt.testIndex, request.Method) } // Check URL if request.URL.Scheme != rt.test.urlScheme { rt.t.Errorf("#%d: Scheme: received %s, expected %s", rt.testIndex, request.URL.Scheme, rt.test.urlScheme) } if request.URL.Host != rt.test.urlHost { rt.t.Errorf("#%d: Host: received %s, expected %s", rt.testIndex, request.URL.Host, rt.test.urlHost) } if request.URL.Path != rt.test.urlPath { rt.t.Errorf("#%d: Path: received %s, expected %s", rt.testIndex, request.URL.Path, rt.test.urlPath) } // Check Body body, err := io.ReadAll(request.Body) if err := request.Body.Close(); err != nil { errStr := fmt.Sprintf("#%d: Could not close request body: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } if err != nil { errStr := fmt.Sprintf("#%d: Could not read request body: %s", rt.testIndex, err.Error()) rt.t.Error(errStr) return nil, errors.New(errStr) } // Create string in the format that the Certificate Transparency logs expect // the body of an add-chain request to be in. var encode = base64.StdEncoding.EncodeToString expStr := "{\"chain\":" if rt.test.chain == nil { expStr += "null" } else { expStr += "[" for i, cert := range rt.test.chain { expStr += "\"" + encode(GetTestCertificateFromPEM(rt.t, cert).Raw) + "\"" if i != len(rt.test.chain)-1 { expStr += "," } } expStr += "]" } expStr += "}" if string(body) != expStr { rt.t.Errorf("#%d: incorrect format of request body. Received %s, expected %s", rt.testIndex, string(body), expStr) } rspData := []byte("") if strings.Contains(request.URL.Path, "/ct/v1/add-chain") { rspData = validAddChainRsp() } // Return a response return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(rspData)}, ContentLength: 0, Request: request, }, nil } func validAddChainRsp() []byte { var sct ct.SignedCertificateTimestamp _, err := tls.Unmarshal(testdata.TestCertProof, &sct) if err != nil { panic(fmt.Sprintf("failed to tls-unmarshal test certificate proof: %v", err)) } sig, err := tls.Marshal(sct.Signature) if err != nil { panic(fmt.Sprintf("failed to marshal signature: %v", err)) } rsp := ct.AddChainResponse{ SCTVersion: sct.SCTVersion, Timestamp: sct.Timestamp, ID: sct.LogID.KeyID[:], Extensions: base64.StdEncoding.EncodeToString(sct.Extensions), Signature: sig, } rspData, err := json.Marshal(rsp) if err != nil { panic(fmt.Sprintf("failed to json-marshal test certificate proof: %v", err)) } return rspData } type newLoggerTestRoundTripper struct{} func (rt newLoggerTestRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { // Return a response b := validAddChainRsp() return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(b)}, ContentLength: int64(len(b)), Request: request, }, nil } type rootCertsTestRoundTripper struct{} func (rt rootCertsTestRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { url := fmt.Sprintf("%s://%s%s", request.URL.Scheme, request.URL.Host, request.URL.Path) if url == "https://ct.googleapis.com/pilot/ct/v1/get-roots" { b := stringRootsToJSON([]string{verisignRoot, comodoRoot}) return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Body: &bytesReadCloser{bytes.NewReader(b)}, ContentLength: int64(len(b)), Request: request, }, nil } return nil, errors.New("") } google-certificate-transparency-go-2308f62/fixchain/url_cache.go000066400000000000000000000045151462611535200247130ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fixchain import ( "fmt" "io" "net/http" "sync" "sync/atomic" "time" "k8s.io/klog/v2" ) type lockedCache struct { m map[string][]byte sync.RWMutex } func (c *lockedCache) get(str string) ([]byte, bool) { c.RLock() defer c.RUnlock() b, ok := c.m[str] return b, ok } func (c *lockedCache) set(str string, b []byte) { c.Lock() defer c.Unlock() c.m[str] = b } func newLockedCache() *lockedCache { return &lockedCache{m: make(map[string][]byte)} } type urlCache struct { client *http.Client cache *lockedCache hit uint32 miss uint32 errors uint32 badStatus uint32 readFail uint32 } func (u *urlCache) getURL(url string) ([]byte, error) { r, ok := u.cache.get(url) if ok { atomic.AddUint32(&u.hit, 1) return r, nil } c, err := u.client.Get(url) if err != nil { atomic.AddUint32(&u.errors, 1) return nil, err } defer func() { if err := c.Body.Close(); err != nil { klog.Errorf("Operation to close response body failed: %v", err) } }() // TODO(katjoyce): Add caching of permanent errors. if c.StatusCode != 200 { atomic.AddUint32(&u.badStatus, 1) return nil, fmt.Errorf("can't deal with status %d", c.StatusCode) } r, err = io.ReadAll(c.Body) if err != nil { atomic.AddUint32(&u.readFail, 1) return nil, err } atomic.AddUint32(&u.miss, 1) u.cache.set(url, r) return r, nil } func newURLCache(c *http.Client, logStats bool) *urlCache { u := &urlCache{cache: newLockedCache(), client: c} if logStats { t := time.NewTicker(time.Second) go func() { for range t.C { klog.Infof("url cache: %d hits, %d misses, %d errors, "+ "%d bad status, %d read fail, %d cached", u.hit, u.miss, u.errors, u.badStatus, u.readFail, len(u.cache.m)) } }() } return u } google-certificate-transparency-go-2308f62/go.mod000066400000000000000000000137221462611535200217540ustar00rootroot00000000000000module github.com/google/certificate-transparency-go go 1.21.0 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/fullstorydev/grpcurl v1.9.1 github.com/go-sql-driver/mysql v1.8.1 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/trillian v1.6.0 github.com/gorilla/mux v1.8.1 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/prometheus/client_golang v1.19.1 github.com/rs/cors v1.11.0 github.com/sergi/go-diff v1.3.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/transparency-dev/merkle v0.0.2 go.etcd.io/etcd/client/v3 v3.5.13 go.etcd.io/etcd/etcdctl/v3 v3.5.13 go.etcd.io/etcd/v3 v3.5.13 golang.org/x/crypto v0.23.0 golang.org/x/net v0.25.0 golang.org/x/time v0.5.0 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/klog/v2 v2.120.1 ) require ( bitbucket.org/creachadair/shell v0.0.8 // indirect cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/monitoring v1.18.0 // indirect cloud.google.com/go/trace v1.10.5 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/aws/aws-sdk-go v1.46.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bufbuild/protocompile v0.10.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/envoyproxy/go-control-plane v0.12.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jhump/protoreflect v1.16.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/letsencrypt/pkcs11key/v4 v4.0.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/prometheus v0.47.2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect github.com/urfave/cli v1.22.14 // indirect github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect go.etcd.io/bbolt v1.3.9 // indirect go.etcd.io/etcd/api/v3 v3.5.13 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect go.etcd.io/etcd/client/v2 v2.305.13 // indirect go.etcd.io/etcd/etcdutl/v3 v3.5.13 // indirect go.etcd.io/etcd/pkg/v3 v3.5.13 // indirect go.etcd.io/etcd/raft/v3 v3.5.13 // indirect go.etcd.io/etcd/server/v3 v3.5.13 // indirect go.etcd.io/etcd/tests/v3 v3.5.13 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.16.1 // indirect google.golang.org/api v0.169.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) google-certificate-transparency-go-2308f62/go.sum000066400000000000000000001274521462611535200220070ustar00rootroot00000000000000bitbucket.org/creachadair/shell v0.0.8 h1:3yM6JcAfaGWzjzcCamTblzSIWXm/YSs0PFGIzBm2HTo= bitbucket.org/creachadair/shell v0.0.8/go.mod h1:vINzudofoUXZSJ5tREgpy+Etyjsag3ait5WOWImEVZ0= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go v1.46.4 h1:48tKgtm9VMPkb6y7HuYlsfhQmoIRAsTEXTsWLVlty4M= github.com/aws/aws-sdk-go v1.46.4/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fullstorydev/grpcurl v1.9.1 h1:YxX1aCcCc4SDBQfj9uoWcTLe8t4NWrZe1y+mk83BQgo= github.com/fullstorydev/grpcurl v1.9.1/go.mod h1:i8gKLIC6s93WdU3LSmkE5vtsCxyRmihUj5FK1cNW5EM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/pkcs11key/v4 v4.0.0 h1:qLc/OznH7xMr5ARJgkZCCWk+EomQkiNTOoOF5LAgagc= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/prometheus v0.47.2 h1:jWcnuQHz1o1Wu3MZ6nMJDuTI0kU5yJp9pkxh8XEkNvI= github.com/prometheus/prometheus v0.47.2/go.mod h1:J/bmOSjgH7lFxz2gZhrWEZs2i64vMS+HIuZfmYNhJ/M= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js= go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI= go.etcd.io/etcd/etcdctl/v3 v3.5.13 h1:bqdaPQe+vnw9efmn8W+riArgdl3uUvuw4yk2QhSZaI0= go.etcd.io/etcd/etcdctl/v3 v3.5.13/go.mod h1:+EKywV/K3xA/OIUWlTKzzhYMpepqQN+KZQ71F85YTuk= go.etcd.io/etcd/etcdutl/v3 v3.5.13 h1:GEAIyquWCRS0P9UAs6QmMgo36t9tT6hHNLb3g25DGNg= go.etcd.io/etcd/etcdutl/v3 v3.5.13/go.mod h1:2vhvTIQobP+Cb04qzlcbKGvX6J5oq/N1kquk1yCDIQY= go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= go.etcd.io/etcd/tests/v3 v3.5.13 h1:vlEC8lzIyDA6Ty+vzSmnMBbrwmZ6pFQ5oTcGA+sD/Oo= go.etcd.io/etcd/tests/v3 v3.5.13/go.mod h1:7oiEl6JJWXvTuZP86ghSKZ7z1llzGR1g7PntZ7C0GHU= go.etcd.io/etcd/v3 v3.5.13 h1:VUSrSxEESP0aSnMybqi5lHT3eJF1XxqMcFPkoi+zMl8= go.etcd.io/etcd/v3 v3.5.13/go.mod h1:S5/hQwdeJgz8vHxHi3IF4c74jrCC8d5EethiCuLNeis= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= google-certificate-transparency-go-2308f62/gossip/000077500000000000000000000000001462611535200221455ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/gossip/README.md000066400000000000000000000006141462611535200234250ustar00rootroot00000000000000### Minimal Gossip Example All the code for this, except for the x509ext package, has been moved over to the [trillian-examples](https://github.com/google/trillian-examples) repository. This keeps the code together and removes a circular dependency between the two repositories. The package layout and structure remains the same so updating should just mean changing any relevant import paths. google-certificate-transparency-go-2308f62/gossip/minimal/000077500000000000000000000000001462611535200235735ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/gossip/minimal/x509ext/000077500000000000000000000000001462611535200250215ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/gossip/minimal/x509ext/x509ext.go000066400000000000000000000062011462611535200265750ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package x509ext holds extensions types and values for minimal gossip. package x509ext import ( "errors" "fmt" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ct "github.com/google/certificate-transparency-go" ) // OIDExtensionCTSTH is the OID value for an X.509 extension that holds // a log STH value. // TODO(drysdale): get an official OID value var OIDExtensionCTSTH = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5} // OIDExtKeyUsageCTMinimalGossip is the OID value for an extended key usage // (EKU) that indicates a leaf certificate is used for the validation of STH // values from public CT logs. // TODO(drysdale): get an official OID value var OIDExtKeyUsageCTMinimalGossip = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 6} // LogSTHInfo is the structure that gets TLS-encoded into the X.509 extension // identified by OIDExtensionCTSTH. type LogSTHInfo struct { LogURL []byte `tls:"maxlen:255"` Version tls.Enum `tls:"maxval:255"` TreeSize uint64 Timestamp uint64 SHA256RootHash ct.SHA256Hash TreeHeadSignature ct.DigitallySigned } // LogSTHInfoFromCert retrieves the STH information embedded in a certificate. func LogSTHInfoFromCert(cert *x509.Certificate) (*LogSTHInfo, error) { for _, ext := range cert.Extensions { if ext.Id.Equal(OIDExtensionCTSTH) { var sthInfo LogSTHInfo rest, err := tls.Unmarshal(ext.Value, &sthInfo) if err != nil { return nil, fmt.Errorf("failed to unmarshal STH: %v", err) } else if len(rest) > 0 { return nil, fmt.Errorf("trailing data (%d bytes) after STH", len(rest)) } return &sthInfo, nil } } return nil, errors.New("no STH extension found") } // HasSTHInfo indicates whether a certificate has embedded STH information. func HasSTHInfo(cert *x509.Certificate) bool { for _, ext := range cert.Extensions { if ext.Id.Equal(OIDExtensionCTSTH) { return true } } return false } // STHFromCert retrieves the STH embedded in a certificate; note the returned STH // does not have the LogID field filled in. func STHFromCert(cert *x509.Certificate) (*ct.SignedTreeHead, error) { sthInfo, err := LogSTHInfoFromCert(cert) if err != nil { return nil, err } return &ct.SignedTreeHead{ Version: ct.Version(sthInfo.Version), TreeSize: sthInfo.TreeSize, Timestamp: sthInfo.Timestamp, SHA256RootHash: sthInfo.SHA256RootHash, TreeHeadSignature: sthInfo.TreeHeadSignature, }, nil } google-certificate-transparency-go-2308f62/gossip/minimal/x509ext/x509ext_test.go000066400000000000000000000106351462611535200276420ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509ext_test import ( "encoding/hex" "encoding/pem" "fmt" "strings" "testing" "time" "github.com/google/certificate-transparency-go/gossip/minimal/x509ext" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" ct "github.com/google/certificate-transparency-go" ) var ( // pilotPubKeyPEM is the public key for Google's Pilot log. pilotPubKeyPEM = []byte(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHT DM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA== -----END PUBLIC KEY-----`) ) func TestSTHFromCert(t *testing.T) { rawPubKey, _ := pem.Decode(pilotPubKeyPEM) pubKey, _, _, err := ct.PublicKeyFromPEM(pilotPubKeyPEM) if err != nil { t.Fatalf("failed to decode test pubkey data: %v", err) } validSTH := x509ext.LogSTHInfo{ LogURL: []byte("http://ct.example.com/log"), Version: 0, TreeSize: 7834120, Timestamp: 1519395540364, SHA256RootHash: [...]byte{ 0xfe, 0xc0, 0xed, 0xe1, 0xbe, 0xf1, 0xa2, 0x25, 0xc3, 0x72, 0xa6, 0x44, 0x1b, 0xa2, 0xd5, 0xdd, 0x3b, 0xbb, 0x9b, 0x7b, 0xa9, 0x79, 0xd1, 0xa7, 0x03, 0xe7, 0xfe, 0x81, 0x49, 0x75, 0x85, 0xfb, }, TreeHeadSignature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{Hash: tls.SHA256, Signature: tls.ECDSA}, Signature: dehex("220164e031604aa2a0b68887ba668cefb3e0046e455d6323c3df38b8d50108895d70220146199ee1d759a029d8b37ce8701d2ca47a387bad8ac8ef1cb84b77bc0820ed"), }, } sthData, err := tls.Marshal(validSTH) if err != nil { t.Fatalf("failed to marshal STH: %v", err) } var tests = []struct { name string cert x509.Certificate wantErr string }{ { name: "ValidSTH", cert: x509.Certificate{ NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour), PublicKey: pubKey, RawSubjectPublicKeyInfo: rawPubKey.Bytes, Subject: pkix.Name{ CommonName: "Test STH holder", }, Extensions: []pkix.Extension{ {Id: x509ext.OIDExtensionCTSTH, Critical: false, Value: sthData}, }, }, }, { name: "MissingSTH", cert: x509.Certificate{ NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour), Subject: pkix.Name{ CommonName: "Test STH holder", }, }, wantErr: "no STH extension found", }, { name: "TrailingData", cert: x509.Certificate{ NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour), Subject: pkix.Name{ CommonName: "Test STH holder", }, Extensions: []pkix.Extension{ {Id: x509ext.OIDExtensionCTSTH, Critical: false, Value: append(sthData, 0xff)}, }, }, wantErr: "trailing data", }, { name: "InvalidSTH", cert: x509.Certificate{ NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour), Subject: pkix.Name{ CommonName: "Test STH holder", }, Extensions: []pkix.Extension{ {Id: x509ext.OIDExtensionCTSTH, Critical: false, Value: []byte{0xff}}, }, }, wantErr: "failed to unmarshal", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := x509ext.STHFromCert(&test.cert) if err != nil { if test.wantErr == "" { t.Errorf("STHFromCert(%+v)=nil,%v; want _,nil", test.cert, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("STHFromCert(%+v)=nil,%v; want nil,err containing %q", test.cert, err, test.wantErr) } return } if test.wantErr != "" { t.Errorf("STHFromCert(%+v)=_,nil; want nil,err containing %q", test.cert, test.wantErr) } t.Logf("retrieved STH %+v", got) }) } } func dehex(h string) []byte { d, err := hex.DecodeString(h) if err != nil { panic(fmt.Sprintf("hard-coded data %q failed to decode! %v", h, err)) } return d } google-certificate-transparency-go-2308f62/integration/000077500000000000000000000000001462611535200231645ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/integration/Dockerfile000066400000000000000000000024261462611535200251620ustar00rootroot00000000000000# This Dockerfile builds a base image for the certificate-transparency-go CloudBuild integration testing. # See https://hub.docker.com/_/golang for the set of golang base images. FROM golang:1.22.3-bookworm@sha256:5c56bd47228dd572d8a82971cf1f946cd8bb1862a8ec6dc9f3d387cc94136976 as ct_testbase WORKDIR /testbase ARG GOFLAGS="" ENV GOFLAGS=$GOFLAGS RUN apt-get update && apt-get -y install build-essential curl docker-compose lsof mariadb-client netcat-openbsd unzip wget xxd RUN cd /usr/bin && curl -L -O https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux64 && mv jq-linux64 /usr/bin/jq && chmod +x /usr/bin/jq RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.1 RUN mkdir protoc && \ (cd protoc && \ PROTOC_VERSION=3.20.1 && \ PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-x86_64.zip" && \ wget "https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP}" && \ unzip -o ${PROTOC_ZIP} -d /usr/local bin/protoc && \ unzip -o ${PROTOC_ZIP} -d /usr/local 'include/*' \ ) ENV PATH /usr/local/bin:$PATH COPY ./integration/test-runner.sh /testbase/ ENV GOPATH /go ENV PATH $GOPATH/bin:/testbase/protoc/bin:$PATH CMD /bin/bash -exc /testbase/test-runner.sh google-certificate-transparency-go-2308f62/integration/test-runner.sh000077500000000000000000000012601462611535200260100ustar00rootroot00000000000000#!/bin/bash # This script is used by the CloudBuild ct_testbase docker image. # It's executed as the default command by that image in order to run a # full presubmit/integration test. ./scripts/presubmit.sh ${PRESUBMIT_OPTS} || exit 1 # Check re-generation didn't change anything status=$(git status --porcelain | egrep -v 'coverage|go\.(mod|sum)') || : if [[ -n ${status} ]]; then echo "Regenerated files differ from checked-in versions: ${status}" git status git diff #exit 1 fi if [[ "${WITH_ETCD}" == "true" ]]; then export ETCD_DIR="${GOPATH}/bin" fi ./trillian/integration/integration_test.sh HAMMER_OPTS="--operations=1500" ./trillian/integration/ct_hammer_test.sh google-certificate-transparency-go-2308f62/internal/000077500000000000000000000000001462611535200224555ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/000077500000000000000000000000001462611535200241515ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/README.md000066400000000000000000000037361462611535200254410ustar00rootroot00000000000000CT Witness ============== The witness is an HTTP service that stores STHs it has seen from a configurable list of Certificate Transparency logs in a sqlite database. This is a lightweight way to help detect or even prevent split-view attacks. An overview of witnessing can be found in [trillian-examples](https://github.com/google/trillian-examples/tree/master/witness), along with "generic" witness implementations. This witness is designed to be compatible with the specific formats used by CT. Once up and running, the witness provides three API endpoints (as defined in [api/http.go](api/http.go)): - `/ctwitness/v0/logs` returns a list of all logs for which the witness is currently storing an STH. - `/ctwitness/v0/logs//update` acts to update the STH stored for `logid`. - `/ctwitness/v0/logs//sth` returns the latest STH for `logid`. Running the witness -------------------- Running the witness is as simple as running `go run ./cmd/witness/main.go` from this directory, with the following flags: - `listen`, which specifies the address and port to listen on. - `db_file`, which specifies the desired location of the sqlite database. The use of sqlite limits the scalability and reliability of the witness (because this is a local file), so if that is required a different database backend would be needed. - `config_file`, which specifies configuration information for the logs. This repository contains a [sample configuration file](cmd/witness/example.conf), and in general it is necessary to specify the following fields for each log: - `logID`, which is the alphanumeric identifier for the log. - `pubKey`, which is the base64-encoded public key of the log. Both of these fields should be populated using an "official" [CT log list](https://www.gstatic.com/ct/log_list/v3/log_list.json). - `private_key`, which specifies the private signing key of the witness. In its current state the witness does not sign STHs so this can exist in any form. google-certificate-transparency-go-2308f62/internal/witness/api/000077500000000000000000000000001462611535200247225ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/api/http.go000066400000000000000000000032161462611535200262320ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package api provides the API endpoints for the witness. package api import ( ct "github.com/google/certificate-transparency-go" ) const ( // HTTPGetSTH is the path of the URL to get an STH. The // placeholder is for the logID (an alphanumeric string). HTTPGetSTH = "/ctwitness/v0/logs/%s/sth" // HTTPUpdate is the path of the URL to update to a new STH. // Again the placeholder is for the logID. HTTPUpdate = "/ctwitness/v0/logs/%s/update" // HTTPGetLogs is the path of the URL to get a list of all logs the // witness is aware of. HTTPGetLogs = "/ctwitness/v0/logs" ) // UpdateRequest encodes the inputs to the witness Update function: a (raw) // STH byte slice and a consistency proof (slice of slices). The logID // is part of the request URL. type UpdateRequest struct { STH []byte Proof [][]byte } // CosignedSTH has all the fields from a CT SignedTreeHead but adds a // WitnessSigs field that holds the extra witness signatures. type CosignedSTH struct { ct.SignedTreeHead WitnessSigs []ct.DigitallySigned `json:"witness_signatures"` } google-certificate-transparency-go-2308f62/internal/witness/client/000077500000000000000000000000001462611535200254275ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/client/http/000077500000000000000000000000001462611535200264065ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/client/http/witness_client.go000066400000000000000000000072041462611535200317720ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package http is a simple client for interacting with witnesses over HTTP. package http import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" wit_api "github.com/google/certificate-transparency-go/internal/witness/api" "k8s.io/klog/v2" ) // ErrSTHTooOld is returned if the STH passed to Update needs to be updated. var ErrSTHTooOld = errors.New("STH too old") // Witness consists of the witness' URL and signature verifier. type Witness struct { URL *url.URL } // GetLatestSTH returns a recent STH from the witness for the specified log ID. func (w Witness) GetLatestSTH(ctx context.Context, logID string) ([]byte, error) { u, err := w.URL.Parse(fmt.Sprintf(wit_api.HTTPGetSTH, url.PathEscape(logID))) if err != nil { return nil, fmt.Errorf("failed to parse URL: %v", err) } req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, fmt.Errorf("failed to create request: %v", err) } resp, err := http.DefaultClient.Do(req.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("failed to do http request: %v", err) } defer func() { if err := resp.Body.Close(); err != nil { klog.Errorf("Failed to close response body: %v", err) } }() if resp.StatusCode == 404 { return nil, os.ErrNotExist } else if resp.StatusCode != 200 { return nil, fmt.Errorf("bad status response: %s", resp.Status) } return io.ReadAll(resp.Body) } // Update attempts to clock the witness forward for the given logID. // The latest signed STH will be returned if this succeeds, or if the error is // http.ErrSTHTooOld. In all other cases no STH should be expected. func (w Witness) Update(ctx context.Context, logID string, sth []byte, proof [][]byte) ([]byte, error) { reqBody, err := json.MarshalIndent(&wit_api.UpdateRequest{ STH: sth, Proof: proof, }, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal update request: %v", err) } u, err := w.URL.Parse(fmt.Sprintf(wit_api.HTTPUpdate, url.PathEscape(logID))) if err != nil { return nil, fmt.Errorf("failed to parse URL: %v", err) } req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewReader(reqBody)) if err != nil { return nil, fmt.Errorf("failed to create request: %v", err) } resp, err := http.DefaultClient.Do(req.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("failed to do http request: %v", err) } if resp.Request.Method != "PUT" { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections return nil, fmt.Errorf("PUT request to %q was converted to %s request to %q", u.String(), resp.Request.Method, resp.Request.URL) } defer func() { if err := resp.Body.Close(); err != nil { klog.Errorf("Failed to close response body: %v", err) } }() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read body: %v", err) } if resp.StatusCode != 200 { if resp.StatusCode == 409 { return body, ErrSTHTooOld } return nil, fmt.Errorf("bad status response (%s): %q", resp.Status, body) } return body, nil } google-certificate-transparency-go-2308f62/internal/witness/cmd/000077500000000000000000000000001462611535200247145ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/client/000077500000000000000000000000001462611535200261725ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/client/main.go000066400000000000000000000153611462611535200274530ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // client fetches and verifies new STHs for a set of logs from a single witness. package main import ( "context" "crypto/x509" "encoding/base64" "encoding/json" "flag" "fmt" "io" "net/http" "net/url" "os" "sync" "time" ct "github.com/google/certificate-transparency-go" wit_api "github.com/google/certificate-transparency-go/internal/witness/api" wh "github.com/google/certificate-transparency-go/internal/witness/client/http" "github.com/google/certificate-transparency-go/internal/witness/verifier" "github.com/google/certificate-transparency-go/loglist3" "k8s.io/klog/v2" ) var ( logList = flag.String("log_list_url", "https://www.gstatic.com/ct/log_list/v3/log_list.json", "The location of the log list") witness = flag.String("witness_url", "", "The endpoint of the witness HTTP API") witnessPK = flag.String("witness_pk", "", "The base64-encoded witness public key") interval = flag.Duration("poll", 10*time.Second, "How frequently to poll to get new witnessed STHs") ) // WitnessSigVerifier verifies the witness' signature on a cosigned STH. type WitnessSigVerifier interface { VerifySignature(cosigned wit_api.CosignedSTH) error } // Witness consists of the witness' URL and signature verifier. type Witness struct { Client *wh.Witness Verifier WitnessSigVerifier } type ctLog struct { id string name string wsth *wit_api.CosignedSTH verifier *ct.SignatureVerifier } func main() { klog.InitFlags(nil) flag.Parse() if *witness == "" { klog.Exit("--witness_url must not be empty") } if *witnessPK == "" { klog.Exit("--witness_pk must not be empty") } ctx := context.Background() // Set up the witness client. wURL, err := url.Parse(*witness) if err != nil { klog.Exitf("Failed to parse witness URL: %v", err) } pk, err := ct.PublicKeyFromB64(*witnessPK) if err != nil { klog.Exitf("Failed to create witness public key: %v", err) } wv, err := verifier.NewWitnessVerifier(pk) if err != nil { klog.Exitf("Failed to create witness signature verifier: %v", err) } w := Witness{ Client: &wh.Witness{ URL: wURL, }, Verifier: wv, } // Set up the log data. ctLogs, err := populateLogs(*logList) if err != nil { klog.Exitf("Failed to set up log data: %v", err) } // Now poll the witness for each log. wg := &sync.WaitGroup{} for _, log := range ctLogs { wg.Add(1) go func(witness *Witness, log ctLog) { defer wg.Done() if err := log.getSTH(ctx, witness, *interval); err != nil { klog.Errorf("getSTH: %v", err) } }(&w, log) } wg.Wait() } // populateLogs populates a list of ctLogs based on the log list. func populateLogs(logListURL string) ([]ctLog, error) { u, err := url.Parse(logListURL) if err != nil { return nil, fmt.Errorf("failed to parse URL: %v", err) } body, err := readURL(u) if err != nil { return nil, fmt.Errorf("failed to get log list data: %v", err) } // Get data for all usable logs. logList, err := loglist3.NewFromJSON(body) if err != nil { return nil, fmt.Errorf("failed to parse JSON: %v", err) } usable := logList.SelectByStatus([]loglist3.LogStatus{loglist3.UsableLogStatus}) var logs []ctLog for _, operator := range usable.Operators { for _, log := range operator.Logs { logID := base64.StdEncoding.EncodeToString(log.LogID) //logPK := base64.StdEncoding.EncodeToString(log.Key) //pk, err := ct.PublicKeyFromB64(logPK) pk, err := x509.ParsePKIXPublicKey(log.Key) if err != nil { return nil, fmt.Errorf("failed to create public key for %s: %v", log.Description, err) } v, err := ct.NewSignatureVerifier(pk) if err != nil { return nil, fmt.Errorf("failed to create signature verifier: %v", err) } l := ctLog{ id: logID, name: log.Description, verifier: v, } logs = append(logs, l) } } return logs, nil } // getSTH gets cosigned STHs for a given log continuously from the witness, // returning only when the context is done. func (l *ctLog) getSTH(ctx context.Context, witness *Witness, interval time.Duration) error { tik := time.NewTicker(interval) defer tik.Stop() for { func() { ctx, cancel := context.WithTimeout(ctx, interval) defer cancel() klog.V(2).Infof("Requesting STH for %s from witness", l.name) if err := l.getOnce(ctx, witness); err != nil { klog.Warningf("Failed to retrieve STH for %s: %v", l.name, err) } else { klog.Infof("Verified the STH for %s at size %d!", l.name, l.wsth.TreeSize) } }() select { case <-ctx.Done(): return ctx.Err() case <-tik.C: } } } // getOnce gets a new cosigned STH once and verifies it, replacing the stored STH // in the case that it does verify. func (l *ctLog) getOnce(ctx context.Context, witness *Witness) error { // Get and parse the latest cosigned STH from the witness. var cSTH wit_api.CosignedSTH sthRaw, err := witness.Client.GetLatestSTH(ctx, l.id) if err != nil { return fmt.Errorf("failed to get STH: %v", err) } if err := json.Unmarshal(sthRaw, &cSTH); err != nil { return fmt.Errorf("failed to unmarshal STH: %v", err) } // First verify the witness signature(s). if err := witness.Verifier.VerifySignature(cSTH); err != nil { return fmt.Errorf("failed to verify witness signature: %v", err) } // Then verify the log signature. plainSTH := cSTH.SignedTreeHead if err := l.verifier.VerifySTHSignature(plainSTH); err != nil { return fmt.Errorf("failed to verify log signature: %v", err) } l.wsth = &cSTH return nil } var getByScheme = map[string]func(*url.URL) ([]byte, error){ "http": readHTTP, "https": readHTTP, "file": func(u *url.URL) ([]byte, error) { return os.ReadFile(u.Path) }, } // readHTTP fetches and reads data from an HTTP-based URL. func readHTTP(u *url.URL) ([]byte, error) { resp, err := http.Get(u.String()) if err != nil { return nil, err } defer func() { if err := resp.Body.Close(); err != nil { klog.Errorf("failed to close response body %v", err) } }() return io.ReadAll(resp.Body) } // readURL fetches and reads data from an HTTP-based or filesystem URL. func readURL(u *url.URL) ([]byte, error) { s := u.Scheme queryFn, ok := getByScheme[s] if !ok { return nil, fmt.Errorf("failed to identify suitable scheme for the URL %q", u.String()) } return queryFn(u) } google-certificate-transparency-go-2308f62/internal/witness/cmd/feeder/000077500000000000000000000000001462611535200261465ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/feeder/Dockerfile000066400000000000000000000011431462611535200301370ustar00rootroot00000000000000FROM golang:1.22.3-bookworm@sha256:5c56bd47228dd572d8a82971cf1f946cd8bb1862a8ec6dc9f3d387cc94136976 as builder ARG GOFLAGS="" ENV GOFLAGS=$GOFLAGS # Move to working directory /build WORKDIR /build # Copy and download dependency using go mod COPY go.mod . COPY go.sum . RUN go mod download # Copy the code into the container COPY . . # Build the application RUN go build -o /build/bin/feeder ./internal/witness/cmd/feeder # Build release image FROM alpine@sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb4f25238ebd COPY --from=builder /build/bin/feeder /bin/feeder ENTRYPOINT ["/bin/feeder"] google-certificate-transparency-go-2308f62/internal/witness/cmd/feeder/main.go000066400000000000000000000200061462611535200274170ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // feeder polls the sumdb log and pushes the results to a generic witness. package main import ( "context" "encoding/base64" "encoding/json" "encoding/pem" "errors" "flag" "fmt" "io" "net/http" "net/url" "os" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" wh "github.com/google/certificate-transparency-go/internal/witness/client/http" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "k8s.io/klog/v2" ) var ( logList = flag.String("log_list_url", "https://www.gstatic.com/ct/log_list/v3/log_list.json", "The location of the log list") witness = flag.String("witness_url", "", "The endpoint of the witness HTTP API") interval = flag.Duration("poll", 10*time.Second, "How quickly to poll the log to get updates") ) // ctLog contains the latest witnessed STH for a log and a log client. type ctLog struct { id string name string wsth *ct.SignedTreeHead client *client.LogClient } // populateLogs populates a list of ctLogs based on the log list. func populateLogs(logListURL string) ([]ctLog, error) { u, err := url.Parse(logListURL) if err != nil { return nil, fmt.Errorf("failed to parse URL: %v", err) } body, err := readURL(u) if err != nil { return nil, fmt.Errorf("failed to get log list data: %v", err) } // Get data for all usable logs. logList, err := loglist3.NewFromJSON(body) if err != nil { return nil, fmt.Errorf("failed to parse JSON: %v", err) } usable := logList.SelectByStatus([]loglist3.LogStatus{loglist3.UsableLogStatus}) var logs []ctLog for _, operator := range usable.Operators { for _, log := range operator.Logs { logID := base64.StdEncoding.EncodeToString(log.LogID) c, err := createLogClient(log.Key, log.URL) if err != nil { return nil, fmt.Errorf("failed to create log client: %v", err) } l := ctLog{ id: logID, name: log.Description, client: c, } logs = append(logs, l) } } return logs, nil } // createLogClient creates a CT log client from a public key and URL. func createLogClient(key []byte, url string) (*client.LogClient, error) { pemPK := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: key, }) opts := jsonclient.Options{PublicKey: string(pemPK)} c, err := client.New(url, http.DefaultClient, opts) if err != nil { return nil, fmt.Errorf("failed to create JSON client: %v", err) } return c, nil } func main() { klog.InitFlags(nil) flag.Parse() if *witness == "" { klog.Exit("--witness_url must not be empty") } ctx := context.Background() // Set up the witness client. var w wh.Witness if wURL, err := url.Parse(*witness); err != nil { klog.Exitf("Failed to parse witness URL: %v", err) } else { w = wh.Witness{ URL: wURL, } } // Now set up the log data (with no initial witness STH). ctLogs, err := populateLogs(*logList) if err != nil { klog.Exitf("Failed to set up log data: %v", err) } // Now feed each log. wg := &sync.WaitGroup{} for _, log := range ctLogs { wg.Add(1) go func(witness *wh.Witness, log ctLog) { defer wg.Done() if err := log.feed(ctx, witness, *interval); err != nil { klog.Errorf("feedLog: %v", err) } }(&w, log) } wg.Wait() } // feed feeds continuously for a given log, returning only when the context // is done. func (l *ctLog) feed(ctx context.Context, witness *wh.Witness, interval time.Duration) error { tik := time.NewTicker(interval) defer tik.Stop() for { func() { ctx, cancel := context.WithTimeout(ctx, interval) defer cancel() klog.V(2).Infof("Start feedOnce for %s", l.name) if err := l.feedOnce(ctx, witness); err != nil { klog.Warningf("Failed to feed for %s: %v", l.name, err) } klog.V(2).Infof("feedOnce complete for %s", l.name) }() select { case <-ctx.Done(): return ctx.Err() case <-tik.C: } } } // feedOnce attempts to update the STH held by the witness to the latest STH // provided by the log. func (l *ctLog) feedOnce(ctx context.Context, w *wh.Witness) error { // Get and parse the latest STH from the log. var sthResp ct.GetSTHResponse _, csthRaw, err := l.client.GetAndParse(ctx, ct.GetSTHPath, nil, &sthResp) if err != nil { return fmt.Errorf("failed to get latest STH: %v", err) } csth, err := sthResp.ToSignedTreeHead() if err != nil { return fmt.Errorf("failed to parse response as STH: %v", err) } wSize, err := l.latestSize(ctx, w) if err != nil { return fmt.Errorf("failed to get latest size for %s: %v", l.name, err) } if wSize >= csth.TreeSize { klog.V(1).Infof("Witness size %d >= log size %d for %s - nothing to do", wSize, csth.TreeSize, l.name) return nil } klog.Infof("Updating witness from size %d to %d for %s", wSize, csth.TreeSize, l.name) // If we want to update the witness then let's get a consistency proof. var pf [][]byte if wSize > 0 { pf, err = l.client.GetSTHConsistency(ctx, wSize, csth.TreeSize) if err != nil { return fmt.Errorf("failed to get consistency proof: %v", err) } } // Now give the new STH and consistency proof to the witness. wsthRaw, err := w.Update(ctx, l.id, csthRaw, pf) if err != nil && !errors.Is(err, wh.ErrSTHTooOld) { return fmt.Errorf("failed to update STH: %v", err) } if errors.Is(err, wh.ErrSTHTooOld) { klog.Infof("STH mismatch at log size %d for %s", wSize, l.name) klog.Infof("%s", wsthRaw) } // Parse the STH it returns. var wsthJSON ct.GetSTHResponse if err := json.Unmarshal(wsthRaw, &wsthJSON); err != nil { return fmt.Errorf("failed to unmarshal json: %v", err) } wsth, err := wsthJSON.ToSignedTreeHead() if err != nil { return fmt.Errorf("failed to create STH: %v", err) } // For now just update our local state with whatever the witness // returns, even if we got wh.ErrSTHTooOld. This is fine if we're the // only feeder for this witness. l.wsth = wsth return nil } // latestSize returns the size of the latest witness STH. If this is nil then // it first checks with the witness to see if it has anything stored before // returning 0. func (l *ctLog) latestSize(ctx context.Context, w *wh.Witness) (uint64, error) { if l.wsth != nil { return l.wsth.TreeSize, nil } wsthRaw, err := w.GetLatestSTH(ctx, l.id) if err != nil { if errors.Is(err, os.ErrNotExist) { // If the witness has no stored STH then 0 is the correct size. return 0, nil } return 0, err } var wsthJSON ct.GetSTHResponse if err := json.Unmarshal(wsthRaw, &wsthJSON); err != nil { return 0, fmt.Errorf("failed to unmarshal json: %v", err) } wsth, err := wsthJSON.ToSignedTreeHead() if err != nil { return 0, fmt.Errorf("failed to create STH: %v", err) } l.wsth = wsth return wsth.TreeSize, nil } var getByScheme = map[string]func(*url.URL) ([]byte, error){ "http": readHTTP, "https": readHTTP, "file": func(u *url.URL) ([]byte, error) { return os.ReadFile(u.Path) }, } // readHTTP fetches and reads data from an HTTP-based URL. func readHTTP(u *url.URL) ([]byte, error) { resp, err := http.Get(u.String()) if err != nil { return nil, err } defer func() { if err := resp.Body.Close(); err != nil { klog.Errorf("Failed to close response body: %v", err) } }() return io.ReadAll(resp.Body) } // readURL fetches and reads data from an HTTP-based or filesystem URL. func readURL(u *url.URL) ([]byte, error) { s := u.Scheme queryFn, ok := getByScheme[s] if !ok { return nil, fmt.Errorf("failed to identify suitable scheme for the URL %q", u.String()) } return queryFn(u) } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/000077500000000000000000000000001462611535200264105ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/Dockerfile000066400000000000000000000011701462611535200304010ustar00rootroot00000000000000FROM golang:1.22.3-bookworm@sha256:5c56bd47228dd572d8a82971cf1f946cd8bb1862a8ec6dc9f3d387cc94136976 AS builder ARG GOFLAGS="" ENV GOFLAGS=$GOFLAGS # Move to working directory /build WORKDIR /build # Copy and download dependency using go mod COPY go.mod . COPY go.sum . RUN go mod download # Copy the code into the container COPY . . # Build the application RUN go build -o /build/bin/witness ./internal/witness/cmd/witness # Build release image FROM golang:1.22.3-bookworm@sha256:5c56bd47228dd572d8a82971cf1f946cd8bb1862a8ec6dc9f3d387cc94136976 COPY --from=builder /build/bin/witness /bin/witness ENTRYPOINT ["/bin/witness"] google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/config/000077500000000000000000000000001462611535200276555ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/config/config.go000066400000000000000000000060451462611535200314560ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // config is a tool to populate the witness config file according to a set of logs. package main import ( "encoding/base64" "flag" "fmt" "io" "net/http" "net/url" "os" "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/impl" "github.com/google/certificate-transparency-go/loglist3" "gopkg.in/yaml.v3" "k8s.io/klog/v2" ) var ( // This can be either an HTTP- or filesystem-based URL. logList = flag.String("log_list_url", "https://www.gstatic.com/ct/log_list/v3/log_list.json", "The location of the log list") configFile = flag.String("config_file", "config.yaml", "path to a YAML config file that specifies the logs followed by this witness") ) func main() { flag.Parse() // Get all usable logs from the log list. u, err := url.Parse(*logList) if err != nil { klog.Exitf("Failed to parse log_list_url as a URL: %v", err) } body, err := readURL(u) if err != nil { klog.Exitf("Failed to get log list data: %v", err) } // Get data for all usable logs. logList, err := loglist3.NewFromJSON(body) if err != nil { klog.Exitf("failed to parse JSON: %v", err) } var config impl.LogConfig usable := logList.SelectByStatus([]loglist3.LogStatus{loglist3.UsableLogStatus}) for _, operator := range usable.Operators { for _, log := range operator.Logs { key := base64.StdEncoding.EncodeToString(log.Key) l := impl.LogInfo{PubKey: key} config.Logs = append(config.Logs, l) } } data, err := yaml.Marshal(&config) if err != nil { klog.Exitf("Failed to marshal log config into YAML: %v", err) } if err := os.WriteFile(*configFile, data, 0644); err != nil { klog.Exitf("Failed to write config to file: %v", err) } } var getByScheme = map[string]func(*url.URL) ([]byte, error){ "http": readHTTP, "https": readHTTP, "file": func(u *url.URL) ([]byte, error) { return os.ReadFile(u.Path) }, } // readHTTP fetches and reads data from an HTTP-based URL. func readHTTP(u *url.URL) ([]byte, error) { resp, err := http.Get(u.String()) if err != nil { return nil, err } defer func() { if err := resp.Body.Close(); err != nil { klog.Errorf("Operation to close response body failed: %v", err) } }() return io.ReadAll(resp.Body) } // readURL fetches and reads data from an HTTP-based or filesystem URL. func readURL(u *url.URL) ([]byte, error) { s := u.Scheme queryFn, ok := getByScheme[s] if !ok { return nil, fmt.Errorf("failed to identify suitable scheme for the URL %q", u.String()) } return queryFn(u) } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/config/config.yaml000066400000000000000000000071031462611535200320070ustar00rootroot00000000000000Logs: - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETeBmZOrzZKo4xYktx9gI2chEce3cw/tbr5xkoQlmhB18aKfsxD+MnILgGNl0FOm0eYGilFVi85wLRIOhK8lxKw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeIPc6fGmuBg6AJkv/z7NFckmHvf/OqmjchZJ6wm2qN200keRDg352dWpi7CHnSV51BpQYAj1CQY5JuRAwrrDwg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0JCPZFJOQqyEti5M8j13ALN3CAVHqkVM4yyOcKWCu2yye5yYeqDpEXYoALIgtM3TmHtNlifmt+4iatGwLpF3eA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER+1MInu8Q39BwDZ5Rp9TwXhwm3ktvgJzpk/r7dDgGk7ZacMm3ljfcoIvP1E72T8jvyLT1bvdapylajZcTH6W5g== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+WS9FSxAYlCVEzg8xyGwOrmPonoV14nWjjETAIdZvLvukPzIWBMKv6tDNlQjpIHNrUcUt1igRPpqoKDXw2MeKw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEchY+C+/vzj5g3ZXLY3q5qY1Kb2zcYYCmRV4vg6yU84WI0KV00HuO/8XuQqLwLZPjwtCymeLhQunSxgAnaXSuzg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEmyGDvYXsRJsNyXSrYc9DjHsIa2xzb4UR7ZxVoV6mrc9iZB7xjI6+NrOiwH+P/xxkRmOFG6Jel20q37hTh58rA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExpon7ipsqehIeU1bmpog9TFo4Pk8+9oN8OYHl1Q2JGVXnkVFnuuvPgSo2Ep+6vLffNLcmEbxOucz03sFiematg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESLJHTlAycmJKDQxIv60pZG8g33lSYxYpCi5gteI6HLevWbFVCdtZx+m9b+0LrwWWl/87mkNN6xE0M4rnrIPA/w== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/8tkhjLRp0SXrlZdTzNkTd6HqmcmXiDJz3fAdWLgOhjmv4mohvRhwXul9bgW0ODgRwC9UGAgH/vpGHPvIS1qA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCFRkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6J4EbcpIAl1+AkSRsbhoY5oRTj3VoFfaf1DlQkfi7Rbe/HcjfVtrwN8jaC+tQDGjF+dqvKhWJAQ6Q6ev6q9Mew== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfQ0DsdWYitzwFTvG3F4Nbj8Nv5XIVYzQpkyWsU4nuSYlmcwrAp6m092fsdXEw6w1BAeHlzaqrSgNfyvZaJ9y0Q== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9o7AiwrbGBIX6Lnc47I6OfLMdZnRzKoP5u072nBi6vpIOEooktTi1gNwlRPzGC2ySGfuc1xLDeaA/wSFGgpYFg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJyTdaAMoy/5jvg4RR019F2ihEV1McclBKMe2okuX7MCv/C87v+nxsfz1Af+p+0lADGMkmNd5LqZVqxbGvlHYcQ== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXu8iQwSCRSf2CbITGpUpBtFVt8+I0IU0d1C36Lfe1+fbwdaI0Z5FktfM2fBoI1bXBd18k2ggKGYGgdZBgLKTg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8m/SiQ8/xfiHHqtls9m7FyOMBg4JVZY9CgiixXGz0akvKD6DEL8S0ERmFe9U4ZiA0M4kbT5nmuk3I85Sk4bagA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7+R9dC4VFbbpuyOL+yy14ceAmEf7QGlo/EmtYU6DRzwat43f/3swtLr/L8ugFOOt1YU/RFmMjGCL17ixv66MZw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELsYzGMNwo8rBIlaklBIdmD2Ofn6HkfrjK0Ukz1uOIUC6Lm0jTITCXhoIdjs7JkyXnwuwYiJYiH7sE1YeKu8k9w== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhjyxDVIjWt5u9sB/o2S8rcGJ2pdZTGA8+IpXhI/tvKBjElGE5r3de4yAfeOPhqTqqc+o7vPgXnDgu/a9/B+RLg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsz0OeL7jrVxEXJu+o4QWQYLKyokXHiPOOKVUL3/TNFFquVzDSer7kZ3gijxzBp98ZTgRgMSaWgCmZ8OD74mFUQ== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESdAfC+h1SZNsSARs188n/dCiNYjGSgkT7avLYe1mmXJzzHhsmxmAorHtOzhDkFgaCSCrUPrXdunK946eyIeSmA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu1LyFs+SC8555lRtwjdTpPX5OqmzBewdvRbsMKwu+HliNRWOGtgWLuRIa/bGE/GWLlwQ/hkeqBi4Dy3DpIZRlw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpBFS2xdBTpDUVlESMFL4mwPPTJ/4Lji18Vq6+ji50o8agdqVzDPsIShmxlY+YDYhINnUrF36XBmhBX3+ICP89Q== google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/impl/000077500000000000000000000000001462611535200273515ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/impl/witness.go000066400000000000000000000102111462611535200313670ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package impl is the implementation of the witness server. package impl import ( "context" "crypto/sha256" "database/sql" "encoding/base64" "errors" "fmt" "net/http" ct "github.com/google/certificate-transparency-go" ih "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/internal/http" "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/internal/witness" "github.com/gorilla/mux" _ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3 "k8s.io/klog/v2" ) // LogConfig contains a list of LogInfo (configuration options for a log). type LogConfig struct { Logs []LogInfo `yaml:"Logs"` } // LogInfo contains the configuration options for a log, which is just its public key. type LogInfo struct { PubKey string `yaml:"PubKey"` } // ServerOpts provides the options for a server (specified in main.go). type ServerOpts struct { // Where to listen for requests. ListenAddr string // The file for sqlite3 storage. DBFile string // The signing key for the witness. PrivKey string // The log configuration information. Config LogConfig } // buildLogMap loads the log configuration information into a map. func buildLogMap(config LogConfig) (map[string]ct.SignatureVerifier, error) { logMap := make(map[string]ct.SignatureVerifier) for _, log := range config.Logs { // Use the PubKey string to first create the verifier. pk, err := ct.PublicKeyFromB64(log.PubKey) if err != nil { return nil, fmt.Errorf("failed to create public key: %v", err) } logV, err := ct.NewSignatureVerifier(pk) if err != nil { return nil, fmt.Errorf("failed to create signature verifier: %v", err) } // And then to create the logID. logID, err := LogIDFromPubKey(log.PubKey) if err != nil { return nil, fmt.Errorf("failed to create log id: %v", err) } logMap[logID] = *logV } return logMap, nil } // LogIDFromPubKey builds the logID given the base64-encoded public key. func LogIDFromPubKey(pk string) (string, error) { der, err := base64.StdEncoding.DecodeString(pk) if err != nil { return "", fmt.Errorf("failed to decode public key: %v", err) } sha := sha256.Sum256(der) return base64.StdEncoding.EncodeToString(sha[:]), nil } // Main sets up and runs the witness given the options. func Main(ctx context.Context, opts ServerOpts) error { if len(opts.DBFile) == 0 { return errors.New("DBFile is required") } // Start up local database. klog.Infof("Connecting to local DB at %q", opts.DBFile) db, err := sql.Open("sqlite3", opts.DBFile) if err != nil { return fmt.Errorf("failed to connect to DB: %w", err) } // Avoid multiple writes colliding and resulting in a "database locked" error. db.SetMaxOpenConns(1) // Load log configuration into the map. logMap, err := buildLogMap(opts.Config) if err != nil { return fmt.Errorf("failed to load configurations: %v", err) } w, err := witness.New(witness.Opts{ DB: db, PrivKey: opts.PrivKey, KnownLogs: logMap, }) if err != nil { return fmt.Errorf("error creating witness: %v", err) } klog.Infof("Starting witness server...") srv := ih.NewServer(w) // These options are required as some logID values contain forward // slashes, and will be PathUnescape-d later. r := mux.NewRouter().UseEncodedPath() srv.RegisterHandlers(r) hServer := &http.Server{ Addr: opts.ListenAddr, Handler: r, } e := make(chan error, 1) go func() { e <- hServer.ListenAndServe() close(e) }() <-ctx.Done() klog.Info("Server shutting down") if err := hServer.Shutdown(ctx); err != nil { klog.Errorf("Shutdown(): %v", err) } return <-e } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/000077500000000000000000000000001462611535200302245ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/http/000077500000000000000000000000001462611535200312035ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/http/server.go000066400000000000000000000107171462611535200330460ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package http contains private implementation details for the witness server. package http import ( "encoding/json" "fmt" "io" "net/http" "net/url" "github.com/google/certificate-transparency-go/internal/witness/api" "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/internal/witness" "github.com/gorilla/mux" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog/v2" ) // Server is the core handler implementation of the witness. type Server struct { w *witness.Witness } // NewServer creates a new server. func NewServer(witness *witness.Witness) *Server { return &Server{ w: witness, } } // update handles requests to update STHs. // It expects a PUT body containing a JSON-formatted api.UpdateRequest // statement and returns a JSON-formatted api.UpdateResponse statement. func (s *Server) update(w http.ResponseWriter, r *http.Request) { v := mux.Vars(r) logID, err := url.PathUnescape(v["logid"]) if err != nil { http.Error(w, fmt.Sprintf("cannot parse URL: %v", err.Error()), http.StatusBadRequest) } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, fmt.Sprintf("cannot read request body: %v", err.Error()), http.StatusBadRequest) return } var req api.UpdateRequest if err := json.Unmarshal(body, &req); err != nil { http.Error(w, fmt.Sprintf("cannot parse request body as proper JSON struct: %v", err.Error()), http.StatusBadRequest) return } // Get the output from the witness. sth, err := s.w.Update(r.Context(), logID, req.STH, req.Proof) if err != nil { // If there was a failed precondition it's possible the caller was // just out of date. Give the returned STH to help them // form a new request. if c := status.Code(err); c == codes.FailedPrecondition { w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(httpForCode(c)) // The returned STH gets written a few lines below. } else { http.Error(w, fmt.Sprintf("failed to update to new STH: %v", err), httpForCode(http.StatusInternalServerError)) return } } w.Header().Set("Content-Type", "text/plain") if _, err := w.Write(sth); err != nil { klog.Errorf("Write(): %v", err) } } // getSTH returns an STH stored for a given log. func (s *Server) getSTH(w http.ResponseWriter, r *http.Request) { v := mux.Vars(r) logID, err := url.PathUnescape(v["logid"]) if err != nil { http.Error(w, fmt.Sprintf("cannot parse URL: %v", err.Error()), http.StatusBadRequest) } // Get the STH from the witness. sth, err := s.w.GetSTH(logID) if err != nil { http.Error(w, fmt.Sprintf("failed to get STH: %v", err), httpForCode(status.Code(err))) return } w.Header().Set("Content-Type", "text/plain") if _, err := w.Write(sth); err != nil { klog.Errorf("Write(): %v", err) } } // getLogs returns a list of all logs the witness is aware of. func (s *Server) getLogs(w http.ResponseWriter, r *http.Request) { logs, err := s.w.GetLogs() if err != nil { http.Error(w, fmt.Sprintf("failed to get log list: %v", err), http.StatusInternalServerError) return } logList, err := json.Marshal(logs) if err != nil { http.Error(w, fmt.Sprintf("failed to convert log list to JSON: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/json") if _, err := w.Write(logList); err != nil { klog.Errorf("Write(): %v", err) } } // RegisterHandlers registers HTTP handlers for witness endpoints. func (s *Server) RegisterHandlers(r *mux.Router) { logStr := "{logid}" r.HandleFunc(fmt.Sprintf(api.HTTPGetSTH, logStr), s.getSTH).Methods("GET") r.HandleFunc(fmt.Sprintf(api.HTTPUpdate, logStr), s.update).Methods("PUT") r.HandleFunc(api.HTTPGetLogs, s.getLogs).Methods("GET") } func httpForCode(c codes.Code) int { switch c { case codes.NotFound: return http.StatusNotFound case codes.FailedPrecondition: return http.StatusConflict default: return http.StatusInternalServerError } } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/http/server_test.go000066400000000000000000000256461462611535200341140ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "context" "crypto" "database/sql" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "net/url" "sort" "strings" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/internal/witness/api" "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/internal/witness" "github.com/gorilla/mux" _ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3 ) var ( // https://play.golang.org/p/gCY2Zi2BJ8G to generate keys and // https://play.golang.org/p/KUXRShKdYTb to sign things with loaded keys. mSK = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIECRHc4ORynd+lpqWYmjCIAmDjyLEJZSuvv4KdcIi+hEoAoGCCqGSM49 AwEHoUQDQgAEn1Ahe5/kYQgqYk1kSzp0ZCvL1Cf/tOZ+GUrGjNC0CrTqSylMuU1f AcWDaKYB/Yr3fq/5lNqJBRjsOnI4KkaEtw== -----END EC PRIVATE KEY-----` mPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEn1Ahe5/kYQgqYk1kSzp0ZCvL1Cf/ tOZ+GUrGjNC0CrTqSylMuU1fAcWDaKYB/Yr3fq/5lNqJBRjsOnI4KkaEtw== -----END PUBLIC KEY-----`) mID = "fRThG/6Ymon8NnpRMQJIgCMgjtrBVnOidYenOB0n6FI=" bSK = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIICRst6QhwffAkeOQGIhcCSmB7/LYQXevwrv8TD9FjU7oAoGCCqGSM49 AwEHoUQDQgAE5FTw9vYXDEFiZb9kS1LV7GzU1Mo/xQ8D2Vnkl7WqNTB2kJ45aTtl F2bBk8i50oWNRlRLyi5MVl7j+6LVhMiBeA== -----END EC PRIVATE KEY-----` bPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5FTw9vYXDEFiZb9kS1LV7GzU1Mo/ xQ8D2Vnkl7WqNTB2kJ45aTtlF2bBk8i50oWNRlRLyi5MVl7j+6LVhMiBeA== -----END PUBLIC KEY-----`) bID = "CwWwEY4IKzy1bfZ6QW0IU9mky0ruOQvzWOYkmRGMVP4=" wSK = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+/pzQGPt88nmVlMC CjHXGLH93bZ5ZkLVTjsHLi2UQiKhRANCAAQ2DYOW5eMnGcMCDtfK7aFIJg0JBKIZ cx8fz81azP6v6s8oYMyU5e5bYAfgm1RjGvjC2YTLqCpMvSIeK+rudqg4 -----END PRIVATE KEY-----` wPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENg2DluXjJxnDAg7Xyu2hSCYNCQSi GXMfH8/NWsz+r+rPKGDMlOXuW2AH4JtUYxr4wtmEy6gqTL0iHivq7naoOA== -----END PUBLIC KEY-----`) mInit = []byte(`{"tree_size":5,"timestamp":0,"sha256_root_hash":"41smjBUiAU70EtKlT6lIOIYtRTYxYXsDB+XHfcvu/BE=","tree_head_signature":"BAMARzBFAiEA4CEXH2Z+T4Rcj3YTvgK5qM9NuFYHipI13Il6A/ozTFUCIBDY1VDFy8ZezXsuWNs+iLzkyO5I5kCZldGeMvspHOof"}`) bInit = []byte(`{"tree_size":5,"timestamp":0,"sha256_root_hash":"41smjBUiAU70EtKlT6lIOIYtRTYxYXsDB+XHfcvu/BE=","tree_head_signature":"BAMASDBGAiEAjSUy1d7/n1MOYWCnx2DzU3nQk1OUHzRtFJl+eDCquBsCIQDEG2vk1A+LmHZfyt/BN4by2324rxWFFzAeG1f2EyXk9w=="}`) mNext = []byte(`{"tree_size":8,"timestamp":1,"sha256_root_hash":"V8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=","tree_head_signature":"BAMARjBEAiB9SZfr3JJbLsSE4mhnHE9hbcbu97nsbKcONnXeJXeigwIgJTWVh5FLNfUre5uCRLY4B1KEyS8tcGbaaHdEMk2WAmc="}`) consProof = [][]byte{ dh("b9e1d62618f7fee8034e4c5010f727ab24d8e4705cb296c374bf2025a87a10d2", 32), dh("aac66cd7a79ce4012d80762fe8eec3a77f22d1ca4145c3f4cee022e7efcd599d", 32), dh("89d0f753f66a290c483b39cd5e9eafb12021293395fad3d4a2ad053cfbcfdc9e", 32), dh("29e40bb79c966f4c6fe96aff6f30acfce5f3e8d84c02215175d6e018a5dee833", 32), } _ = mSK _ = bSK _ = wPK ) type logOpts struct { ID string PK crypto.PublicKey } func newWitness(t *testing.T, d *sql.DB, logs []logOpts) *witness.Witness { // Set up Opts for the witness. logMap := make(map[string]ct.SignatureVerifier) for _, log := range logs { logV, err := ct.NewSignatureVerifier(log.PK) if err != nil { t.Fatalf("couldn't create a log verifier: %v", err) } logMap[log.ID] = *logV } opts := witness.Opts{ DB: d, PrivKey: wSK, KnownLogs: logMap, } // Create the witness w, err := witness.New(opts) if err != nil { t.Fatalf("couldn't create witness: %v", err) } return w } func dh(h string, expLen int) []byte { r, err := hex.DecodeString(h) if err != nil { panic(err) } if got := len(r); got != expLen { panic(fmt.Sprintf("decode %q: len=%d, want %d", h, got, expLen)) } return r } func mustCreateDB(t *testing.T) (*sql.DB, func()) { t.Helper() db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open temporary in-memory DB: %v", err) } return db, func() { if err := db.Close(); err != nil { t.Error(err) } } } func mustCreatePK(pkPem string) crypto.PublicKey { pk, _, _, err := ct.PublicKeyFromPEM([]byte(pkPem)) if err != nil { panic(err) } return pk } func createTestEnv(w *witness.Witness) (*httptest.Server, func()) { r := mux.NewRouter().UseEncodedPath() server := NewServer(w) server.RegisterHandlers(r) ts := httptest.NewServer(r) return ts, ts.Close } func TestGetLogs(t *testing.T) { for _, test := range []struct { desc string logIDs []string logPKs []crypto.PublicKey sths [][]byte wantStatus int wantBody []string }{ { desc: "no logs", logIDs: []string{}, wantStatus: http.StatusOK, wantBody: []string{}, }, { desc: "one log", logIDs: []string{mID}, logPKs: []crypto.PublicKey{mPK}, sths: [][]byte{mInit}, wantStatus: http.StatusOK, wantBody: []string{mID}, }, { desc: "two logs", logIDs: []string{bID, mID}, logPKs: []crypto.PublicKey{bPK, mPK}, sths: [][]byte{bInit, mInit}, wantStatus: http.StatusOK, wantBody: []string{bID, mID}, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() // Set up witness and give it some STHs. logs := make([]logOpts, len(test.logIDs)) for i, logID := range test.logIDs { logs[i] = logOpts{ID: logID, PK: test.logPKs[i], } } w := newWitness(t, d, logs) for i, logID := range test.logIDs { if _, err := w.Update(ctx, logID, test.sths[i], nil); err != nil { t.Errorf("failed to set STH: %v", err) } } // Now set up the http server. ts, tsCloseFn := createTestEnv(w) defer tsCloseFn() client := ts.Client() url := fmt.Sprintf("%s%s", ts.URL, api.HTTPGetLogs) resp, err := client.Get(url) if err != nil { t.Errorf("error response: %v", err) } if got, want := resp.StatusCode, test.wantStatus; got != want { t.Errorf("status code got %d, want %d", got, want) } if len(test.wantBody) > 0 { body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("failed to read body: %v", err) } var logs []string if err := json.Unmarshal(body, &logs); err != nil { t.Fatalf("failed to unmarshal body: %v", err) } if len(logs) != len(test.wantBody) { t.Fatalf("got %d logs, want %d", len(logs), len(test.wantBody)) } sort.Strings(logs) for i := range logs { if logs[i] != test.wantBody[i] { t.Fatalf("got %q, want %q", logs[i], test.wantBody[i]) } } } }) } } func TestGetChkpt(t *testing.T) { for _, test := range []struct { desc string setID string setPK crypto.PublicKey queryID string queryPK crypto.PublicKey sth []byte wantStatus int }{ { desc: "happy path", setID: mID, setPK: mPK, queryID: mID, queryPK: mPK, sth: mInit, wantStatus: http.StatusOK, }, { desc: "other log", setID: mID, setPK: mPK, queryID: bID, sth: mInit, wantStatus: http.StatusNotFound, }, { desc: "nothing there", setID: mID, setPK: mPK, queryID: mID, sth: nil, wantStatus: http.StatusNotFound, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() // Set up witness. w := newWitness(t, d, []logOpts{{ID: test.setID, PK: test.setPK}}) // Set an STH for the log if we want to for this test. if test.sth != nil { if _, err := w.Update(ctx, test.setID, test.sth, nil); err != nil { t.Errorf("failed to set STH: %v", err) } } // Now set up the http server. ts, tsCloseFn := createTestEnv(w) defer tsCloseFn() client := ts.Client() chkptQ := fmt.Sprintf(api.HTTPGetSTH, url.PathEscape(test.queryID)) url := fmt.Sprintf("%s%s", ts.URL, chkptQ) resp, err := client.Get(url) if err != nil { t.Errorf("error response: %v", err) } if got, want := resp.StatusCode, test.wantStatus; got != want { t.Errorf("status code got %d, want %d", got, want) } }) } } func TestUpdate(t *testing.T) { for _, test := range []struct { desc string initC []byte initSize uint64 body api.UpdateRequest wantStatus int }{ { desc: "happy path", initC: mInit, initSize: 5, body: api.UpdateRequest{STH: mNext, Proof: consProof}, wantStatus: http.StatusOK, }, { desc: "smaller STH", initC: mNext, initSize: 8, body: api.UpdateRequest{STH: mInit, Proof: consProof}, wantStatus: http.StatusConflict, }, { desc: "garbage proof", initC: mInit, initSize: 5, body: api.UpdateRequest{STH: mNext, Proof: [][]byte{ dh("aaaa", 2), dh("bbbb", 2), dh("cccc", 2), dh("dddd", 2), }}, wantStatus: http.StatusConflict, }, { desc: "garbage STH", initC: mInit, initSize: 5, body: api.UpdateRequest{STH: []byte("aaa"), Proof: consProof}, wantStatus: http.StatusInternalServerError, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() logID := mID // Set up witness. w := newWitness(t, d, []logOpts{{ID: logID, PK: mPK}}) // Set an initial STH for the log. if _, err := w.Update(ctx, logID, test.initC, nil); err != nil { t.Errorf("failed to set STH: %v", err) } // Now set up the http server. ts, tsCloseFn := createTestEnv(w) defer tsCloseFn() // Update to a newer STH. client := ts.Client() reqBody, err := json.Marshal(test.body) if err != nil { t.Fatalf("couldn't parse request: %v", err) } url := fmt.Sprintf("%s%s", ts.URL, fmt.Sprintf(api.HTTPUpdate, url.PathEscape(logID))) req, err := http.NewRequest(http.MethodPut, url, strings.NewReader(string(reqBody))) if err != nil { t.Fatalf("couldn't form http request: %v", err) } resp, err := client.Do(req) if err != nil { t.Errorf("error response: %v", err) } if got, want := resp.StatusCode, test.wantStatus; got != want { t.Errorf("status code got %d, want %d", got, want) } }) } } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/witness/000077500000000000000000000000001462611535200317205ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/witness/witness.go000066400000000000000000000230321462611535200337430ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package witness is designed to make sure the STHs of CT logs are consistent // and store/serve/sign them if so. It is expected that a separate feeder // component would be responsible for the actual interaction with logs. package witness import ( "bytes" "context" "crypto" "crypto/x509" "database/sql" "encoding/json" "encoding/pem" "errors" "fmt" "reflect" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/internal/witness/api" "github.com/google/certificate-transparency-go/tls" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog/v2" ) // Opts is the options passed to a witness. type Opts struct { DB *sql.DB PrivKey string KnownLogs map[string]ct.SignatureVerifier } // Witness consists of a database for storing STHs, a signing key, and a list // of logs for which it stores and verifies STHs. type Witness struct { db *sql.DB sk crypto.PrivateKey Logs map[string]ct.SignatureVerifier } // New creates a new witness, which initially has no logs to follow. func New(wo Opts) (*Witness, error) { // Create the sths table if needed. _, err := wo.DB.Exec(`CREATE TABLE IF NOT EXISTS sths (logID BLOB PRIMARY KEY, sth BLOB)`) if err != nil { return nil, fmt.Errorf("failed to create table: %v", err) } // Parse the PEM-encoded secret key. p, _ := pem.Decode([]byte(wo.PrivKey)) if p == nil { return nil, errors.New("no PEM block found in secret key string") } sk, err := x509.ParsePKCS8PrivateKey(p.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse private key: %v", err) } // x509 can return either the key object or a pointer to it. This // discrepancy is handled in the same way as in tls/signature_test.go, // namely changing it to be the object if it's a pointer. if reflect.TypeOf(sk).Kind() == reflect.Ptr { sk = reflect.ValueOf(sk).Elem().Interface() } return &Witness{ db: wo.DB, sk: sk, Logs: wo.KnownLogs, }, nil } // parse verifies the STH under the appropriate key for logID and returns // the parsed STH. If the STH contained an incorrect logID the witness returns // an error indicating this, and if the logID is missing the witness fills it in. // This assumes sthRaw parses as a SignedTreeHead (not a CosignedSTH), so STHs are // stored unsigned and signed only right when they are being returned. func (w *Witness) parse(sthRaw []byte, logID string) (*ct.SignedTreeHead, error) { sv, ok := w.Logs[logID] if !ok { return nil, fmt.Errorf("log %q not found", logID) } var sth ct.SignedTreeHead if err := json.Unmarshal(sthRaw, &sth); err != nil { return nil, fmt.Errorf("failed to unmarshal json: %v", err) } var idHash ct.SHA256Hash if err := idHash.FromBase64String(logID); err != nil { return nil, fmt.Errorf("failed to decode logID: %v", err) } var empty ct.SHA256Hash if bytes.Equal(sth.LogID[:], empty[:]) { sth.LogID = idHash } else if !bytes.Equal(sth.LogID[:], idHash[:]) { return nil, status.Errorf(codes.FailedPrecondition, "STH logID = %q, input logID = %q", sth.LogID.Base64String(), logID) } if err := sv.VerifySTHSignature(sth); err != nil { return nil, fmt.Errorf("failed to verify STH signature: %v", err) } return &sth, nil } // GetLogs returns a list of all logs the witness is aware of. func (w *Witness) GetLogs() ([]string, error) { rows, err := w.db.Query("SELECT logID FROM sths") if err != nil { return nil, err } defer func() { if err := rows.Close(); err != nil { klog.Errorf("Operation to close rows failed: %v", err) } }() var logs []string for rows.Next() { var logID string err := rows.Scan(&logID) if err != nil { return nil, err } logs = append(logs, logID) } if err := rows.Err(); err != nil { return nil, err } return logs, nil } // GetSTH gets a cosigned STH for a given log, which is consistent with all // other STHs for the same log signed by this witness. func (w *Witness) GetSTH(logID string) ([]byte, error) { sthRaw, err := w.getLatestSTH(w.db.QueryRow, logID) if err != nil { return nil, err } sth, err := w.parse(sthRaw, logID) if err != nil { return nil, fmt.Errorf("couldn't parse raw STH: %v", err) } signed, err := w.signSTH(sth) if err != nil { return nil, fmt.Errorf("couldn't sign retrieved STH: %v", err) } return signed, nil } // Update updates the latest STH if nextRaw is consistent with the current // latest one for this log. It returns the latest cosigned STH held by // the witness, which is a signed version of nextRaw if the update was applied. func (w *Witness) Update(ctx context.Context, logID string, nextRaw []byte, pf [][]byte) ([]byte, error) { // If we don't witness this log then no point in going further. _, ok := w.Logs[logID] if !ok { return nil, status.Errorf(codes.NotFound, "log %q not found", logID) } // Check the signatures on the raw STH and parse it into the STH format. next, err := w.parse(nextRaw, logID) if err != nil { return nil, fmt.Errorf("couldn't parse input STH: %v", err) } // Get the latest one for the log because we don't want consistency proofs // with respect to older STHs. Bind this all in a transaction to // avoid race conditions when updating the database. tx, err := w.db.BeginTx(ctx, nil) if err != nil { return nil, fmt.Errorf("couldn't create db tx: %v", err) } defer func() { if err := tx.Rollback(); err != nil { klog.Errorf("Rollback(): %v", err) } }() // Get the latest STH (if one exists). prevRaw, err := w.getLatestSTH(tx.QueryRow, logID) if err != nil { // If there was nothing stored already then treat this new // STH as trust-on-first-use (TOFU). if status.Code(err) == codes.NotFound { if err := w.setSTH(tx, logID, nextRaw); err != nil { return nil, fmt.Errorf("couldn't set TOFU STH: %v", err) } signed, err := w.signSTH(next) if err != nil { return nil, fmt.Errorf("couldn't sign STH: %v", err) } return signed, nil } return nil, fmt.Errorf("couldn't retrieve latest STH: %w", err) } // Parse the raw retrieved STH into the STH format. prev, err := w.parse(prevRaw, logID) if err != nil { return nil, fmt.Errorf("couldn't parse stored STH: %v", err) } if next.TreeSize < prev.TreeSize { // Complain if prev is bigger than next. return prevRaw, status.Errorf(codes.FailedPrecondition, "cannot prove consistency backwards (%d < %d)", next.TreeSize, prev.TreeSize) } if next.TreeSize == prev.TreeSize { if !bytes.Equal(next.SHA256RootHash[:], prev.SHA256RootHash[:]) { return prevRaw, status.Errorf(codes.FailedPrecondition, "STH for same size log with differing hash (got %x, have %x)", next.SHA256RootHash, prev.SHA256RootHash) } // If it's identical to the previous one do nothing. return prevRaw, nil } // The only remaining option is next.Size > prev.Size. This might be // valid so we verify the consistency proof. if err := proof.VerifyConsistency(rfc6962.DefaultHasher, prev.TreeSize, next.TreeSize, pf, prev.SHA256RootHash[:], next.SHA256RootHash[:]); err != nil { // Complain if the STHs aren't consistent. return prevRaw, status.Errorf(codes.FailedPrecondition, "failed to verify consistency proof: %v", err) } // If the consistency proof is good we store the raw STH and return the // signed one. if err := w.setSTH(tx, logID, nextRaw); err != nil { return nil, fmt.Errorf("failed to store new STH: %v", err) } signed, err := w.signSTH(next) if err != nil { return nil, fmt.Errorf("failed to sign new STH: %v", err) } return signed, nil } // signSTH adds the witness' signature to an STH. func (w *Witness) signSTH(sth *ct.SignedTreeHead) ([]byte, error) { sigInput, err := tls.Marshal(*sth) if err != nil { return nil, fmt.Errorf("couldn't marshal signature input: %v", err) } sig, err := tls.CreateSignature(w.sk, tls.SHA256, sigInput) if err != nil { return nil, fmt.Errorf("couldn't sign STH data: %v", err) } cosigned := api.CosignedSTH{SignedTreeHead: *sth, WitnessSigs: []ct.DigitallySigned{ct.DigitallySigned(sig)}, } cosignedRaw, err := json.Marshal(cosigned) if err != nil { return nil, fmt.Errorf("couldn't marshal cosigned STH: %v", err) } return cosignedRaw, nil } // getLatestSTH returns the raw stored data for the latest STH of a given log. func (w *Witness) getLatestSTH(queryRow func(query string, args ...interface{}) *sql.Row, logID string) ([]byte, error) { row := queryRow("SELECT sth FROM sths WHERE logID = ?", logID) if err := row.Err(); err != nil { return nil, err } var sth []byte if err := row.Scan(&sth); err != nil { if err == sql.ErrNoRows { return nil, status.Errorf(codes.NotFound, "no STH for log %q", logID) } return nil, err } return sth, nil } // setSTH writes the STH to the database for a given log. func (w *Witness) setSTH(tx *sql.Tx, logID string, sth []byte) error { if _, err := tx.Exec(`INSERT OR REPLACE INTO sths (logID, sth) VALUES (?, ?)`, logID, sth); err != nil { return fmt.Errorf("failed to update STH; %v", err) } return tx.Commit() } witness_test.go000066400000000000000000000246011462611535200347260ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/internal/witness// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package witness import ( "context" "crypto" "database/sql" "encoding/hex" "encoding/json" "fmt" "sort" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/internal/witness/api" "github.com/google/certificate-transparency-go/tls" _ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3 ) var ( // https://play.golang.org/p/gCY2Zi2BJ8G to generate keys and // https://play.golang.org/p/KUXRShKdYTb to sign things with loaded // keys. Importantly, need to switch to using MarshalPKCS8PrivateKey // for the witness keys. mSK = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIECRHc4ORynd+lpqWYmjCIAmDjyLEJZSuvv4KdcIi+hEoAoGCCqGSM49 AwEHoUQDQgAEn1Ahe5/kYQgqYk1kSzp0ZCvL1Cf/tOZ+GUrGjNC0CrTqSylMuU1f AcWDaKYB/Yr3fq/5lNqJBRjsOnI4KkaEtw== -----END EC PRIVATE KEY-----` mPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEn1Ahe5/kYQgqYk1kSzp0ZCvL1Cf/ tOZ+GUrGjNC0CrTqSylMuU1fAcWDaKYB/Yr3fq/5lNqJBRjsOnI4KkaEtw== -----END PUBLIC KEY-----`) mID = "fRThG/6Ymon8NnpRMQJIgCMgjtrBVnOidYenOB0n6FI=" bSK = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIICRst6QhwffAkeOQGIhcCSmB7/LYQXevwrv8TD9FjU7oAoGCCqGSM49 AwEHoUQDQgAE5FTw9vYXDEFiZb9kS1LV7GzU1Mo/xQ8D2Vnkl7WqNTB2kJ45aTtl F2bBk8i50oWNRlRLyi5MVl7j+6LVhMiBeA== -----END EC PRIVATE KEY-----` bPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5FTw9vYXDEFiZb9kS1LV7GzU1Mo/ xQ8D2Vnkl7WqNTB2kJ45aTtlF2bBk8i50oWNRlRLyi5MVl7j+6LVhMiBeA== -----END PUBLIC KEY-----`) bID = "CwWwEY4IKzy1bfZ6QW0IU9mky0ruOQvzWOYkmRGMVP4=" wSK = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+/pzQGPt88nmVlMC CjHXGLH93bZ5ZkLVTjsHLi2UQiKhRANCAAQ2DYOW5eMnGcMCDtfK7aFIJg0JBKIZ cx8fz81azP6v6s8oYMyU5e5bYAfgm1RjGvjC2YTLqCpMvSIeK+rudqg4 -----END PRIVATE KEY-----` wPK = mustCreatePK(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENg2DluXjJxnDAg7Xyu2hSCYNCQSi GXMfH8/NWsz+r+rPKGDMlOXuW2AH4JtUYxr4wtmEy6gqTL0iHivq7naoOA== -----END PUBLIC KEY-----`) mInit = []byte(`{"tree_size":5,"timestamp":0,"sha256_root_hash":"41smjBUiAU70EtKlT6lIOIYtRTYxYXsDB+XHfcvu/BE=","tree_head_signature":"BAMARzBFAiEA4CEXH2Z+T4Rcj3YTvgK5qM9NuFYHipI13Il6A/ozTFUCIBDY1VDFy8ZezXsuWNs+iLzkyO5I5kCZldGeMvspHOof"}`) bInit = []byte(`{"tree_size":5,"timestamp":0,"sha256_root_hash":"41smjBUiAU70EtKlT6lIOIYtRTYxYXsDB+XHfcvu/BE=","tree_head_signature":"BAMASDBGAiEAjSUy1d7/n1MOYWCnx2DzU3nQk1OUHzRtFJl+eDCquBsCIQDEG2vk1A+LmHZfyt/BN4by2324rxWFFzAeG1f2EyXk9w=="}`) mNext = []byte(`{"tree_size":8,"timestamp":1,"sha256_root_hash":"V8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=","tree_head_signature":"BAMARjBEAiB9SZfr3JJbLsSE4mhnHE9hbcbu97nsbKcONnXeJXeigwIgJTWVh5FLNfUre5uCRLY4B1KEyS8tcGbaaHdEMk2WAmc="}`) consProof = [][]byte{ dh("b9e1d62618f7fee8034e4c5010f727ab24d8e4705cb296c374bf2025a87a10d2", 32), dh("aac66cd7a79ce4012d80762fe8eec3a77f22d1ca4145c3f4cee022e7efcd599d", 32), dh("89d0f753f66a290c483b39cd5e9eafb12021293395fad3d4a2ad053cfbcfdc9e", 32), dh("29e40bb79c966f4c6fe96aff6f30acfce5f3e8d84c02215175d6e018a5dee833", 32), } _ = mSK _ = bSK _ = wPK ) type logOpts struct { ID string PK crypto.PublicKey } func mustCreatePK(pkPem string) crypto.PublicKey { pk, _, _, err := ct.PublicKeyFromPEM([]byte(pkPem)) if err != nil { panic(err) } return pk } func newWitness(t *testing.T, d *sql.DB, logs []logOpts) *Witness { // Set up Opts for the witness. logMap := make(map[string]ct.SignatureVerifier) for _, log := range logs { sigV, err := ct.NewSignatureVerifier(log.PK) if err != nil { t.Fatalf("couldn't create a log verifier: %v", err) } logMap[log.ID] = *sigV } opts := Opts{ DB: d, PrivKey: wSK, KnownLogs: logMap, } // Create the witness. w, err := New(opts) if err != nil { t.Fatalf("couldn't create witness: %v", err) } return w } func dh(h string, expLen int) []byte { r, err := hex.DecodeString(h) if err != nil { panic(err) } if got := len(r); got != expLen { panic(fmt.Sprintf("decode %q: len=%d, want %d", h, got, expLen)) } return r } func mustCreateDB(t *testing.T) (*sql.DB, func()) { t.Helper() db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open temporary in-memory DB: %v", err) } return db, func() { if err := db.Close(); err != nil { t.Error(err) } } } func TestGetLogs(t *testing.T) { for _, test := range []struct { desc string logIDs []string logPKs []crypto.PublicKey sths [][]byte }{ { desc: "no logs", logIDs: []string{}, }, { desc: "one log", logIDs: []string{mID}, logPKs: []crypto.PublicKey{mPK}, sths: [][]byte{mInit}, }, { desc: "two logs", logIDs: []string{bID, mID}, logPKs: []crypto.PublicKey{bPK, mPK}, sths: [][]byte{bInit, mInit}, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() // Set up witness. logs := make([]logOpts, len(test.logIDs)) for i, logID := range test.logIDs { logs[i] = logOpts{ID: logID, PK: test.logPKs[i], } } w := newWitness(t, d, logs) // Update to an STH for all logs. for i, logID := range test.logIDs { if _, err := w.Update(ctx, logID, test.sths[i], nil); err != nil { t.Errorf("failed to set STH: %v", err) } } // Now see if the witness knows about these logs. knownLogs, err := w.GetLogs() if err != nil { t.Fatalf("couldn't get logs from witness: %v", err) } if len(knownLogs) != len(test.logIDs) { t.Fatalf("got %d logs, want %d", len(knownLogs), len(test.logIDs)) } sort.Strings(knownLogs) for i := range knownLogs { if knownLogs[i] != test.logIDs[i] { t.Fatalf("got %q, want %q", test.logIDs[i], knownLogs[i]) } } }) } } func TestGetSTH(t *testing.T) { for _, test := range []struct { desc string setID string setPK crypto.PublicKey queryID string queryPK crypto.PublicKey sth []byte wantThere bool }{ { desc: "happy path", setID: mID, setPK: mPK, queryID: mID, queryPK: mPK, sth: mInit, wantThere: true, }, { desc: "other log", setID: mID, setPK: mPK, queryID: bID, queryPK: bPK, sth: mInit, wantThere: false, }, { desc: "nothing there", setID: mID, setPK: mPK, queryID: mID, queryPK: mPK, sth: nil, wantThere: false, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() // Set up witness. w := newWitness(t, d, []logOpts{{ID: test.setID, PK: test.setPK}}) // Set an STH for the log if we want to for this test. if test.sth != nil { if _, err := w.Update(ctx, test.setID, test.sth, nil); err != nil { t.Errorf("failed to set STH: %v", err) } } // Try to get the latest STH. sthRaw, err := w.GetSTH(test.queryID) if !test.wantThere && err == nil { t.Fatalf("returned an STH but shouldn't have") } // Check to see if we got something. if test.wantThere { if err != nil { t.Fatalf("failed to get latest: %v", err) } sv, err := ct.NewSignatureVerifier(wPK) if err != nil { t.Fatalf("failed to create signature verifier: %v", err) } var sth api.CosignedSTH if err := json.Unmarshal(sthRaw, &sth); err != nil { t.Fatalf("failed to unmarshal raw STH: %v", err) } sig := tls.DigitallySigned(sth.WitnessSigs[0]) sigData, err := tls.Marshal(sth.SignedTreeHead) if err != nil { t.Fatalf("failed to marshal internal STH: %v", err) } if err := sv.VerifySignature(sigData, sig); err != nil { t.Fatal("failed to verify co-signature") } } }) } } func TestUpdate(t *testing.T) { for _, test := range []struct { desc string initSTH []byte initSize uint64 newSTH []byte pf [][]byte isGood bool }{ { desc: "happy path", initSTH: mInit, initSize: 5, newSTH: mNext, pf: consProof, isGood: true, }, { desc: "smaller STH", initSTH: mNext, initSize: 8, newSTH: mInit, pf: consProof, isGood: false, }, { desc: "garbage proof", initSTH: mInit, initSize: 5, newSTH: mNext, pf: [][]byte{ dh("aaaa", 2), dh("bbbb", 2), dh("cccc", 2), dh("dddd", 2), }, isGood: false, }, { desc: "right logID", initSTH: mInit, initSize: 5, newSTH: []byte(`{"log_id":"fRThG/6Ymon8NnpRMQJIgCMgjtrBVnOidYenOB0n6FI=","tree_size":8,"timestamp":1,"sha256_root_hash":"V8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=","tree_head_signature":"BAMARzBFAiEA2yPvkeRF0cvGOAxx0s+NUf7LT9gumx3MDYob3swzgHgCICGN1tbbu8FqagkE5kV0DSL3CsQWjv095AeL7b+iFMOu"}`), pf: consProof, isGood: true, }, { desc: "wrong logID", initSTH: mInit, initSize: 5, newSTH: []byte(`{"log_id":"aaaaa/6Ymon8NnpRMQJIgCMgjtrBVnOidYenOB0n6FI=","tree_size":8,"timestamp":1,"sha256_root_hash":"V8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=","tree_head_signature":"BAMARzBFAiEA2yPvkeRF0cvGOAxx0s+NUf7LT9gumx3MDYob3swzgHgCICGN1tbbu8FqagkE5kV0DSL3CsQWjv095AeL7b+iFMOu"}`), pf: consProof, isGood: false, }, } { t.Run(test.desc, func(t *testing.T) { d, closeFn := mustCreateDB(t) defer closeFn() ctx := context.Background() logID := mID // Set up witness. w := newWitness(t, d, []logOpts{{ID: logID, PK: mPK}}) // Set an initial STH for the log. if _, err := w.Update(ctx, logID, test.initSTH, nil); err != nil { t.Errorf("failed to set STH: %v", err) } // Now update from this STH to a newer one. _, err := w.Update(ctx, logID, test.newSTH, test.pf) if test.isGood { if err != nil { t.Fatalf("can't update to new STH: %v", err) } } else { if err == nil { t.Fatal("should have gotten an error but didn't") } } }) } } google-certificate-transparency-go-2308f62/internal/witness/cmd/witness/main.go000066400000000000000000000044621462611535200276710ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package witness is designed to make sure the STHs of CT logs are consistent // and store/serve/sign them if so. It is expected that a separate feeder // component would be responsible for the actual interaction with logs. package main import ( "context" "flag" "os" "github.com/google/certificate-transparency-go/internal/witness/cmd/witness/impl" "gopkg.in/yaml.v3" "k8s.io/klog/v2" ) var ( listenAddr = flag.String("listen", ":8000", "address:port to listen for requests on") // If this is run with an in-memory database there is a good chance of the // witness occasionally hitting a race condition and returning a 500. If // a file is specified this won't happen. dbFile = flag.String("db_file", ":memory:", "path to a file to be used as sqlite3 storage for STHs, e.g. /tmp/sths.db") configFile = flag.String("config_file", "config/config.yaml", "path to a YAML config file that specifies the logs followed by this witness") witnessSK = flag.String("private_key", "", "private signing key for the witness") ) func main() { klog.InitFlags(nil) flag.Parse() if *witnessSK == "" { klog.Exit("--private_key must not be empty") } if *configFile == "" { klog.Exit("--config_file must not be empty") } fileData, err := os.ReadFile(*configFile) if err != nil { klog.Exitf("Failed to read from config file: %v", err) } var lc impl.LogConfig if err := yaml.Unmarshal(fileData, &lc); err != nil { klog.Exitf("Failed to parse config file as proper YAML: %v", err) } ctx := context.Background() if err := impl.Main(ctx, impl.ServerOpts{ ListenAddr: *listenAddr, DBFile: *dbFile, PrivKey: *witnessSK, Config: lc, }); err != nil { klog.Exitf("Error running witness: %v", err) } } google-certificate-transparency-go-2308f62/internal/witness/omniwitness/000077500000000000000000000000001462611535200265305ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/omniwitness/.env000066400000000000000000000001371462611535200273220ustar00rootroot00000000000000WITNESS_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- YOURPRIVATEKEYHERE -----END PRIVATE KEY-----" google-certificate-transparency-go-2308f62/internal/witness/omniwitness/README.md000066400000000000000000000012301462611535200300030ustar00rootroot00000000000000 ## A CT omniwitness This docker configuration allows a witness to be deployed that witnesses STHs for all usable CT logs. The witness obtains a new STH from these logs every 10 seconds and exposes port 8100 outside the container, meaning it could also be used to distribute the cosigned STHs. ### Configuration The only file that needs to be edited is the `.env` file. In particular, it should be populated with a PEM-encoded `WITNESS_PRIVATE_KEY`. ### Running From the directory containing the `docker-compose.yaml` file, run `docker-compose up -d`. After a few minutes both the feeder and the witness processes should be running inside the container. google-certificate-transparency-go-2308f62/internal/witness/omniwitness/docker-compose.yaml000066400000000000000000000016011462611535200323240ustar00rootroot00000000000000version: '3.2' services: witness: build: context: ../../.. dockerfile: ./internal/witness/cmd/witness/Dockerfile volumes: - type: volume source: data target: /data volume: nocopy: true - type: bind source: ./witness_configs target: /witness-config read_only: true command: - "--listen=:8100" - "--db_file=/data/witness.sqlite" - "--private_key=${WITNESS_PRIVATE_KEY}" - "--config_file=/witness-config/witness.yaml" - "--logtostderr" restart: always ports: - "8100:8100" feeder: depends_on: - witness build: context: ../../.. dockerfile: ./internal/witness/cmd/feeder/Dockerfile command: - "--witness_url=http://witness:8100" - "--alsologtostderr" restart: always volumes: data: google-certificate-transparency-go-2308f62/internal/witness/omniwitness/witness_configs/000077500000000000000000000000001462611535200317345ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/omniwitness/witness_configs/witness.yaml000066400000000000000000000071031462611535200343150ustar00rootroot00000000000000Logs: - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETeBmZOrzZKo4xYktx9gI2chEce3cw/tbr5xkoQlmhB18aKfsxD+MnILgGNl0FOm0eYGilFVi85wLRIOhK8lxKw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeIPc6fGmuBg6AJkv/z7NFckmHvf/OqmjchZJ6wm2qN200keRDg352dWpi7CHnSV51BpQYAj1CQY5JuRAwrrDwg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0JCPZFJOQqyEti5M8j13ALN3CAVHqkVM4yyOcKWCu2yye5yYeqDpEXYoALIgtM3TmHtNlifmt+4iatGwLpF3eA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER+1MInu8Q39BwDZ5Rp9TwXhwm3ktvgJzpk/r7dDgGk7ZacMm3ljfcoIvP1E72T8jvyLT1bvdapylajZcTH6W5g== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+WS9FSxAYlCVEzg8xyGwOrmPonoV14nWjjETAIdZvLvukPzIWBMKv6tDNlQjpIHNrUcUt1igRPpqoKDXw2MeKw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEchY+C+/vzj5g3ZXLY3q5qY1Kb2zcYYCmRV4vg6yU84WI0KV00HuO/8XuQqLwLZPjwtCymeLhQunSxgAnaXSuzg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEmyGDvYXsRJsNyXSrYc9DjHsIa2xzb4UR7ZxVoV6mrc9iZB7xjI6+NrOiwH+P/xxkRmOFG6Jel20q37hTh58rA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExpon7ipsqehIeU1bmpog9TFo4Pk8+9oN8OYHl1Q2JGVXnkVFnuuvPgSo2Ep+6vLffNLcmEbxOucz03sFiematg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESLJHTlAycmJKDQxIv60pZG8g33lSYxYpCi5gteI6HLevWbFVCdtZx+m9b+0LrwWWl/87mkNN6xE0M4rnrIPA/w== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/8tkhjLRp0SXrlZdTzNkTd6HqmcmXiDJz3fAdWLgOhjmv4mohvRhwXul9bgW0ODgRwC9UGAgH/vpGHPvIS1qA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCFRkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6J4EbcpIAl1+AkSRsbhoY5oRTj3VoFfaf1DlQkfi7Rbe/HcjfVtrwN8jaC+tQDGjF+dqvKhWJAQ6Q6ev6q9Mew== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfQ0DsdWYitzwFTvG3F4Nbj8Nv5XIVYzQpkyWsU4nuSYlmcwrAp6m092fsdXEw6w1BAeHlzaqrSgNfyvZaJ9y0Q== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9o7AiwrbGBIX6Lnc47I6OfLMdZnRzKoP5u072nBi6vpIOEooktTi1gNwlRPzGC2ySGfuc1xLDeaA/wSFGgpYFg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJyTdaAMoy/5jvg4RR019F2ihEV1McclBKMe2okuX7MCv/C87v+nxsfz1Af+p+0lADGMkmNd5LqZVqxbGvlHYcQ== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXu8iQwSCRSf2CbITGpUpBtFVt8+I0IU0d1C36Lfe1+fbwdaI0Z5FktfM2fBoI1bXBd18k2ggKGYGgdZBgLKTg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8m/SiQ8/xfiHHqtls9m7FyOMBg4JVZY9CgiixXGz0akvKD6DEL8S0ERmFe9U4ZiA0M4kbT5nmuk3I85Sk4bagA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7+R9dC4VFbbpuyOL+yy14ceAmEf7QGlo/EmtYU6DRzwat43f/3swtLr/L8ugFOOt1YU/RFmMjGCL17ixv66MZw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELsYzGMNwo8rBIlaklBIdmD2Ofn6HkfrjK0Ukz1uOIUC6Lm0jTITCXhoIdjs7JkyXnwuwYiJYiH7sE1YeKu8k9w== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhjyxDVIjWt5u9sB/o2S8rcGJ2pdZTGA8+IpXhI/tvKBjElGE5r3de4yAfeOPhqTqqc+o7vPgXnDgu/a9/B+RLg== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsz0OeL7jrVxEXJu+o4QWQYLKyokXHiPOOKVUL3/TNFFquVzDSer7kZ3gijxzBp98ZTgRgMSaWgCmZ8OD74mFUQ== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESdAfC+h1SZNsSARs188n/dCiNYjGSgkT7avLYe1mmXJzzHhsmxmAorHtOzhDkFgaCSCrUPrXdunK946eyIeSmA== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu1LyFs+SC8555lRtwjdTpPX5OqmzBewdvRbsMKwu+HliNRWOGtgWLuRIa/bGE/GWLlwQ/hkeqBi4Dy3DpIZRlw== - PubKey: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpBFS2xdBTpDUVlESMFL4mwPPTJ/4Lji18Vq6+ji50o8agdqVzDPsIShmxlY+YDYhINnUrF36XBmhBX3+ICP89Q== google-certificate-transparency-go-2308f62/internal/witness/verifier/000077500000000000000000000000001462611535200257645ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/internal/witness/verifier/verifier.go000066400000000000000000000043531462611535200301330ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package verifier is designed to verify the signatures produced by a witness. package verifier import ( "crypto" "errors" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/internal/witness/api" "github.com/google/certificate-transparency-go/tls" ) // WitnessVerifier consists of a CT signature verifier. type WitnessVerifier struct { SigVerifier *ct.SignatureVerifier } // NewWitnessVerifier creates a witness signature verifier from a public key. func NewWitnessVerifier(pk crypto.PublicKey) (*WitnessVerifier, error) { sv, err := ct.NewSignatureVerifier(pk) if err != nil { return nil, fmt.Errorf("failed to create signature verifier: %v", err) } return &WitnessVerifier{SigVerifier: sv}, nil } // VerifySignature finds and verifies this witness' signature on a cosigned STH. // This may mean that there are other witness signatures that remain unverified, // so future implementations may want to take in multiple signature verifiers // like in the Note package (https://pkg.go.dev/golang.org/x/mod/sumdb/note). func (wv WitnessVerifier) VerifySignature(sth api.CosignedSTH) error { if len(sth.WitnessSigs) == 0 { return errors.New("no witness signature present in the STH") } sigData, err := tls.Marshal(sth.SignedTreeHead) if err != nil { return fmt.Errorf("failed to marshal internal STH: %v", err) } for _, sig := range sth.WitnessSigs { // If we find a signature that verifies then we're okay. if err := wv.SigVerifier.VerifySignature(sigData, tls.DigitallySigned(sig)); err == nil { return nil } } return errors.New("failed to verify any signature for this witness") } google-certificate-transparency-go-2308f62/jsonclient/000077500000000000000000000000001462611535200230115ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/jsonclient/backoff.go000066400000000000000000000032461462611535200247400ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jsonclient import ( "sync" "time" ) type backoff struct { mu sync.RWMutex multiplier uint notBefore time.Time } const ( // maximum backoff is 2^(maxMultiplier-1) = 128 seconds maxMultiplier = 8 ) func (b *backoff) set(override *time.Duration) time.Duration { b.mu.Lock() defer b.mu.Unlock() if b.notBefore.After(time.Now()) { if override != nil { // If existing backoff is set but override would be longer than // it then set it to that. notBefore := time.Now().Add(*override) if notBefore.After(b.notBefore) { b.notBefore = notBefore } } return time.Until(b.notBefore) } var wait time.Duration if override != nil { wait = *override } else { if b.multiplier < maxMultiplier { b.multiplier++ } wait = time.Second * time.Duration(1<<(b.multiplier-1)) } b.notBefore = time.Now().Add(wait) return wait } func (b *backoff) decreaseMultiplier() { b.mu.Lock() defer b.mu.Unlock() if b.multiplier > 0 { b.multiplier-- } } func (b *backoff) until() time.Time { b.mu.RLock() defer b.mu.RUnlock() return b.notBefore } google-certificate-transparency-go-2308f62/jsonclient/backoff_test.go000066400000000000000000000062221462611535200257740ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jsonclient import ( "math" "testing" "time" ) const testLeeway = 25 * time.Millisecond func fuzzyTimeEquals(a, b time.Time, leeway time.Duration) bool { diff := math.Abs(float64(a.Sub(b).Nanoseconds())) return diff < float64(leeway.Nanoseconds()) } func fuzzyDurationEquals(a, b time.Duration, leeway time.Duration) bool { diff := math.Abs(float64(a.Nanoseconds() - b.Nanoseconds())) return diff < float64(leeway.Nanoseconds()) } func TestBackoff(t *testing.T) { b := backoff{} // Test that the interval increases as expected for i := uint(0); i < maxMultiplier; i++ { n := time.Now() interval := b.set(nil) if interval != time.Second*(1< maxMultiplier { t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, maxMultiplier) } if interval > time.Second*(1<<(maxMultiplier-1)) { t.Fatalf("backoff.set(nil)=%v; want %v", interval, 1<<(maxMultiplier-1)*time.Second) } // Test decreaseMultiplier properly decreases the multiplier b.multiplier = 1 b.notBefore = time.Time{} b.decreaseMultiplier() if b.multiplier != 0 { t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, 0) } // Test decreaseMultiplier doesn't reduce multiplier below 0 b.decreaseMultiplier() if b.multiplier != 0 { t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, 0) } } func TestBackoffOverride(t *testing.T) { b := backoff{} for _, tc := range []struct { notBefore time.Time override time.Duration expectedInterval time.Duration }{ { notBefore: time.Now().Add(time.Hour), override: time.Second * 1800, expectedInterval: time.Hour, }, { notBefore: time.Now().Add(time.Hour), override: time.Second * 7200, expectedInterval: 2 * time.Hour, }, { notBefore: time.Time{}, override: time.Second * 7200, expectedInterval: 2 * time.Hour, }, } { b.multiplier = 0 b.notBefore = tc.notBefore interval := b.set(&tc.override) if !fuzzyDurationEquals(tc.expectedInterval, interval, testLeeway) { t.Fatalf("backoff.set(%v)=%v; want %v", tc.override, interval, tc.expectedInterval) } } } google-certificate-transparency-go-2308f62/jsonclient/client.go000066400000000000000000000262411462611535200246230ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package jsonclient provides a simple client for fetching and parsing // JSON CT structures from a log. package jsonclient import ( "bytes" "context" "crypto" "encoding/json" "errors" "fmt" "io" "log" "math/rand" "net/http" "net/url" "strconv" "strings" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/x509" "golang.org/x/net/context/ctxhttp" "k8s.io/klog/v2" ) const maxJitter = 250 * time.Millisecond type backoffer interface { // set adjusts/increases the current backoff interval (typically on retryable failure); // if the optional parameter is provided, this will be used as the interval if it is greater // than the currently set interval. Returns the current wait period so that it can be // logged along with any error message. set(*time.Duration) time.Duration // decreaseMultiplier reduces the current backoff multiplier, typically on success. decreaseMultiplier() // until returns the time until which the client should wait before making a request, // it may be in the past in which case it should be ignored. until() time.Time } // JSONClient provides common functionality for interacting with a JSON server // that uses cryptographic signatures. type JSONClient struct { uri string // the base URI of the server. e.g. https://ct.googleapis/pilot httpClient *http.Client // used to interact with the server via HTTP Verifier *ct.SignatureVerifier // nil for no verification (e.g. no public key available) logger Logger // interface to use for logging warnings and errors backoff backoffer // object used to store and calculate backoff information userAgent string // If set, this is sent as the UserAgent header. } // Logger is a simple logging interface used to log internal errors and warnings type Logger interface { // Printf formats and logs a message Printf(string, ...interface{}) } // Options are the options for creating a new JSONClient. type Options struct { // Interface to use for logging warnings and errors, if nil the // standard library log package will be used. Logger Logger // PEM format public key to use for signature verification. PublicKey string // DER format public key to use for signature verification. PublicKeyDER []byte // UserAgent, if set, will be sent as the User-Agent header with each request. UserAgent string } // ParsePublicKey parses and returns the public key contained in opts. // If both opts.PublicKey and opts.PublicKeyDER are set, PublicKeyDER is used. // If neither is set, nil will be returned. func (opts *Options) ParsePublicKey() (crypto.PublicKey, error) { if len(opts.PublicKeyDER) > 0 { return x509.ParsePKIXPublicKey(opts.PublicKeyDER) } if opts.PublicKey != "" { pubkey, _ /* keyhash */, rest, err := ct.PublicKeyFromPEM([]byte(opts.PublicKey)) if err != nil { return nil, err } if len(rest) > 0 { return nil, errors.New("extra data found after PEM key decoded") } return pubkey, nil } return nil, nil } type basicLogger struct{} func (bl *basicLogger) Printf(msg string, args ...interface{}) { log.Printf(msg, args...) } // RspError represents an error that occurred when processing a response from a server, // and also includes key details from the http.Response that triggered the error. type RspError struct { Err error StatusCode int Body []byte } // Error formats the RspError instance, focusing on the error. func (e RspError) Error() string { return e.Err.Error() } // New constructs a new JSONClient instance, for the given base URI, using the // given http.Client object (if provided) and the Options object. // If opts does not specify a public key, signatures will not be verified. func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) { pubkey, err := opts.ParsePublicKey() if err != nil { return nil, fmt.Errorf("invalid public key: %v", err) } var verifier *ct.SignatureVerifier if pubkey != nil { var err error verifier, err = ct.NewSignatureVerifier(pubkey) if err != nil { return nil, err } } if hc == nil { hc = new(http.Client) } logger := opts.Logger if logger == nil { logger = &basicLogger{} } return &JSONClient{ uri: strings.TrimRight(uri, "/"), httpClient: hc, Verifier: verifier, logger: logger, backoff: &backoff{}, userAgent: opts.UserAgent, }, nil } // BaseURI returns the base URI that the JSONClient makes queries to. func (c *JSONClient) BaseURI() string { return c.uri } // GetAndParse makes a HTTP GET call to the given path, and attempts to parse // the response as a JSON representation of the rsp structure. Returns the // http.Response, the body of the response, and an error (which may be of // type RspError if the HTTP response was available). func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[string]string, rsp interface{}) (*http.Response, []byte, error) { if ctx == nil { return nil, nil, errors.New("context.Context required") } // Build a GET request with URL-encoded parameters. vals := url.Values{} for k, v := range params { vals.Add(k, v) } fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode()) klog.V(2).Infof("GET %s", fullURI) httpReq, err := http.NewRequest(http.MethodGet, fullURI, nil) if err != nil { return nil, nil, err } if len(c.userAgent) != 0 { httpReq.Header.Set("User-Agent", c.userAgent) } httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq) if err != nil { return nil, nil, err } // Read everything now so http.Client can reuse the connection. body, err := io.ReadAll(httpRsp.Body) if err := httpRsp.Body.Close(); err != nil { return nil, nil, err } if err != nil { return nil, nil, RspError{Err: fmt.Errorf("failed to read response body: %v", err), StatusCode: httpRsp.StatusCode, Body: body} } if httpRsp.StatusCode != http.StatusOK { return nil, nil, RspError{Err: fmt.Errorf("got HTTP Status %q", httpRsp.Status), StatusCode: httpRsp.StatusCode, Body: body} } if err := json.NewDecoder(bytes.NewReader(body)).Decode(rsp); err != nil { return nil, nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } return httpRsp, body, nil } // PostAndParse makes a HTTP POST call to the given path, including the request // parameters, and attempts to parse the response as a JSON representation of // the rsp structure. Returns the http.Response, the body of the response, and // an error (which may be of type RspError if the HTTP response was available). func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) { if ctx == nil { return nil, nil, errors.New("context.Context required") } // Build a POST request with JSON body. postBody, err := json.Marshal(req) if err != nil { return nil, nil, err } fullURI := fmt.Sprintf("%s%s", c.uri, path) klog.V(2).Infof("POST %s", fullURI) httpReq, err := http.NewRequest(http.MethodPost, fullURI, bytes.NewReader(postBody)) if err != nil { return nil, nil, err } if len(c.userAgent) != 0 { httpReq.Header.Set("User-Agent", c.userAgent) } httpReq.Header.Set("Content-Type", "application/json") httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq) // Read all of the body, if there is one, so that the http.Client can do Keep-Alive. var body []byte if httpRsp != nil { body, err = io.ReadAll(httpRsp.Body) if err := httpRsp.Body.Close(); err != nil { return nil, nil, err } } if err != nil { if httpRsp != nil { return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err} } return nil, nil, err } if httpRsp.Request.Method != http.MethodPost { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections return nil, nil, fmt.Errorf("POST request to %q was converted to %s request to %q", fullURI, httpRsp.Request.Method, httpRsp.Request.URL) } if httpRsp.StatusCode == http.StatusOK { if err = json.Unmarshal(body, &rsp); err != nil { return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err} } } return httpRsp, body, nil } // waitForBackoff blocks until the defined backoff interval or context has expired, if the returned // not before time is in the past it returns immediately. func (c *JSONClient) waitForBackoff(ctx context.Context) error { dur := time.Until(c.backoff.until().Add(time.Millisecond * time.Duration(rand.Intn(int(maxJitter.Seconds()*1000))))) if dur < 0 { dur = 0 } backoffTimer := time.NewTimer(dur) select { case <-ctx.Done(): return ctx.Err() case <-backoffTimer.C: } return nil } // PostAndParseWithRetry makes a HTTP POST call, but retries (with backoff) on // retryable errors; the caller should set a deadline on the provided context // to prevent infinite retries. Return values are as for PostAndParse. func (c *JSONClient) PostAndParseWithRetry(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) { if ctx == nil { return nil, nil, errors.New("context.Context required") } for { httpRsp, body, err := c.PostAndParse(ctx, path, req, rsp) if err != nil { // Don't retry context errors. if err == context.Canceled || err == context.DeadlineExceeded { return nil, nil, err } wait := c.backoff.set(nil) c.logger.Printf("Request to %s failed, backing-off %s: %s", c.uri, wait, err) } else { switch { case httpRsp.StatusCode == http.StatusOK: return httpRsp, body, nil case httpRsp.StatusCode == http.StatusRequestTimeout: // Request timeout, retry immediately c.logger.Printf("Request to %s timed out, retrying immediately", c.uri) case httpRsp.StatusCode == http.StatusServiceUnavailable: fallthrough case httpRsp.StatusCode == http.StatusTooManyRequests: var backoff *time.Duration // Retry-After may be either a number of seconds as a int or a RFC 1123 // date string (RFC 7231 Section 7.1.3) if retryAfter := httpRsp.Header.Get("Retry-After"); retryAfter != "" { if seconds, err := strconv.Atoi(retryAfter); err == nil { b := time.Duration(seconds) * time.Second backoff = &b } else if date, err := time.Parse(time.RFC1123, retryAfter); err == nil { b := time.Until(date) backoff = &b } } wait := c.backoff.set(backoff) c.logger.Printf("Request to %s failed, backing-off for %s: got HTTP status %s", c.uri, wait, httpRsp.Status) default: return nil, nil, RspError{ StatusCode: httpRsp.StatusCode, Body: body, Err: fmt.Errorf("got HTTP status %q", httpRsp.Status)} } } if err := c.waitForBackoff(ctx); err != nil { return nil, nil, err } } } google-certificate-transparency-go-2308f62/jsonclient/client_test.go000066400000000000000000000360411462611535200256610ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jsonclient import ( "context" "encoding/json" "encoding/pem" "fmt" "net/http" "net/http/httptest" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/google/certificate-transparency-go/testdata" ) func publicKeyPEMToDER(key string) []byte { block, _ := pem.Decode([]byte(key)) if block == nil { panic("failed to decode public key PEM") } if block.Type != "PUBLIC KEY" { panic("PEM does not have type 'PUBLIC KEY'") } return block.Bytes } func TestNewJSONClient(t *testing.T) { tests := []struct { name string opts Options wantErr string }{ { name: "invalid PublicKey", opts: Options{PublicKey: "bogus"}, wantErr: "no PEM block", }, { name: "invalid PublicKeyDER", opts: Options{PublicKeyDER: []byte("bogus")}, wantErr: "asn1: structure error", }, { name: "RSA PublicKey", opts: Options{PublicKey: testdata.RsaPublicKeyPEM}, }, { name: "RSA PublicKeyDER", opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.RsaPublicKeyPEM)}, }, { name: "ECDSA PublicKey", opts: Options{PublicKey: testdata.EcdsaPublicKeyPEM}, }, { name: "ECDSA PublicKeyDER", opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.EcdsaPublicKeyPEM)}, }, { name: "DSA PublicKey", opts: Options{PublicKey: testdata.DsaPublicKeyPEM}, wantErr: "unsupported public key type", }, { name: "DSA PublicKeyDER", opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.DsaPublicKeyPEM)}, wantErr: "unsupported public key type", }, { name: "PublicKey contains trailing garbage", opts: Options{PublicKey: testdata.RsaPublicKeyPEM + "bogus"}, wantErr: "extra data found", }, { name: "PublicKeyDER contains trailing garbage", opts: Options{PublicKeyDER: append(publicKeyPEMToDER(testdata.RsaPublicKeyPEM), []byte("deadbeef")...)}, wantErr: "trailing data", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := New("http://127.0.0.1", nil, test.opts) if err != nil { if len(test.wantErr) == 0 { t.Errorf("New()=nil,%v; want _,nil", err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("New()=nil,%v; want _, error containing %q", err, test.wantErr) } return } if len(test.wantErr) > 0 { t.Errorf("New()=_,nil; want nil, error containing %q", test.wantErr) } if got == nil { t.Errorf("New()=nil,nil; want non-nil,nil") } }) } } type TestStruct struct { TreeSize int `json:"tree_size"` Timestamp int `json:"timestamp"` Data string `json:"data"` } type TestParams struct { RespCode int `json:"rc"` } func MockServer(t *testing.T, failCount int, retryAfter int) *httptest.Server { t.Helper() mu := sync.Mutex{} return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() switch r.URL.Path { case "/struct/path": fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`) case "/struct/params": var s TestStruct if r.Method == http.MethodGet { s.TreeSize, _ = strconv.Atoi(r.FormValue("tree_size")) s.Timestamp, _ = strconv.Atoi(r.FormValue("timestamp")) s.Data = r.FormValue("data") } else { decoder := json.NewDecoder(r.Body) err := decoder.Decode(&s) if err != nil { t.Fatalf("Failed to decode: " + err.Error()) } defer func() { if err := r.Body.Close(); err != nil { t.Fatalf("Failed to close request body: " + err.Error()) } }() } fmt.Fprintf(w, `{"tree_size": %d, "timestamp": %d, "data": "%s"}`, s.TreeSize, s.Timestamp, s.Data) case "/error": var params TestParams if r.Method == http.MethodGet { params.RespCode, _ = strconv.Atoi(r.FormValue("rc")) } else { decoder := json.NewDecoder(r.Body) err := decoder.Decode(¶ms) if err != nil { t.Fatalf("Failed to decode: " + err.Error()) } defer func() { if err := r.Body.Close(); err != nil { t.Fatalf("Failed to close request body: " + err.Error()) } }() } http.Error(w, "error page", params.RespCode) case "/malformed": fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99`) // no closing } case "/retry": if failCount > 0 { failCount-- if retryAfter != 0 { if retryAfter > 0 { w.Header().Add("Retry-After", strconv.Itoa(retryAfter)) } w.WriteHeader(http.StatusServiceUnavailable) } else { w.WriteHeader(http.StatusRequestTimeout) } } else { fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`) } case "/retry-rfc1123": if failCount > 0 { failCount-- w.Header().Add("Retry-After", time.Now().Add(time.Duration(retryAfter)*time.Second).Format(time.RFC1123)) w.WriteHeader(http.StatusServiceUnavailable) } else { fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`) } case "/useragent/banana": if got, want := r.Header.Get("User-Agent"), "banana"; got != want { w.WriteHeader(400) } fmt.Fprintf(w, `{}`) case "/useragent/none": if got, want := r.Header.Get("User-Agent"), ""; got != want { w.WriteHeader(400) } fmt.Fprintf(w, `{}`) default: t.Fatalf("Unhandled URL path: %s", r.URL.Path) } })) } func TestGetAndParse(t *testing.T) { tests := []struct { uri string params map[string]string wantStatus int want TestStruct wantErr string ua string }{ {uri: "/short%", wantErr: "invalid URL escape"}, {uri: "/malformed", wantStatus: http.StatusOK, wantErr: "unexpected EOF"}, {uri: "/error", params: map[string]string{"rc": "404"}, wantErr: "404 Not Found"}, {uri: "/error", params: map[string]string{"rc": "403"}, wantErr: "403 Forbidden"}, {uri: "/struct/path", wantStatus: http.StatusOK, want: TestStruct{11, 99, ""}}, {uri: "/useragent/banana", wantStatus: http.StatusOK, ua: "banana"}, {uri: "/useragent/banana", wantErr: "400 Bad Request", ua: "not-a-banana"}, { uri: "/struct/params", params: map[string]string{"tree_size": "42", "timestamp": "88", "data": "abcd"}, wantStatus: http.StatusOK, want: TestStruct{42, 88, "abcd"}, }, } ts := MockServer(t, -1, 0) defer ts.Close() ctx := context.Background() for _, test := range tests { logClient, err := New(ts.URL, &http.Client{}, Options{UserAgent: test.ua}) if err != nil { t.Fatal(err) } var got TestStruct httpRsp, body, err := logClient.GetAndParse(ctx, test.uri, test.params, &got) var gotStatus int if httpRsp != nil { gotStatus = httpRsp.StatusCode } else if rspErr, ok := err.(RspError); ok { gotStatus = rspErr.StatusCode } if err != nil { if len(test.wantErr) == 0 { t.Errorf("GetAndParse(%q)=_,_,%q; want _, _, nil", test.uri, err.Error()) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("GetAndParse(%q)=_,_,%q; want _, _, error containing %q", test.uri, err.Error(), test.wantErr) } continue } if len(test.wantErr) > 0 { t.Errorf("GetAndParse(%q)=%+v,_,nil; want error matching %q", test.uri, got, test.wantErr) } if gotStatus != test.wantStatus { t.Errorf("GetAndParse('%s') got status %d; want %d", test.uri, gotStatus, test.wantStatus) } if body == nil { t.Errorf("GetAndParse(%q)=_,nil,_; want _,non-nil,_", test.uri) } if test.wantStatus == http.StatusOK { if !reflect.DeepEqual(got, test.want) { t.Errorf("GetAndParse(%q)=%+v,_,nil; want %+v", test.uri, got, test.want) } } } } func TestPostAndParse(t *testing.T) { tests := []struct { uri string request interface{} wantStatus int want TestStruct wantErr string ua string }{ {uri: "/short%", wantErr: "invalid URL escape"}, {uri: "/struct/params", request: json.Number(`invalid`), wantErr: "invalid number literal"}, {uri: "/malformed", wantStatus: http.StatusOK, wantErr: "unexpected end of JSON"}, {uri: "/error", request: TestParams{RespCode: 404}, wantStatus: http.StatusNotFound}, {uri: "/error", request: TestParams{RespCode: 403}, wantStatus: http.StatusForbidden}, {uri: "/struct/path", wantStatus: http.StatusOK, want: TestStruct{11, 99, ""}}, {uri: "/useragent/banana", wantStatus: http.StatusOK, ua: "banana"}, {uri: "/useragent/banana", wantStatus: 400, ua: "not-a-banana"}, { uri: "/struct/params", wantStatus: http.StatusOK, request: TestStruct{42, 88, "abcd"}, want: TestStruct{42, 88, "abcd"}, }, } ts := MockServer(t, -1, 0) defer ts.Close() ctx := context.Background() for _, test := range tests { logClient, err := New(ts.URL, &http.Client{}, Options{UserAgent: test.ua}) if err != nil { t.Fatal(err) } var got TestStruct httpRsp, body, err := logClient.PostAndParse(ctx, test.uri, test.request, &got) var gotStatus int if httpRsp != nil { gotStatus = httpRsp.StatusCode } else if rspErr, ok := err.(RspError); ok { gotStatus = rspErr.StatusCode } if err != nil { if len(test.wantErr) == 0 { t.Errorf("PostAndParse(%q)=_,_,%q; want _, _, nil", test.uri, err.Error()) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("PostAndParse(%q)=nil,%q; want error matching %q", test.uri, err.Error(), test.wantErr) } continue } if len(test.wantErr) > 0 { t.Errorf("PostAndParse(%q)=%+v,nil; want error matching %q", test.uri, got, test.wantErr) } if gotStatus != test.wantStatus { t.Errorf("PostAndParse('%s') got status %d; want %d", test.uri, gotStatus, test.wantStatus) } if body == nil { t.Errorf("PostAndParse(%q)=_,nil,_; want _,non-nil,_ ", test.uri) } if test.wantStatus == http.StatusOK { if !reflect.DeepEqual(got, test.want) { t.Errorf("PostAndParse(%q)=%+v,nil; want %+v", test.uri, got, test.want) } } } } // mockBackoff is not safe for concurrent usage type mockBackoff struct { override time.Duration } func (mb *mockBackoff) set(o *time.Duration) time.Duration { if o != nil { mb.override = *o } return 0 } func (mb *mockBackoff) decreaseMultiplier() {} func (mb *mockBackoff) until() time.Time { return time.Time{} } func TestPostAndParseWithRetry(t *testing.T) { tests := []struct { uri string request interface{} deadlineSecs int // -1 indicates no deadline retryAfter int // -1 indicates generate 503 with no Retry-After failCount int wantErr string expectedBackoff time.Duration // 0 indicates no expected backoff override set }{ { uri: "/error", request: TestParams{RespCode: 418}, deadlineSecs: -1, retryAfter: 0, failCount: 0, wantErr: "teapot", expectedBackoff: 0, }, { uri: "/short%", request: nil, deadlineSecs: 0, retryAfter: 0, failCount: 0, wantErr: "deadline exceeded", expectedBackoff: 0, }, { uri: "/retry", request: nil, deadlineSecs: -1, retryAfter: 0, failCount: 1, wantErr: "", expectedBackoff: 0, }, { uri: "/retry", request: nil, deadlineSecs: -1, retryAfter: 5, failCount: 1, wantErr: "", expectedBackoff: 5 * time.Second, }, { uri: "/retry-rfc1123", request: nil, deadlineSecs: -1, retryAfter: 5, failCount: 1, wantErr: "", expectedBackoff: 5 * time.Second, }, } for _, test := range tests { t.Run(test.uri, func(t *testing.T) { ts := MockServer(t, test.failCount, test.retryAfter) defer ts.Close() logClient, err := New(ts.URL, &http.Client{}, Options{}) if err != nil { t.Fatal(err) } mb := mockBackoff{} logClient.backoff = &mb ctx := context.Background() if test.deadlineSecs >= 0 { var cancel context.CancelFunc ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Duration(test.deadlineSecs)*time.Second)) defer cancel() } var got TestStruct httpRsp, _, err := logClient.PostAndParseWithRetry(ctx, test.uri, test.request, &got) if test.wantErr != "" { if err == nil { t.Errorf("PostAndParseWithRetry()=%+v,nil; want error %q", got, test.wantErr) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("PostAndParseWithRetry()=nil,%q; want error %q", err.Error(), test.wantErr) } else if _, isRspError := err.(RspError); !isRspError && err != context.DeadlineExceeded { // We expect all non-nil errors to be either a RspError instance or to // be the context DeadlineExceeded error. t.Errorf("PostAndParseWithRetry()=%T; want jsonClient.RspError or context.DeadlineExceeded", err) } return } if err != nil { t.Errorf("PostAndParseWithRetry()=nil,%q; want no error", err.Error()) } else if httpRsp.StatusCode != http.StatusOK { t.Errorf("PostAndParseWithRetry() got status %d; want OK(404)", httpRsp.StatusCode) } if test.expectedBackoff > 0 && !fuzzyDurationEquals(test.expectedBackoff, mb.override, time.Second) { t.Errorf("Unexpected backoff override set: got: %s, wanted: %s", mb.override, test.expectedBackoff) } }) } } // nolint:staticcheck func TestContextRequired(t *testing.T) { ts := MockServer(t, -1, 0) defer ts.Close() logClient, err := New(ts.URL, &http.Client{}, Options{}) if err != nil { t.Fatal(err) } var result TestStruct _, _, err = logClient.GetAndParse(nil, "/struct/path", nil, &result) if err == nil { t.Errorf("GetAndParse() succeeded with empty Context") } _, _, err = logClient.PostAndParse(nil, "/struct/path", nil, &result) if err == nil { t.Errorf("PostAndParse() succeeded with empty Context") } _, _, err = logClient.PostAndParseWithRetry(nil, "/struct/path", nil, &result) if err == nil { t.Errorf("PostAndParseWithRetry() succeeded with empty Context") } } func TestCancelledContext(t *testing.T) { ts := MockServer(t, -1, 0) defer ts.Close() logClient, err := New(ts.URL, &http.Client{}, Options{}) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) cancel() var result TestStruct _, _, err = logClient.GetAndParse(ctx, "/struct/path", nil, &result) if err != context.Canceled { t.Errorf("GetAndParse() = (_,_,%v), want %q", err, context.Canceled) } _, _, err = logClient.PostAndParse(ctx, "/struct/path", nil, &result) if err != context.Canceled { t.Errorf("PostAndParse() = (_,_,%v), want %q", err, context.Canceled) } _, _, err = logClient.PostAndParseWithRetry(ctx, "/struct/path", nil, &result) if err != context.Canceled { t.Errorf("PostAndParseWithRetry() = (_,_,%v), want %q", err, context.Canceled) } } google-certificate-transparency-go-2308f62/logid/000077500000000000000000000000001462611535200217375ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/logid/logid.go000066400000000000000000000057351462611535200233760ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package logid provides a type and accompanying helpers for manipulating log IDs. package logid import ( "crypto/sha256" "encoding/base64" "fmt" "log" ) // LogID is a unique identifier for a CT Log derived from its public key as described by RFC6962 // sect. 3.2. Since it is derived from a SHA-256 hash, its length is fixed at 32 bytes. type LogID [sha256.Size]byte // FromBytes returns a LogID copied from the supplied byte slice. func FromBytes(bytes []byte) (LogID, error) { var logID LogID if len(bytes) != sha256.Size { return logID, fmt.Errorf("FromBytes(%x): want %d bytes, got %d", bytes, sha256.Size, len(bytes)) } copy(logID[:], bytes) return logID, nil } // FromB64 returns a LogID from parsing the supplied base64-encoded Log ID. func FromB64(logIDB64 string) (LogID, error) { buf, err := base64.StdEncoding.DecodeString(logIDB64) if err != nil { return LogID{}, err } return FromBytes(buf[:]) } // FromB64OrDie returns a LogID from parsing supplied base64-encoded data that we assert is // already well-formed, so it 'cannot fail'. func FromB64OrDie(logIDB64 string) LogID { logID, err := FromB64(logIDB64) if err != nil { log.Fatalf("FromB64(%q): %v", logIDB64, err) } return logID } // FromPubKeyB64 takes a base64 encoded DER public key, and converts it into // a LogID, as defined in RFC6962 - i.e. the SHA-256 hash of the base64 decoded // bytes of the log's public key. func FromPubKeyB64(pubKeyB64 string) (LogID, error) { pkBytes, err := base64.StdEncoding.DecodeString(pubKeyB64) if err != nil { return LogID{}, fmt.Errorf("error decoding public key %q from base64: %s", pubKeyB64, err) } return LogID(sha256.Sum256(pkBytes)), nil } // FromPubKeyB64OrDie takes a base64 encoded DER public key, and converts it // into a LogID, as defined in RFC6962 - i.e. the sha256 hash of the base64 // decoded bytes of the log's public key. This is for data that we assert is // already well-formed, so it 'cannot fail'. func FromPubKeyB64OrDie(pubKeyB64 string) LogID { logID, err := FromPubKeyB64(pubKeyB64) if err != nil { log.Fatalf("FromPubKeyB64(%q): %v", pubKeyB64, err) } return logID } // Bytes returns the raw bytes of the LogID, as a slice. func (l LogID) Bytes() []byte { return l[:] } // String base64-encodes a LogID for ease of debugging. func (l LogID) String() string { return fmt.Sprintf("logid:[%s]", base64.StdEncoding.EncodeToString(l.Bytes())) } google-certificate-transparency-go-2308f62/logid/logid_test.go000066400000000000000000000077401462611535200244330ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package logid import ( "crypto/sha256" "encoding/base64" "log" "reflect" "testing" ) var ( wanted LogID = [32]byte{ 'c', 't', 'l', 'o', 'g', '_', 'i', 'd', '_', 'f', 'o', 'r', '_', 't', 'e', 's', 't', 'i', 'n', 'g', '_', 'p', 'u', 'r', 'p', 'o', 's', 'e', 's', '!', '!', '1', } ) func toBytes(s string, l int) []byte { bs := []byte(s) if len(bs) != l { log.Fatalf("string %q is %d bytes long, wanted length %d", s, len(bs), l) } return bs } func TestFromBytes(t *testing.T) { tests := []struct { in string size int want *LogID wantErr bool }{ {"ctlog_id_for_testing_purposes!!1", sha256.Size, &wanted, false}, // "invalid CT LogID - expected 32 bytes but got 16" {"ctlog_id_for_tes", 16, nil, true}, // "invalid CT LogID - expected 32 bytes but got 37" {"ctlog_id_for_testing_purposes!!1extra", 37, nil, true}, } for _, test := range tests { got, err := FromBytes(toBytes(test.in, test.size)) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("FromBytes(%q): got err? %t, want? %t (err=%v)", test.in, gotErr, test.wantErr, err) } if err == nil && !reflect.DeepEqual(&got, test.want) { t.Errorf("FromBytes(%q): got %v, wanted %v", test.in, got, test.want) } } } func TestFromB64(t *testing.T) { tests := []struct { in string want *LogID wantErr bool }{ {"Y3Rsb2dfaWRfZm9yX3Rlc3RpbmdfcHVycG9zZXMhITE=", &wanted, false}, // "illegal base64 data at input byte 4" {"garbage", nil, true}, // "invalid CT LogID - expected 32 bytes but got 1" {"ab==", nil, true}, // "invalid CT LogID - expected 32 bytes but got 36" {"this+input+decodes+to+36+bytes+which+is+too+long", nil, true}, } for _, test := range tests { got, err := FromB64(test.in) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("FromB64(%q): got err? %t, want? %t (err=%v)", test.in, gotErr, test.wantErr, err) } if err == nil && !reflect.DeepEqual(&got, test.want) { t.Errorf("FromB64(%q): got %v, wanted %v", test.in, got, test.want) } } } func TestFromPubKeyB64(t *testing.T) { tests := []struct { desc string b64 string want string wantErr bool }{ { desc: "not base64", b64: "Not valid b64", want: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", wantErr: true, }, { desc: "ECDSA", b64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==", want: "pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA=", wantErr: false, }, { desc: "RSA", b64: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAolpIHxdSlTXLo1s6H1OCdpSj/4DyHDc8wLG9wVmLqy1lk9fz4ATVmm+/1iN2Nk8jmctUKK2MFUtlWXZBSpym97M7frGlSaQXUWyA3CqQUEuIJOmlEjKTBEiQAvpfDjCHjlV2Be4qTM6jamkJbiWtgnYPhJL6ONaGTiSPm7Byy57iaz/hbckldSOIoRhYBiMzeNoA0DiRZ9KmfSeXZ1rB8y8X5urSW+iBzf2SaOfzBvDpcoTuAaWx2DPazoOl28fP1hZ+kHUYvxbcMjttjauCFx+JII0dmuZNIwjfeG/GBb9frpSX219k1O4Wi6OEbHEr8at/XQ0y7gTikOxBn/s5wQIDAQAB", want: "rDua7X+pZ0dXFZ5tfVdWcvnZgQCUHpve/+yhMTt1eC0=", wantErr: false, }, } for _, test := range tests { logID, err := FromPubKeyB64(test.b64) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("%s: got? %t want? %t (err=%v)", test.desc, gotErr, test.wantErr, err) continue } if got := base64.StdEncoding.EncodeToString(logID.Bytes()); got != test.want { t.Errorf("%s: got? %s want? %s", test.desc, got, test.want) } } } google-certificate-transparency-go-2308f62/loglist3/000077500000000000000000000000001462611535200224015ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/loglist3/logfilter.go000066400000000000000000000102111462611535200247120ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loglist3 import ( "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) // LogRoots maps Log-URLs (stated at LogList) to the pools of their accepted // root-certificates. type LogRoots map[string]*x509util.PEMCertPool // Compatible creates a new LogList containing only Logs matching the temporal, // root-acceptance and Log-status conditions. func (ll *LogList) Compatible(cert *x509.Certificate, certRoot *x509.Certificate, roots LogRoots) LogList { active := ll.TemporallyCompatible(cert) // Do not check root compatbility if roots are not being provided. if certRoot == nil { return active } return active.RootCompatible(certRoot, roots) } // SelectByStatus creates a new LogList containing only logs with status // provided from the original. func (ll *LogList) SelectByStatus(lstats []LogStatus) LogList { var active LogList for _, op := range ll.Operators { activeOp := *op activeOp.Logs = []*Log{} for _, l := range op.Logs { for _, lstat := range lstats { if l.State.LogStatus() == lstat { activeOp.Logs = append(activeOp.Logs, l) break } } } if len(activeOp.Logs) > 0 { active.Operators = append(active.Operators, &activeOp) } } return active } // RootCompatible creates a new LogList containing only the logs of original // LogList that are compatible with the provided cert, according to // the passed in collection of per-log roots. Logs that are missing from // the collection are treated as always compatible and included, even if // an empty cert root is passed in. // Cert-root when provided is expected to be CA-cert. func (ll *LogList) RootCompatible(certRoot *x509.Certificate, roots LogRoots) LogList { var compatible LogList // Check whether root is a CA-cert. if certRoot != nil && !certRoot.IsCA { klog.Warningf("Compatible method expects fully rooted chain, while last cert of the chain provided is not root") return compatible } for _, op := range ll.Operators { compatibleOp := *op compatibleOp.Logs = []*Log{} for _, l := range op.Logs { // If root set is not defined, we treat Log as compatible assuming no // knowledge of its roots. if _, ok := roots[l.URL]; !ok { compatibleOp.Logs = append(compatibleOp.Logs, l) continue } if certRoot == nil { continue } // Check root is accepted. if roots[l.URL].Included(certRoot) { compatibleOp.Logs = append(compatibleOp.Logs, l) } } if len(compatibleOp.Logs) > 0 { compatible.Operators = append(compatible.Operators, &compatibleOp) } } return compatible } // TemporallyCompatible creates a new LogList containing only the logs of // original LogList that are compatible with the provided cert, according to // NotAfter and TemporalInterval matching. // Returns empty LogList if nil-cert is provided. func (ll *LogList) TemporallyCompatible(cert *x509.Certificate) LogList { var compatible LogList if cert == nil { return compatible } for _, op := range ll.Operators { compatibleOp := *op compatibleOp.Logs = []*Log{} for _, l := range op.Logs { if l.TemporalInterval == nil { compatibleOp.Logs = append(compatibleOp.Logs, l) continue } if cert.NotAfter.Before(l.TemporalInterval.EndExclusive) && (cert.NotAfter.After(l.TemporalInterval.StartInclusive) || cert.NotAfter.Equal(l.TemporalInterval.StartInclusive)) { compatibleOp.Logs = append(compatibleOp.Logs, l) } } if len(compatibleOp.Logs) > 0 { compatible.Operators = append(compatible.Operators, &compatibleOp) } } return compatible } google-certificate-transparency-go-2308f62/loglist3/logfilter_test.go000066400000000000000000000216731462611535200257670ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loglist3 import ( "log" "testing" "time" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/kylelemons/godebug/pretty" ) func subLogList(logURLs map[string]bool) LogList { var ll LogList for _, op := range sampleLogList.Operators { opCopy := *op opCopy.Logs = []*Log{} for _, l := range op.Logs { if logURLs[l.URL] { opCopy.Logs = append(opCopy.Logs, l) } } if len(opCopy.Logs) > 0 { ll.Operators = append(ll.Operators, &opCopy) } } return ll } func TestSelectUsable(t *testing.T) { tests := []struct { name string in LogList want LogList }{ { name: "Sample", in: sampleLogList, want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { usableStat := make([]LogStatus, 1) usableStat[0] = UsableLogStatus got := test.in.SelectByStatus(usableStat) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("Extracting active logs out of %v diff: (-want +got)\n%s", test.in, diff) } }) } } func TestSelectPendingAndQualified(t *testing.T) { tests := []struct { name string in LogList want LogList }{ { name: "Sample", in: sampleLogList, want: subLogList(map[string]bool{"https://ct.googleapis.com/logs/argon2020/": true}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { stats := make([]LogStatus, 2) stats[0] = PendingLogStatus stats[1] = QualifiedLogStatus got := test.in.SelectByStatus(stats) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("Extracting qualified logs out of %v diff: (-want +got)\n%s", test.in, diff) } }) } } func artificialRoots(source string) LogRoots { roots := LogRoots{ "https://log.bob.io": x509util.NewPEMCertPool(), "https://ct.googleapis.com/racketeer/": x509util.NewPEMCertPool(), "https://ct.googleapis.com/rocketeer/": x509util.NewPEMCertPool(), "https://ct.googleapis.com/aviator/": x509util.NewPEMCertPool(), "https://ct.googleapis.com/logs/argon2020/": x509util.NewPEMCertPool(), } roots["https://log.bob.io"].AppendCertsFromPEM([]byte(source)) return roots } func TestRootCompatible(t *testing.T) { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM)) caCert, _ := x509util.CertificateFromPEM([]byte(testdata.CACertPEM)) tests := []struct { name string in LogList cert *x509.Certificate rootCert *x509.Certificate roots LogRoots want LogList }{ { name: "RootedChain", in: sampleLogList, cert: cert, rootCert: caCert, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{"https://log.bob.io": true, "https://ct.googleapis.com/icarus/": true}), // icarus has no root info. }, { name: "RootedChainNoRootAccepted", in: sampleLogList, cert: cert, rootCert: caCert, roots: artificialRoots(testdata.TestPreCertPEM), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true}), // icarus has no root info. }, { name: "Non-RootedChain", in: sampleLogList, cert: cert, rootCert: cert, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{}), }, { name: "EmptyChain", in: sampleLogList, cert: nil, rootCert: nil, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := test.in.RootCompatible(test.rootCert, test.roots) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("Getting compatible logs diff: (-want +got)\n%s", diff) } }) } } // stripErr is helper func for wrapping time.Parse func stripErr(t time.Time, err error) time.Time { if err != nil { log.Fatal(err) } return t } func TestTemporallyCompatible(t *testing.T) { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM)) tests := []struct { name string in LogList cert *x509.Certificate notAfter time.Time want LogList }{ { name: "AllLogsFitTemporally", in: sampleLogList, cert: cert, notAfter: stripErr(time.Parse(time.UnixDate, "Sat Nov 8 11:06:00 PST 2014")), want: subLogList(map[string]bool{"https://ct.googleapis.com/aviator/": true, "https://log.bob.io": true, "https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/racketeer/": true, "https://ct.googleapis.com/rocketeer/": true}), }, { name: "OperatorExcludedAllItsLogsMismatch", in: sampleLogList, cert: cert, notAfter: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2014")), want: subLogList(map[string]bool{"https://ct.googleapis.com/aviator/": true, "https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/racketeer/": true, "https://ct.googleapis.com/rocketeer/": true}), }, { name: "TwoLogsAfterCertTimeExcluded", in: sampleLogList, cert: cert, notAfter: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2013")), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/racketeer/": true, "https://ct.googleapis.com/rocketeer/": true}), }, { name: "TwoLogsBeforeCertTimeExcluded", in: sampleLogList, cert: cert, notAfter: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2016")), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true, "https://ct.googleapis.com/racketeer/": true, "https://ct.googleapis.com/rocketeer/": true}), }, { name: "NilCert", in: sampleLogList, cert: nil, notAfter: stripErr(time.Parse(time.UnixDate, "Sat Nov 8 11:06:00 PST 2014")), want: subLogList(map[string]bool{}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.cert != nil { test.cert.NotAfter = test.notAfter } got := test.in.TemporallyCompatible(test.cert) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("Getting NotBefore-compatible logs diff: (-want +got)\n%s", diff) } }) } } func TestCompatible(t *testing.T) { cert, _ := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM)) caCert, _ := x509util.CertificateFromPEM([]byte(testdata.CACertPEM)) tests := []struct { name string in LogList notBefore time.Time cert *x509.Certificate rootCert *x509.Certificate roots LogRoots want LogList }{ { name: "RootedChain", in: sampleLogList, notBefore: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2016")), cert: cert, rootCert: caCert, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true}), // icarus has no root info. }, { name: "RootedChainNoRootAccepted", in: sampleLogList, notBefore: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2016")), cert: cert, rootCert: caCert, roots: artificialRoots(testdata.TestPreCertPEM), want: subLogList(map[string]bool{"https://ct.googleapis.com/icarus/": true}), // icarus has no root info. }, { name: "Non-RootedChain", in: sampleLogList, notBefore: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2016")), cert: cert, rootCert: cert, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{}), }, { name: "EmptyChain", in: sampleLogList, notBefore: stripErr(time.Parse(time.UnixDate, "Sat Mar 8 11:06:00 PST 2016")), cert: nil, rootCert: nil, roots: artificialRoots(testdata.CACertPEM), want: subLogList(map[string]bool{}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.cert != nil { test.cert.NotBefore = test.notBefore } got := test.in.Compatible(test.cert, test.rootCert, test.roots) if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("Getting compatible logs diff: (-want +got)\n%s", diff) } }) } } google-certificate-transparency-go-2308f62/loglist3/loglist3.go000066400000000000000000000275731462611535200245060ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package loglist3 allows parsing and searching of the master CT Log list. // It expects the log list to conform to the v3 schema. package loglist3 import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "regexp" "strings" "time" "unicode" "github.com/google/certificate-transparency-go/tls" ) const ( // LogListURL has the master URL for Google Chrome's log list. LogListURL = "https://www.gstatic.com/ct/log_list/v3/log_list.json" // LogListSignatureURL has the URL for the signature over Google Chrome's log list. LogListSignatureURL = "https://www.gstatic.com/ct/log_list/v3/log_list.sig" // AllLogListURL has the URL for the list of all known logs (which isn't signed). AllLogListURL = "https://www.gstatic.com/ct/log_list/v3/all_logs_list.json" ) // Manually mapped from https://www.gstatic.com/ct/log_list/v3/log_list_schema.json // LogList holds a collection of CT logs, grouped by operator. type LogList struct { // IsAllLogs is set to true if the list contains all known logs, not // only usable ones. IsAllLogs bool `json:"is_all_logs,omitempty"` // Version is the version of the log list. Version string `json:"version,omitempty"` // LogListTimestamp is the time at which the log list was published. LogListTimestamp time.Time `json:"log_list_timestamp,omitempty"` // Operators is a list of CT log operators and the logs they operate. Operators []*Operator `json:"operators"` } // Operator holds a collection of CT logs run by the same organisation. // It also provides information about that organisation, e.g. contact details. type Operator struct { // Name is the name of the CT log operator. Name string `json:"name"` // Email lists the email addresses that can be used to contact this log // operator. Email []string `json:"email"` // Logs is a list of CT logs run by this operator. Logs []*Log `json:"logs"` } // Log describes a single CT log. type Log struct { // Description is a human-readable string that describes the log. Description string `json:"description,omitempty"` // LogID is the SHA-256 hash of the log's public key. LogID []byte `json:"log_id"` // Key is the public key with which signatures can be verified. Key []byte `json:"key"` // URL is the address of the HTTPS API. URL string `json:"url"` // DNS is the address of the DNS API. DNS string `json:"dns,omitempty"` // MMD is the Maximum Merge Delay, in seconds. All submitted // certificates must be incorporated into the log within this time. MMD int32 `json:"mmd"` // PreviousOperators is a list of previous operators and the timestamp // of when they stopped running the log. PreviousOperators []*PreviousOperator `json:"previous_operators,omitempty"` // State is the current state of the log, from the perspective of the // log list distributor. State *LogStates `json:"state,omitempty"` // TemporalInterval, if set, indicates that this log only accepts // certificates with a NotAfter date in this time range. TemporalInterval *TemporalInterval `json:"temporal_interval,omitempty"` // Type indicates the purpose of this log, e.g. "test" or "prod". Type string `json:"log_type,omitempty"` } // PreviousOperator holds information about a log operator and the time at which // they stopped running a log. type PreviousOperator struct { // Name is the name of the CT log operator. Name string `json:"name"` // EndTime is the time at which the operator stopped running a log. EndTime time.Time `json:"end_time"` } // TemporalInterval is a time range. type TemporalInterval struct { // StartInclusive is the beginning of the time range. StartInclusive time.Time `json:"start_inclusive"` // EndExclusive is just after the end of the time range. EndExclusive time.Time `json:"end_exclusive"` } // LogStatus indicates Log status. type LogStatus int // LogStatus values const ( UndefinedLogStatus LogStatus = iota PendingLogStatus QualifiedLogStatus UsableLogStatus ReadOnlyLogStatus RetiredLogStatus RejectedLogStatus ) //go:generate stringer -type=LogStatus // LogStates are the states that a CT log can be in, from the perspective of a // user agent. Only one should be set - this is the current state. type LogStates struct { // Pending indicates that the log is in the "pending" state. Pending *LogState `json:"pending,omitempty"` // Qualified indicates that the log is in the "qualified" state. Qualified *LogState `json:"qualified,omitempty"` // Usable indicates that the log is in the "usable" state. Usable *LogState `json:"usable,omitempty"` // ReadOnly indicates that the log is in the "readonly" state. ReadOnly *ReadOnlyLogState `json:"readonly,omitempty"` // Retired indicates that the log is in the "retired" state. Retired *LogState `json:"retired,omitempty"` // Rejected indicates that the log is in the "rejected" state. Rejected *LogState `json:"rejected,omitempty"` } // LogState contains details on the current state of a CT log. type LogState struct { // Timestamp is the time when the state began. Timestamp time.Time `json:"timestamp"` } // ReadOnlyLogState contains details on the current state of a read-only CT log. type ReadOnlyLogState struct { LogState // FinalTreeHead is the root hash and tree size at which the CT log was // made read-only. This should never change while the log is read-only. FinalTreeHead TreeHead `json:"final_tree_head"` } // TreeHead is the root hash and tree size of a CT log. type TreeHead struct { // SHA256RootHash is the root hash of the CT log's Merkle tree. SHA256RootHash []byte `json:"sha256_root_hash"` // TreeSize is the size of the CT log's Merkle tree. TreeSize int64 `json:"tree_size"` } // LogStatus method returns Log-status enum value for descriptive struct. func (ls *LogStates) LogStatus() LogStatus { switch { case ls == nil: return UndefinedLogStatus case ls.Pending != nil: return PendingLogStatus case ls.Qualified != nil: return QualifiedLogStatus case ls.Usable != nil: return UsableLogStatus case ls.ReadOnly != nil: return ReadOnlyLogStatus case ls.Retired != nil: return RetiredLogStatus case ls.Rejected != nil: return RejectedLogStatus default: return UndefinedLogStatus } } // String method returns printable name of the state. func (ls *LogStates) String() string { return ls.LogStatus().String() } // Active picks the set-up state. If multiple states are set (not expected) picks one of them. func (ls *LogStates) Active() (*LogState, *ReadOnlyLogState) { if ls == nil { return nil, nil } switch { case ls.Pending != nil: return ls.Pending, nil case ls.Qualified != nil: return ls.Qualified, nil case ls.Usable != nil: return ls.Usable, nil case ls.ReadOnly != nil: return nil, ls.ReadOnly case ls.Retired != nil: return ls.Retired, nil case ls.Rejected != nil: return ls.Rejected, nil default: return nil, nil } } // GoogleOperated returns whether Operator is considered to be Google. func (op *Operator) GoogleOperated() bool { for _, email := range op.Email { if strings.Contains(email, "google-ct-logs@googlegroups") { return true } } return false } // NewFromJSON creates a LogList from JSON encoded data. func NewFromJSON(llData []byte) (*LogList, error) { var ll LogList if err := json.Unmarshal(llData, &ll); err != nil { return nil, fmt.Errorf("failed to parse log list: %v", err) } return &ll, nil } // NewFromSignedJSON creates a LogList from JSON encoded data, checking a // signature along the way. The signature data should be provided as the // raw signature data. func NewFromSignedJSON(llData, rawSig []byte, pubKey crypto.PublicKey) (*LogList, error) { var sigAlgo tls.SignatureAlgorithm switch pkType := pubKey.(type) { case *rsa.PublicKey: sigAlgo = tls.RSA case *ecdsa.PublicKey: sigAlgo = tls.ECDSA default: return nil, fmt.Errorf("unsupported public key type %v", pkType) } tlsSig := tls.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: sigAlgo, }, Signature: rawSig, } if err := tls.VerifySignature(pubKey, llData, tlsSig); err != nil { return nil, fmt.Errorf("failed to verify signature: %v", err) } return NewFromJSON(llData) } // FindLogByName returns all logs whose names contain the given string. func (ll *LogList) FindLogByName(name string) []*Log { name = strings.ToLower(name) var results []*Log for _, op := range ll.Operators { for _, log := range op.Logs { if strings.Contains(strings.ToLower(log.Description), name) { results = append(results, log) } } } return results } // FindLogByURL finds the log with the given URL. func (ll *LogList) FindLogByURL(url string) *Log { for _, op := range ll.Operators { for _, log := range op.Logs { // Don't count trailing slashes if strings.TrimRight(log.URL, "/") == strings.TrimRight(url, "/") { return log } } } return nil } // FindLogByKeyHash finds the log with the given key hash. func (ll *LogList) FindLogByKeyHash(keyhash [sha256.Size]byte) *Log { for _, op := range ll.Operators { for _, log := range op.Logs { if bytes.Equal(log.LogID, keyhash[:]) { return log } } } return nil } // FindLogByKeyHashPrefix finds all logs whose key hash starts with the prefix. func (ll *LogList) FindLogByKeyHashPrefix(prefix string) []*Log { var results []*Log for _, op := range ll.Operators { for _, log := range op.Logs { hh := hex.EncodeToString(log.LogID[:]) if strings.HasPrefix(hh, prefix) { results = append(results, log) } } } return results } // FindLogByKey finds the log with the given DER-encoded key. func (ll *LogList) FindLogByKey(key []byte) *Log { for _, op := range ll.Operators { for _, log := range op.Logs { if bytes.Equal(log.Key[:], key) { return log } } } return nil } var hexDigits = regexp.MustCompile("^[0-9a-fA-F]+$") // FuzzyFindLog tries to find logs that match the given unspecified input, // whose format is unspecified. This generally returns a single log, but // if text input that matches multiple log descriptions is provided, then // multiple logs may be returned. func (ll *LogList) FuzzyFindLog(input string) []*Log { input = strings.Trim(input, " \t") if logs := ll.FindLogByName(input); len(logs) > 0 { return logs } if log := ll.FindLogByURL(input); log != nil { return []*Log{log} } // Try assuming the input is binary data of some form. First base64: if data, err := base64.StdEncoding.DecodeString(input); err == nil { if len(data) == sha256.Size { var hash [sha256.Size]byte copy(hash[:], data) if log := ll.FindLogByKeyHash(hash); log != nil { return []*Log{log} } } if log := ll.FindLogByKey(data); log != nil { return []*Log{log} } } // Now hex, but strip all internal whitespace first. input = stripInternalSpace(input) if data, err := hex.DecodeString(input); err == nil { if len(data) == sha256.Size { var hash [sha256.Size]byte copy(hash[:], data) if log := ll.FindLogByKeyHash(hash); log != nil { return []*Log{log} } } if log := ll.FindLogByKey(data); log != nil { return []*Log{log} } } // Finally, allow hex strings with an odd number of digits. if hexDigits.MatchString(input) { if logs := ll.FindLogByKeyHashPrefix(input); len(logs) > 0 { return logs } } return nil } func stripInternalSpace(input string) string { return strings.Map(func(r rune) rune { if !unicode.IsSpace(r) { return r } return -1 }, input) } google-certificate-transparency-go-2308f62/loglist3/loglist3_test.go000066400000000000000000000434621462611535200255400ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loglist3 import ( "bytes" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "log" "reflect" "sort" "strings" "testing" "time" "github.com/sergi/go-diff/diffmatchpatch" ) // mustParseTime is helper func func mustParseTime(format string, sTime string) time.Time { tm, e := time.Parse(format, sTime) if e != nil { log.Fatal(e) } return tm.UTC() } var sampleLogList = LogList{ IsAllLogs: true, Version: "1.1.1c", LogListTimestamp: mustParseTime(time.UnixDate, "Fri Dec 3 11:06:00 UTC 2021"), Operators: []*Operator{ { Name: "Google", Email: []string{"google-ct-logs@googlegroups.com"}, Logs: []*Log{ { Description: "Google 'Aviator' log", LogID: deb64("aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q="), Key: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q=="), URL: "https://ct.googleapis.com/aviator/", MMD: 86400, State: &LogStates{ ReadOnly: &ReadOnlyLogState{ LogState: LogState{Timestamp: time.Unix(1480512258, 330000000).UTC()}, FinalTreeHead: TreeHead{ TreeSize: 46466472, SHA256RootHash: deb64("LcGcZRsm+LGYmrlyC5LXhV1T6OD8iH5dNlb0sEJl9bA="), }, }, }, TemporalInterval: &TemporalInterval{ StartInclusive: mustParseTime(time.UnixDate, "Fri Mar 7 11:06:00 UTC 2014"), EndExclusive: mustParseTime(time.UnixDate, "Sat Mar 7 12:00:00 UTC 2015"), }, DNS: "aviator.ct.googleapis.com", }, { Description: "Google 'Icarus' log", LogID: deb64("KTxRllTIOWW6qlD8WAfUt2+/WHopctykwwz05UVH9Hg="), Key: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA=="), URL: "https://ct.googleapis.com/icarus/", MMD: 86400, State: &LogStates{ Usable: &LogState{ Timestamp: mustParseTime(time.RFC3339, "2018-02-27T00:00:00Z"), }, }, DNS: "icarus.ct.googleapis.com", }, { Description: "Google 'Racketeer' log", LogID: deb64("7kEv4llINIlh4vPgjGgugT7A/3cLbXUXF2OvMBT/l2g="), // Key value chosen to have a hash that starts ee4... (specifically ee412fe25948348961e2f3e08c682e813ec0ff770b6d75171763af3014ff9768) Key: deb64("Hy2TPTZ2yq9ASMmMZiB9SZEUx5WNH5G0Ft5Tm9vKMcPXA+ic/Ap3gg6fXzBJR8zLkt5lQjvKMdbHYMGv7yrsZg=="), URL: "https://ct.googleapis.com/racketeer/", MMD: 86400, DNS: "racketeer.ct.googleapis.com", }, { Description: "Google 'Rocketeer' log", LogID: deb64("7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs="), Key: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg=="), URL: "https://ct.googleapis.com/rocketeer/", MMD: 86400, DNS: "rocketeer.ct.googleapis.com", }, { Description: "Google 'Argon2020' log", LogID: deb64("sh4FzIuizYogTodm+Su5iiUgZ2va+nDnsklTLe+LkF4="), Key: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Tx2p1yKY4015NyIYvdrk36es0uAc1zA4PQ+TGRY+3ZjUTIYY9Wyu+3q/147JG4vNVKLtDWarZwVqGkg6lAYzA=="), URL: "https://ct.googleapis.com/logs/argon2020/", DNS: "argon2020.ct.googleapis.com", MMD: 86400, State: &LogStates{ Qualified: &LogState{ Timestamp: mustParseTime(time.RFC3339, "2018-02-27T00:00:00Z"), }, }, TemporalInterval: &TemporalInterval{ StartInclusive: mustParseTime(time.RFC3339, "2020-01-01T00:00:00Z"), EndExclusive: mustParseTime(time.RFC3339, "2021-01-01T00:00:00Z"), }, }, }, }, { Name: "Bob's CT Log Shop", Email: []string{"bob@example.com"}, Logs: []*Log{ { Description: "Bob's Dubious Log", LogID: deb64("zbUXm3/BwEb+6jETaj+PAC5hgvr4iW/syLL1tatgSQA="), Key: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2MNvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA=="), URL: "https://log.bob.io", MMD: 86400, State: &LogStates{ Retired: &LogState{ Timestamp: time.Unix(1460678400, 0).UTC(), }, }, TemporalInterval: &TemporalInterval{ StartInclusive: mustParseTime(time.UnixDate, "Fri Nov 7 12:00:00 UTC 2014"), EndExclusive: mustParseTime(time.UnixDate, "Sat Mar 7 12:00:00 UTC 2015"), }, DNS: "dubious-bob.ct.googleapis.com", PreviousOperators: []*PreviousOperator{ { Name: "Alice's Shady Log", EndTime: mustParseTime(time.UnixDate, "Thu Nov 6 12:00:00 UTC 2014"), }, }, }, }, }, }, } func TestJSONMarshal(t *testing.T) { var tests = []struct { name string in LogList want, wantErr string }{ { name: "MultiValid", in: sampleLogList, want: `{"is_all_logs":true,"version":"1.1.1c","log_list_timestamp":"2021-12-03T11:06:00Z","operators":[` + `{"name":"Google","email":["google-ct-logs@googlegroups.com"],"logs":[` + `{"description":"Google 'Aviator' log","log_id":"aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q=","key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q==","url":"https://ct.googleapis.com/aviator/","dns":"aviator.ct.googleapis.com","mmd":86400,"state":{"readonly":{"timestamp":"2016-11-30T13:24:18.33Z","final_tree_head":{"sha256_root_hash":"LcGcZRsm+LGYmrlyC5LXhV1T6OD8iH5dNlb0sEJl9bA=","tree_size":46466472}}},"temporal_interval":{"start_inclusive":"2014-03-07T11:06:00Z","end_exclusive":"2015-03-07T12:00:00Z"}},` + `{"description":"Google 'Icarus' log","log_id":"KTxRllTIOWW6qlD8WAfUt2+/WHopctykwwz05UVH9Hg=","key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA==","url":"https://ct.googleapis.com/icarus/","dns":"icarus.ct.googleapis.com","mmd":86400,"state":{"usable":{"timestamp":"2018-02-27T00:00:00Z"}}},` + `{"description":"Google 'Racketeer' log","log_id":"7kEv4llINIlh4vPgjGgugT7A/3cLbXUXF2OvMBT/l2g=","key":"Hy2TPTZ2yq9ASMmMZiB9SZEUx5WNH5G0Ft5Tm9vKMcPXA+ic/Ap3gg6fXzBJR8zLkt5lQjvKMdbHYMGv7yrsZg==","url":"https://ct.googleapis.com/racketeer/","dns":"racketeer.ct.googleapis.com","mmd":86400},` + `{"description":"Google 'Rocketeer' log","log_id":"7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=","key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==","url":"https://ct.googleapis.com/rocketeer/","dns":"rocketeer.ct.googleapis.com","mmd":86400},` + `{"description":"Google 'Argon2020' log","log_id": "sh4FzIuizYogTodm+Su5iiUgZ2va+nDnsklTLe+LkF4=","key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Tx2p1yKY4015NyIYvdrk36es0uAc1zA4PQ+TGRY+3ZjUTIYY9Wyu+3q/147JG4vNVKLtDWarZwVqGkg6lAYzA==","url":"https://ct.googleapis.com/logs/argon2020/","dns":"argon2020.ct.googleapis.com","mmd":86400,"state":{"qualified":{"timestamp":"2018-02-27T00:00:00Z"}},"temporal_interval":{"start_inclusive":"2020-01-01T00:00:00Z","end_exclusive":"2021-01-01T00:00:00Z"}}]},` + `{"name":"Bob's CT Log Shop","email":["bob@example.com"],"logs":[` + `{"description":"Bob's Dubious Log","log_id":"zbUXm3/BwEb+6jETaj+PAC5hgvr4iW/syLL1tatgSQA=","key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2MNvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA==","url":"https://log.bob.io","dns":"dubious-bob.ct.googleapis.com","mmd":86400,"previous_operators":[ {"name":"Alice's Shady Log","end_time":"2014-11-06T12:00:00Z"}],"state":{"retired":{"timestamp":"2016-04-15T00:00:00Z"}},"temporal_interval":{"start_inclusive":"2014-11-07T12:00:00Z","end_exclusive":"2015-03-07T12:00:00Z"}}]}]}`, }, } const jsonPrefix = "" const jsonIndent = " " for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := json.MarshalIndent(&test.in, jsonPrefix, jsonIndent) if err != nil { if test.wantErr == "" { t.Errorf("json.Marshal()=nil,%v; want _,nil", err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("json.Marshal()=nil,%v; want nil,err containing %q", err, test.wantErr) } return } if test.wantErr != "" { t.Errorf("json.Marshal()=%q,nil; want nil,err containing %q", got, test.wantErr) } // Format `test.want` in the same way as `got` var wantIndented bytes.Buffer if err := json.Indent(&wantIndented, []byte(test.want), jsonPrefix, jsonIndent); err != nil { t.Fatalf("json.Indent(test.want) = %q, want nil", err) } dmp := diffmatchpatch.New() if diffs := dmp.DiffMain(wantIndented.String(), string(got), false); len(diffs) > 1 { t.Errorf("json.Marshal(): diff \n%s", dmp.DiffPrettyText(diffs)) } }) } } func TestFindLogByName(t *testing.T) { var tests = []struct { name, in string want int }{ {name: "Single", in: "Dubious", want: 1}, {name: "SingleDifferentCase", in: "DUBious", want: 1}, {name: "Multiple", in: "Google", want: 5}, {name: "None", in: "Llamalog", want: 0}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := sampleLogList.FindLogByName(test.in) if len(got) != test.want { t.Errorf("len(FindLogByName(%q)=%d, want %d", test.in, len(got), test.want) } }) } } func TestFindLogByURL(t *testing.T) { var tests = []struct { name, in, want string }{ {name: "NotFound", in: "nowhere.com"}, {name: "Found//", in: "https://ct.googleapis.com/icarus/", want: "Google 'Icarus' log"}, {name: "Found./", in: "https://ct.googleapis.com/icarus", want: "Google 'Icarus' log"}, {name: "Found/.", in: "https://log.bob.io/", want: "Bob's Dubious Log"}, {name: "Found..", in: "https://log.bob.io", want: "Bob's Dubious Log"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { l := sampleLogList.FindLogByURL(test.in) got := "" if l != nil { got = l.Description } if got != test.want { t.Errorf("FindLogByURL(%q)=%q, want %q", test.in, got, test.want) } }) } } func TestFindLogByKeyhash(t *testing.T) { var tests = []struct { name string in []byte want string }{ { name: "NotFound", in: []byte{0xaa, 0xbb, 0xcc}, }, { name: "FoundRocketeer", in: []byte{ 0xee, 0x4b, 0xbd, 0xb7, 0x75, 0xce, 0x60, 0xba, 0xe1, 0x42, 0x69, 0x1f, 0xab, 0xe1, 0x9e, 0x66, 0xa3, 0x0f, 0x7e, 0x5f, 0xb0, 0x72, 0xd8, 0x83, 0x00, 0xc4, 0x7b, 0x89, 0x7a, 0xa8, 0xfd, 0xcb, }, want: "Google 'Rocketeer' log", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var hash [sha256.Size]byte copy(hash[:], test.in) l := sampleLogList.FindLogByKeyHash(hash) got := "" if l != nil { got = l.Description } if got != test.want { t.Errorf("FindLogByKeyHash(%x)=%q, want %q", test.in, got, test.want) } }) } } func TestFindLogByKeyhashPrefix(t *testing.T) { var tests = []struct { name, in string want []string }{ { name: "NotFound", in: "aabbcc", want: []string{}, }, { name: "FoundRocketeer", in: "ee4b", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundRocketeerOdd", in: "ee4bb", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundMultiple", in: "ee4", want: []string{"Google 'Racketeer' log", "Google 'Rocketeer' log"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { logs := sampleLogList.FindLogByKeyHashPrefix(test.in) got := make([]string, len(logs)) for i, l := range logs { got[i] = l.Description } sort.Strings(got) if !reflect.DeepEqual(got, test.want) { t.Errorf("FindLogByKeyHash(%x)=%q, want %q", test.in, got, test.want) } }) } } func TestFindLogByKey(t *testing.T) { var tests = []struct { name string in []byte want string }{ { name: "NotFound", in: []byte{0xaa, 0xbb, 0xcc}, }, { name: "FoundRocketeer", in: deb64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg=="), want: "Google 'Rocketeer' log", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { l := sampleLogList.FindLogByKey(test.in) got := "" if l != nil { got = l.Description } if got != test.want { t.Errorf("FindLogByKey(%x)=%q, want %q", test.in, got, test.want) } }) } } func TestFuzzyFindLog(t *testing.T) { var tests = []struct { name, in string want []string }{ { name: "NotFound", in: "aabbcc", want: []string{}, }, { name: "FoundByKey64", in: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByKeyHex", in: "3059301306072a8648ce3d020106082a8648ce3d03010703420004205b18c83cc18bb3310800bfa090572bb7478c6fb568b08e9078e9a073ea4f28212e9cc0f4161baaf9d5d7a980c34e2f523c9801254624252823772d05c2407a", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByKeyHashHex", in: " ee 4b bd b7 75 ce 60 ba e1 42 69 1f ab e1 9e 66 a3 0f 7e 5f b0 72 d8 83 00 c4 7b 89 7a a8 fd cb", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByKeyHashHexPrefix", in: "ee4bbdb7", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByKeyHash64", in: "7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByName", in: "Rocketeer", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByNameDifferentCase", in: "rocketeer", want: []string{"Google 'Rocketeer' log"}, }, { name: "FoundByURL", in: "https://ct.googleapis.com/rocketeer", want: []string{"Google 'Rocketeer' log"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { logs := sampleLogList.FuzzyFindLog(test.in) got := make([]string, len(logs)) for i, l := range logs { got[i] = l.Description } if !reflect.DeepEqual(got, test.want) { t.Errorf("FuzzyFindLog(%q)=%v, want %v", test.in, got, test.want) } }) } } func TestStripInternalSpace(t *testing.T) { var tests = []struct { in string want string }{ {in: "xyz", want: "xyz"}, {in: "x y z", want: "xyz"}, {in: "x yz ", want: "xyz"}, {in: " xyz ", want: "xyz"}, {in: "xy\t\tz", want: "xyz"}, } for _, test := range tests { got := stripInternalSpace(test.in) if got != test.want { t.Errorf("stripInternalSpace(%q)=%q, want %q", test.in, got, test.want) } } } func TestLogStatesString(t *testing.T) { var tests = []struct { name string logURL string want string }{ {name: "ReadOnly", logURL: "https://ct.googleapis.com/aviator/", want: "ReadOnlyLogStatus"}, {name: "Empty", logURL: "https://ct.googleapis.com/racketeer/", want: "UndefinedLogStatus"}, {name: "Usable", logURL: "https://ct.googleapis.com/icarus/", want: "UsableLogStatus"}, {name: "Retired", logURL: "https://log.bob.io", want: "RetiredLogStatus"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { l := sampleLogList.FindLogByURL(test.logURL) if got := l.State.String(); got != test.want { t.Errorf("%q: Log.State.String() = %s, want %s", test.logURL, got, test.want) } }) } } func TestLogStatesActive(t *testing.T) { a := &LogState{Timestamp: time.Unix(1460678400, 0).UTC()} f := &ReadOnlyLogState{ LogState: LogState{Timestamp: time.Unix(1480512258, 330000000).UTC()}, FinalTreeHead: TreeHead{ TreeSize: 46466472, SHA256RootHash: []byte{}, }, } var tests = []struct { name string in *LogStates wantState *LogState wantFState *ReadOnlyLogState }{ { name: "Retired", in: &LogStates{ Retired: a, }, wantState: a, wantFState: nil, }, { name: "ReadOnly", in: &LogStates{ ReadOnly: f, }, wantState: nil, wantFState: f, }, { name: "Qualified", in: &LogStates{ Qualified: a, }, wantState: a, wantFState: nil, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { gotState, gotFState := test.in.Active() if gotState != test.wantState { t.Errorf("Log-state from Active() = %q, want %q", gotState, test.wantState) } if gotFState != test.wantFState { t.Errorf("ReadOnly Log-state from Active() = %q, want %q", gotFState, test.wantFState) } }) } } func changeOperatorEmail(op Operator, email string) Operator { op.Email = make([]string, 1) op.Email[0] = email return op } func TestGoogleOperated(t *testing.T) { var tests = []struct { in Operator out bool }{ {in: *(sampleLogList.Operators[0]), out: true}, {in: *sampleLogList.Operators[1], out: false}, {in: changeOperatorEmail(*sampleLogList.Operators[1], "google-ct-logs@googlegroups"), out: true}, {in: changeOperatorEmail(*sampleLogList.Operators[0], "operator@googlegroups"), out: false}, } for _, test := range tests { isGoog := test.in.GoogleOperated() if isGoog != test.out { t.Errorf("GoogleOperated status for %s is %t, want %t", test.in.Name, isGoog, test.out) } } } func deb64(b string) []byte { data, err := base64.StdEncoding.DecodeString(b) if err != nil { panic(fmt.Sprintf("hard-coded test data failed to decode: %v", err)) } return data } google-certificate-transparency-go-2308f62/loglist3/logstatus_string.go000066400000000000000000000016221462611535200263440ustar00rootroot00000000000000// Code generated by "stringer -type=LogStatus"; DO NOT EDIT. package loglist3 import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[UndefinedLogStatus-0] _ = x[PendingLogStatus-1] _ = x[QualifiedLogStatus-2] _ = x[UsableLogStatus-3] _ = x[ReadOnlyLogStatus-4] _ = x[RetiredLogStatus-5] _ = x[RejectedLogStatus-6] } const _LogStatus_name = "UndefinedLogStatusPendingLogStatusQualifiedLogStatusUsableLogStatusReadOnlyLogStatusRetiredLogStatusRejectedLogStatus" var _LogStatus_index = [...]uint8{0, 18, 34, 52, 67, 84, 100, 117} func (i LogStatus) String() string { if i < 0 || i >= LogStatus(len(_LogStatus_index)-1) { return "LogStatus(" + strconv.FormatInt(int64(i), 10) + ")" } return _LogStatus_name[_LogStatus_index[i]:_LogStatus_index[i+1]] } google-certificate-transparency-go-2308f62/preload/000077500000000000000000000000001462611535200222675ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/preload/dumpscts/000077500000000000000000000000001462611535200241315ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/preload/dumpscts/dumpscts.go000066400000000000000000000035171462611535200263300ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Dumpscts prints out SCTs written to a file by the preloader command in // the ../preloader directory. package main import ( "compress/zlib" "encoding/gob" "flag" "io" "log" "os" "github.com/google/certificate-transparency-go/preload" ) var sctFile = flag.String("sct_file", "", "File to load SCTs & leaf data from") func main() { flag.Parse() var sctReader io.ReadCloser if *sctFile == "" { log.Fatal("Must specify --sct_file") } sctFileReader, err := os.Open(*sctFile) if err != nil { log.Fatal(err) } sctReader, err = zlib.NewReader(sctFileReader) if err != nil { log.Fatal(err) } defer func() { err := sctReader.Close() if err != nil && err != io.EOF { log.Fatalf("Error closing file: %s", err) } }() // TODO(alcutter) should probably store this stuff in a protobuf really. decoder := gob.NewDecoder(sctReader) var addedCert preload.AddedCert numAdded := 0 numFailed := 0 for { err = decoder.Decode(&addedCert) if err != nil { break } if addedCert.AddedOk { log.Println(addedCert.SignedCertificateTimestamp) numAdded++ } else { log.Printf("Cert was not added: %s", addedCert.ErrorMessage) numFailed++ } } log.Printf("Num certs added: %d, num failed: %d\n", numAdded, numFailed) } google-certificate-transparency-go-2308f62/preload/preloader/000077500000000000000000000000001462611535200242445ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/preload/preloader/preloader.go000066400000000000000000000214031462611535200265500ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Binary preloader submits certificates that may not already be present in CT Logs. package main import ( "compress/zlib" "context" "encoding/gob" "flag" "io" "net/http" "os" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/preload" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/x509" "k8s.io/klog/v2" ) var ( sourceLogURI = flag.String("source_log_uri", "https://ct.googleapis.com/aviator", "CT log base URI to fetch entries from") targetLogURI = flag.String("target_log_uri", "https://example.com/ct", "CT log base URI to add entries to") targetTemporalLogCfg = flag.String("target_temporal_log_cfg", "", "File holding temporal log configuration") batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries") numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers") parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches") parallelSubmit = flag.Int("parallel_submit", 2, "Number of concurrent add-[pre]-chain requests") startIndex = flag.Int64("start_index", 0, "Log index to start scanning at") sctInputFile = flag.String("sct_file", "", "File to save SCTs & leaf data to") precertsOnly = flag.Bool("precerts_only", false, "Only match precerts") tlsTimeout = flag.Duration("tls_timeout", 30*time.Second, "TLS handshake timeout (see http.Transport)") rspHeaderTimeout = flag.Duration("response_header_timeout", 30*time.Second, "Response header timeout (see http.Transport)") maxIdlePerHost = flag.Int("max_idle_conns_per_host", 10, "Maximum number of idle connections per host (see http.Transport)") maxIdleConns = flag.Int("max_idle_conns", 100, "Maximum number of idle connections (see http.Transport)") idleTimeout = flag.Duration("idle_conn_timeout", 90*time.Second, "Idle connections with no use within this period will be closed (see http.Transport)") disableKeepAlive = flag.Bool("disable_keepalive", false, "Disable HTTP Keep-Alive (see http.Transport)") expectContinueTimeout = flag.Duration("expect_continue_timeout", time.Second, "Amount of time to wait for a response if request uses Expect: 100-continue (see http.Transport") ) func recordSct(addedCerts chan<- *preload.AddedCert, certDer ct.ASN1Cert, sct *ct.SignedCertificateTimestamp) { addedCert := preload.AddedCert{ CertDER: certDer, SignedCertificateTimestamp: *sct, AddedOk: true, } addedCerts <- &addedCert } func recordFailure(addedCerts chan<- *preload.AddedCert, certDer ct.ASN1Cert, addError error) { addedCert := preload.AddedCert{ CertDER: certDer, AddedOk: false, ErrorMessage: addError.Error(), } addedCerts <- &addedCert } func sctDumper(addedCerts <-chan *preload.AddedCert, sctWriter io.Writer) { encoder := gob.NewEncoder(sctWriter) numAdded := 0 numFailed := 0 for c := range addedCerts { if c.AddedOk { numAdded++ } else { numFailed++ } if encoder != nil { err := encoder.Encode(c) if err != nil { klog.Fatalf("failed to encode to %s: %v", *sctInputFile, err) } } } klog.Infof("Added %d certs, %d failed, total: %d\n", numAdded, numFailed, numAdded+numFailed) } func certSubmitter(ctx context.Context, addedCerts chan<- *preload.AddedCert, logClient client.AddLogClient, certs <-chan *ct.LogEntry) { for c := range certs { chain := make([]ct.ASN1Cert, len(c.Chain)+1) chain[0] = ct.ASN1Cert{Data: c.X509Cert.Raw} copy(chain[1:], c.Chain) sct, err := logClient.AddChain(ctx, chain) if err != nil { klog.Errorf("failed to add chain with CN %s: %v\n", c.X509Cert.Subject.CommonName, err) recordFailure(addedCerts, chain[0], err) continue } recordSct(addedCerts, chain[0], sct) klog.V(2).Infof("Added chain for CN '%s', SCT: %s\n", c.X509Cert.Subject.CommonName, sct) } } func precertSubmitter(ctx context.Context, addedCerts chan<- *preload.AddedCert, logClient client.AddLogClient, precerts <-chan *ct.LogEntry) { for c := range precerts { chain := make([]ct.ASN1Cert, len(c.Chain)+1) chain[0] = c.Precert.Submitted copy(chain[1:], c.Chain) sct, err := logClient.AddPreChain(ctx, chain) if err != nil { klog.Errorf("failed to add pre-chain with CN %s: %v", c.Precert.TBSCertificate.Subject.CommonName, err) recordFailure(addedCerts, chain[0], err) continue } recordSct(addedCerts, chain[0], sct) klog.V(2).Infof("Added precert chain for CN '%s', SCT: %s\n", c.Precert.TBSCertificate.Subject.CommonName, sct) } } func main() { flag.Parse() klog.CopyStandardLogTo("WARNING") var sctFileWriter io.Writer var err error if *sctInputFile != "" { sctFile, err := os.Create(*sctInputFile) if err != nil { klog.Exitf("Failed to create SCT file: %v", err) } defer func() { if err := sctFile.Close(); err != nil { klog.Exitf("Failed to close SCT file: %v", err) } }() sctFileWriter = sctFile } else { sctFileWriter = io.Discard } sctWriter := zlib.NewWriter(sctFileWriter) defer func() { err := sctWriter.Close() if err != nil { klog.Exitf("Failed to close SCT file: %v", err) } }() transport := &http.Transport{ TLSHandshakeTimeout: *tlsTimeout, ResponseHeaderTimeout: *rspHeaderTimeout, MaxIdleConnsPerHost: *maxIdlePerHost, DisableKeepAlives: *disableKeepAlive, MaxIdleConns: *maxIdleConns, IdleConnTimeout: *idleTimeout, ExpectContinueTimeout: *expectContinueTimeout, } fetchLogClient, err := client.New(*sourceLogURI, &http.Client{ Timeout: 10 * time.Second, Transport: transport, }, jsonclient.Options{UserAgent: "ct-go-preloader/1.0"}) if err != nil { klog.Exitf("Failed to create client for source log: %v", err) } opts := scanner.ScannerOptions{ FetcherOptions: scanner.FetcherOptions{ BatchSize: *batchSize, ParallelFetch: *parallelFetch, StartIndex: *startIndex, }, Matcher: scanner.MatchAll{}, PrecertOnly: *precertsOnly, NumWorkers: *numWorkers, } s := scanner.NewScanner(fetchLogClient, opts) bufferSize := 10 * *parallelSubmit certs := make(chan *ct.LogEntry, bufferSize) precerts := make(chan *ct.LogEntry, bufferSize) addedCerts := make(chan *preload.AddedCert, bufferSize) var sctWriterWG sync.WaitGroup sctWriterWG.Add(1) go func() { defer sctWriterWG.Done() sctDumper(addedCerts, sctWriter) }() var submitLogClient client.AddLogClient if *targetTemporalLogCfg != "" { cfg, err := client.TemporalLogConfigFromFile(*targetTemporalLogCfg) if err != nil { klog.Exitf("Failed to load temporal log config: %v", err) } submitLogClient, err = client.NewTemporalLogClient(cfg, &http.Client{Transport: transport}) if err != nil { klog.Exitf("Failed to create client for destination temporal log: %v", err) } } else { submitLogClient, err = client.New(*targetLogURI, &http.Client{Transport: transport}, jsonclient.Options{UserAgent: "ct-go-preloader/1.0"}) if err != nil { klog.Exitf("Failed to create client for destination log: %v", err) } } ctx := context.Background() var submitterWG sync.WaitGroup for w := 0; w < *parallelSubmit; w++ { submitterWG.Add(2) go func() { defer submitterWG.Done() certSubmitter(ctx, addedCerts, submitLogClient, certs) }() go func() { defer submitterWG.Done() precertSubmitter(ctx, addedCerts, submitLogClient, precerts) }() } addChainFunc := func(rawEntry *ct.RawLogEntry) { entry, err := rawEntry.ToLogEntry() if x509.IsFatal(err) { klog.Errorf("Failed to parse cert at %d: %v", rawEntry.Index, err) return } certs <- entry } addPreChainFunc := func(rawEntry *ct.RawLogEntry) { entry, err := rawEntry.ToLogEntry() if x509.IsFatal(err) { klog.Errorf("Failed to parse precert at %d: %v", rawEntry.Index, err) return } precerts <- entry } if err := s.Scan(ctx, addChainFunc, addPreChainFunc); err != nil { klog.Errorf("Scan(): %v", err) } close(certs) close(precerts) submitterWG.Wait() close(addedCerts) sctWriterWG.Wait() } google-certificate-transparency-go-2308f62/preload/types.go000066400000000000000000000020211462611535200237550ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package preload holds code for adding batches of certificates to CT logs. package preload import ( ct "github.com/google/certificate-transparency-go" ) // AddedCert holds information about a certificate that has been added to a log. type AddedCert struct { CertDER ct.ASN1Cert SignedCertificateTimestamp ct.SignedCertificateTimestamp AddedOk bool ErrorMessage string } google-certificate-transparency-go-2308f62/proto_gen.go000066400000000000000000000030531462611535200231650ustar00rootroot00000000000000// Copyright 2021 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct // We do the protoc generation here (rather than in the individual directories) // in order to work around the newly-enforced rule that all protobuf file "names" // must be unique. // See https://developers.google.com/protocol-buffers/docs/proto#packages and // https://github.com/golang/protobuf/issues/1122 //go:generate sh -c "protoc -I=. -I$(go list -f '{{ .Dir }}' github.com/google/trillian) -I$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go) --go_out=paths=source_relative:. trillian/ctfe/configpb/config.proto" //go:generate sh -c "protoc -I=. -I$(go list -f '{{ .Dir }}' github.com/google/trillian) -I$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go) --go_out=paths=source_relative:. trillian/migrillian/configpb/config.proto" //go:generate sh -c "protoc -I=. -I$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go) --go_out=paths=source_relative:. client/configpb/multilog.proto" google-certificate-transparency-go-2308f62/scanner/000077500000000000000000000000001462611535200222725ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/scanner/fetcher.go000066400000000000000000000232051462611535200242430ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scanner import ( "context" "net/http" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/trillian/client/backoff" "k8s.io/klog/v2" ) // LogClient implements the subset of CT log API that the Fetcher uses. type LogClient interface { BaseURI() string GetSTH(context.Context) (*ct.SignedTreeHead, error) GetRawEntries(ctx context.Context, start, end int64) (*ct.GetEntriesResponse, error) } // FetcherOptions holds configuration options for the Fetcher. type FetcherOptions struct { // Number of entries to request in one batch from the Log. BatchSize int // Number of concurrent fetcher workers to run. ParallelFetch int // [StartIndex, EndIndex) is a log entry range to fetch. If EndIndex == 0, // then it gets reassigned to sth.TreeSize. StartIndex int64 EndIndex int64 // Continuous determines whether Fetcher should run indefinitely after // reaching EndIndex. Continuous bool } // DefaultFetcherOptions returns new FetcherOptions with sensible defaults. func DefaultFetcherOptions() *FetcherOptions { return &FetcherOptions{ BatchSize: 1000, ParallelFetch: 1, StartIndex: 0, EndIndex: 0, Continuous: false, } } // Fetcher is a tool that fetches entries from a CT Log. type Fetcher struct { // Base URI of the CT log, for diagnostics. uri string // Client used to talk to the CT log instance. client LogClient // Configuration options for this Fetcher instance. opts *FetcherOptions // Current STH of the Log this Fetcher sends queries to. sth *ct.SignedTreeHead // The STH retrieval backoff state. Used only in Continuous fetch mode. sthBackoff *backoff.Backoff // Stops range generator, which causes the Fetcher to terminate gracefully. mu sync.Mutex cancel context.CancelFunc } // EntryBatch represents a contiguous range of entries of the Log. type EntryBatch struct { Start int64 // LeafIndex of the first entry in the range. Entries []ct.LeafEntry // Entries of the range. } // fetchRange represents a range of certs to fetch from a CT log. type fetchRange struct { start int64 // inclusive end int64 // inclusive } // NewFetcher creates a Fetcher instance using client to talk to the log, // taking configuration options from opts. func NewFetcher(client LogClient, opts *FetcherOptions) *Fetcher { cancel := func() {} // Protect against calling Stop before Run. return &Fetcher{ uri: client.BaseURI(), client: client, opts: opts, cancel: cancel, } } // Prepare caches the latest Log's STH if not present and returns it. It also // adjusts the entry range to fit the size of the tree. func (f *Fetcher) Prepare(ctx context.Context) (*ct.SignedTreeHead, error) { if f.sth != nil { return f.sth, nil } sth, err := f.client.GetSTH(ctx) if err != nil { klog.Errorf("%s: GetSTH() failed: %v", f.uri, err) return nil, err } klog.V(1).Infof("%s: Got STH with %d certs", f.uri, sth.TreeSize) if size := int64(sth.TreeSize); f.opts.EndIndex == 0 || f.opts.EndIndex > size { klog.V(1).Infof("%s: Reset EndIndex from %d to %d", f.uri, f.opts.EndIndex, size) f.opts.EndIndex = size } f.sth = sth return sth, nil } // Run performs fetching of the Log. Blocks until scanning is complete, the // passed in context is canceled, or Stop is called (and pending work is // finished). For each successfully fetched batch, runs the fn callback. func (f *Fetcher) Run(ctx context.Context, fn func(EntryBatch)) error { klog.V(1).Infof("%s: Starting up Fetcher...", f.uri) if _, err := f.Prepare(ctx); err != nil { return err } cctx, cancel := context.WithCancel(ctx) defer cancel() f.mu.Lock() f.cancel = cancel f.mu.Unlock() // Use a separately-cancelable context for the range generator, so we can // close it down (in Stop) but still let the fetchers below run to // completion. ranges := f.genRanges(cctx) // Run fetcher workers. var wg sync.WaitGroup for w, cnt := 0, f.opts.ParallelFetch; w < cnt; w++ { wg.Add(1) go func(idx int) { defer wg.Done() klog.V(1).Infof("%s: Fetcher worker %d starting...", f.uri, idx) f.runWorker(ctx, ranges, fn) klog.V(1).Infof("%s: Fetcher worker %d finished", f.uri, idx) }(w) } wg.Wait() klog.V(1).Infof("%s: Fetcher terminated", f.uri) return nil } // Stop causes the Fetcher to terminate gracefully. After this call Run will // try to finish all the started fetches, and then return. Does nothing if // there was no preceding Run invocation. func (f *Fetcher) Stop() { f.mu.Lock() defer f.mu.Unlock() f.cancel() } // genRanges returns a channel of ranges to fetch, and starts a goroutine that // sends things down this channel. The goroutine terminates when all ranges // have been generated, or if context is cancelled. func (f *Fetcher) genRanges(ctx context.Context) <-chan fetchRange { batch := int64(f.opts.BatchSize) ranges := make(chan fetchRange) go func() { klog.V(1).Infof("%s: Range generator starting", f.uri) defer klog.V(1).Infof("%s: Range generator finished", f.uri) defer close(ranges) start, end := f.opts.StartIndex, f.opts.EndIndex for start < end || f.opts.Continuous { // In continuous mode wait for bigger STH every time we reach the end, // including, possibly, the very first iteration. if start == end { // Implies f.opts.Continuous == true. if err := f.updateSTH(ctx); err != nil { klog.Warningf("%s: Failed to obtain bigger STH: %v", f.uri, err) return } end = f.opts.EndIndex } batchEnd := start + min(end-start, batch) next := fetchRange{start, batchEnd - 1} select { case <-ctx.Done(): klog.Warningf("%s: Cancelling genRanges: %v", f.uri, ctx.Err()) return case ranges <- next: } start = batchEnd } }() return ranges } // updateSTH waits until a bigger STH is discovered, and updates the Fetcher // accordingly. It is optimized for both bulk-load (new STH is way bigger then // the last one) and keep-up (STH grows slowly) modes of operation. Waits for // some time until the STH grows enough to request a full batch, but falls back // to *any* STH bigger than the old one if it takes too long. // Returns error only if the context is cancelled. func (f *Fetcher) updateSTH(ctx context.Context) error { // TODO(pavelkalinnikov): Make these parameters tunable. const quickDur = 45 * time.Second if f.sthBackoff == nil { f.sthBackoff = &backoff.Backoff{ Min: 1 * time.Second, Max: 30 * time.Second, Factor: 2, Jitter: true, } } lastSize := uint64(f.opts.EndIndex) targetSize := lastSize + uint64(f.opts.BatchSize) quickDeadline := time.Now().Add(quickDur) return f.sthBackoff.Retry(ctx, func() error { sth, err := f.client.GetSTH(ctx) if err != nil { return backoff.RetriableErrorf("GetSTH: %v", err) } klog.V(2).Infof("%s: Got STH with %d certs", f.uri, sth.TreeSize) quick := time.Now().Before(quickDeadline) if sth.TreeSize <= lastSize || quick && sth.TreeSize < targetSize { return backoff.RetriableErrorf("wait for bigger STH than %d (last=%d, target=%d)", sth.TreeSize, lastSize, targetSize) } if quick { f.sthBackoff.Reset() // Growth is presumably fast, set next pause to Min. } f.sth = sth f.opts.EndIndex = int64(sth.TreeSize) return nil }) } // runWorker is a worker function for handling fetcher ranges. // Accepts cert ranges to fetch over the ranges channel, and if the fetch is // successful sends the corresponding EntryBatch through the fn callback. Will // retry failed attempts to retrieve ranges until the context is cancelled. func (f *Fetcher) runWorker(ctx context.Context, ranges <-chan fetchRange, fn func(EntryBatch)) { for r := range ranges { // Logs MAY return fewer than the number of leaves requested. Only complete // if we actually got all the leaves we were expecting. for r.start <= r.end { if ctx.Err() != nil { // Prevent spinning when context is canceled. return } // TODO(pavelkalinnikov): Make these parameters tunable. // This backoff will only apply to a single request and be reset for the next one. // This precludes reaching some kind of stability in request rate, but means that // an intermittent problem won't harm long-term running of the worker. bo := &backoff.Backoff{ Min: 1 * time.Second, Max: 30 * time.Second, Factor: 2, Jitter: true, } var resp *ct.GetEntriesResponse // TODO(pavelkalinnikov): Report errors in a LogClient decorator on failure. if err := bo.Retry(ctx, func() error { var err error resp, err = f.client.GetRawEntries(ctx, r.start, r.end) return err }); err != nil { if rspErr, isRspErr := err.(jsonclient.RspError); isRspErr && rspErr.StatusCode == http.StatusTooManyRequests { klog.V(2).Infof("%s: GetRawEntries() failed: %v", f.uri, err) } else { klog.Errorf("%s: GetRawEntries() failed: %v", f.uri, err) } // There is no error reporting yet for this worker, so just retry again. continue } fn(EntryBatch{Start: r.start, Entries: resp.Entries}) r.start += int64(len(resp.Entries)) } } } func min(a, b int64) int64 { if a < b { return a } return b } google-certificate-transparency-go-2308f62/scanner/matcher.go000066400000000000000000000225131462611535200242470ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scanner import ( "context" "log" "math/big" "regexp" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/x509" ) // Matcher describes how to match certificates and precertificates, based solely on the parsed [pre-]certificate; // clients should implement this interface to perform their own match criteria. type Matcher interface { // CertificateMatches is called by the scanner for each X509 Certificate found in the log. // The implementation should return true if the passed Certificate is interesting, and false otherwise. CertificateMatches(*x509.Certificate) bool // PrecertificateMatches is called by the scanner for each CT Precertificate found in the log. // The implementation should return true if the passed Precertificate is interesting, and false otherwise. PrecertificateMatches(*ct.Precertificate) bool } // MatchAll is a Matcher which will match every possible Certificate and Precertificate. type MatchAll struct{} // CertificateMatches returns true if the given cert should match; in this case, always. func (m MatchAll) CertificateMatches(_ *x509.Certificate) bool { return true } // PrecertificateMatches returns true if the given precert should match, in this case, always. func (m MatchAll) PrecertificateMatches(_ *ct.Precertificate) bool { return true } // MatchNone is a Matcher which will never match any Certificate or Precertificate. type MatchNone struct{} // CertificateMatches returns true if the given cert should match; in this case, never. func (m MatchNone) CertificateMatches(_ *x509.Certificate) bool { return false } // PrecertificateMatches returns true if the given cert should match; in this case, never. func (m MatchNone) PrecertificateMatches(_ *ct.Precertificate) bool { return false } // MatchSerialNumber performs a match for a specific serial number. type MatchSerialNumber struct { SerialNumber big.Int } // CertificateMatches returns true if the given cert should match; in this // case, only if the serial number matches. func (m MatchSerialNumber) CertificateMatches(c *x509.Certificate) bool { return c.SerialNumber.String() == m.SerialNumber.String() } // PrecertificateMatches returns true if the given cert should match; in this // case, only if the serial number matches. func (m MatchSerialNumber) PrecertificateMatches(p *ct.Precertificate) bool { return p.TBSCertificate.SerialNumber.String() == m.SerialNumber.String() } // MatchSubjectRegex is a Matcher which will use CertificateSubjectRegex and PrecertificateSubjectRegex // to determine whether Certificates and Precertificates are interesting. // The two regexes are tested against Subject CN (Common Name) as well as all // Subject Alternative Names type MatchSubjectRegex struct { CertificateSubjectRegex *regexp.Regexp PrecertificateSubjectRegex *regexp.Regexp } // CertificateMatches returns true if either CN or any SAN of c matches m.CertificateSubjectRegex. func (m MatchSubjectRegex) CertificateMatches(c *x509.Certificate) bool { if m.CertificateSubjectRegex.FindStringIndex(c.Subject.CommonName) != nil { return true } for _, alt := range c.DNSNames { if m.CertificateSubjectRegex.FindStringIndex(alt) != nil { return true } } return false } // PrecertificateMatches returns true if either CN or any SAN of p matches m.PrecertificateSubjectRegex. func (m MatchSubjectRegex) PrecertificateMatches(p *ct.Precertificate) bool { if m.PrecertificateSubjectRegex.FindStringIndex(p.TBSCertificate.Subject.CommonName) != nil { return true } for _, alt := range p.TBSCertificate.DNSNames { if m.PrecertificateSubjectRegex.FindStringIndex(alt) != nil { return true } } return false } // MatchIssuerRegex matches on issuer CN (common name) by regex type MatchIssuerRegex struct { CertificateIssuerRegex *regexp.Regexp PrecertificateIssuerRegex *regexp.Regexp } // CertificateMatches returns true if the given cert's CN matches. func (m MatchIssuerRegex) CertificateMatches(c *x509.Certificate) bool { return m.CertificateIssuerRegex.FindStringIndex(c.Issuer.CommonName) != nil } // PrecertificateMatches returns true if the given precert's CN matches. func (m MatchIssuerRegex) PrecertificateMatches(p *ct.Precertificate) bool { return m.PrecertificateIssuerRegex.FindStringIndex(p.TBSCertificate.Issuer.CommonName) != nil } // MatchSCTTimestamp is a matcher which matches leaf entries with the specified Timestamp. type MatchSCTTimestamp struct { Timestamp uint64 } // Matches returns true if the timestamp embedded in the leaf matches the one // specified by this matcher. func (m MatchSCTTimestamp) Matches(leaf *ct.LeafEntry) bool { entry, _ := ct.LogEntryFromLeaf(1, leaf) if entry == nil { // Can't validate if we can't parse return false } return entry.Leaf.TimestampedEntry.Timestamp == m.Timestamp } // LeafMatcher describes how to match log entries, based on the Log LeafEntry // (which includes the unparsed [pre-]certificate; clients should implement this // interface to perform their own match criteria. type LeafMatcher interface { Matches(*ct.LeafEntry) bool } // CertParseFailMatcher is a LeafMatcher which will match any Certificate or Precertificate that // triggered an error on parsing. type CertParseFailMatcher struct { MatchNonFatalErrs bool } // Matches returns true for parse errors. func (m CertParseFailMatcher) Matches(leaf *ct.LeafEntry) bool { _, err := ct.LogEntryFromLeaf(1, leaf) if err != nil { if x509.IsFatal(err) { return true } return m.MatchNonFatalErrs } return false } // CertVerifyFailMatcher is a LeafMatcher which will match any Certificate or Precertificate that fails // validation. The PopulateRoots() method should be called before use. type CertVerifyFailMatcher struct { roots *x509.CertPool } // PopulateRoots adds the accepted roots for the log to the pool for validation. func (m *CertVerifyFailMatcher) PopulateRoots(ctx context.Context, logClient *client.LogClient) { if m.roots != nil { return } m.roots = x509.NewCertPool() roots, err := logClient.GetAcceptedRoots(ctx) if err != nil { log.Fatal(err) } for _, root := range roots { cert, _ := x509.ParseCertificate(root.Data) if cert != nil { m.roots.AddCert(cert) } else { log.Fatal(err) } } } // Matches returns true for validation errors. func (m CertVerifyFailMatcher) Matches(leaf *ct.LeafEntry) bool { entry, _ := ct.LogEntryFromLeaf(1, leaf) if entry == nil { // Can't validate if we can't parse return false } // Validate the [pre-]certificate as of just before its expiry. var notBefore time.Time if entry.X509Cert != nil { notBefore = entry.X509Cert.NotAfter } else { notBefore = entry.Precert.TBSCertificate.NotAfter } when := notBefore.Add(-1 * time.Second) opts := x509.VerifyOptions{ KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, Roots: m.roots, Intermediates: x509.NewCertPool(), CurrentTime: when, } chain := make([]*x509.Certificate, len(entry.Chain)) for ii, cert := range entry.Chain { intermediate, err := x509.ParseCertificate(cert.Data) if intermediate == nil { log.Printf("Intermediate %d fails to parse: %v", ii, err) return true } chain[ii] = intermediate opts.Intermediates.AddCert(intermediate) } if entry.X509Cert != nil { if _, err := entry.X509Cert.Verify(opts); err != nil { log.Printf("Cert fails to validate as of %v: %v", opts.CurrentTime, err) return true } return false } if entry.Precert != nil { precert, err := x509.ParseCertificate(entry.Precert.Submitted.Data) if err != nil { log.Printf("Precert fails to parse as of %v: %v", opts.CurrentTime, err) return true } // Ignore unhandled poison extension. dropUnhandledExtension(precert, x509.OIDExtensionCTPoison) for i := 1; i < len(chain); i++ { // PolicyConstraints is legal (and critical) but unparsed. dropUnhandledExtension(chain[i], x509.OIDExtensionPolicyConstraints) } // Drop CT EKU from preissuer if present. if len(chain) > 0 { for i, eku := range chain[0].ExtKeyUsage { if eku == x509.ExtKeyUsageCertificateTransparency { chain[0].ExtKeyUsage = append(chain[0].ExtKeyUsage[:i], chain[0].ExtKeyUsage[i+1:]...) break } } } if _, err := precert.Verify(opts); err != nil { log.Printf("Precert fails to validate as of %v: %v", opts.CurrentTime, err) return true } return false } log.Printf("Neither cert nor precert present!") return true } func dropUnhandledExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier) { for j, extOID := range cert.UnhandledCriticalExtensions { if extOID.Equal(oid) { cert.UnhandledCriticalExtensions = append(cert.UnhandledCriticalExtensions[:j], cert.UnhandledCriticalExtensions[j+1:]...) return } } } google-certificate-transparency-go-2308f62/scanner/scanlog/000077500000000000000000000000001462611535200237205ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/scanner/scanlog/scanlog.go000066400000000000000000000171051462611535200257010ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Binary scanlog allows an existing CT Log to be scanned for certificates of interest. package main import ( "context" "encoding/base64" "flag" "fmt" "log" "math/big" "net/http" "os" "path" "regexp" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/x509" ) const ( // matchesNothingRegex is a regex which cannot match any input. matchesNothingRegex = "a^" ) var ( logURI = flag.String("log_uri", "https://ct.googleapis.com/aviator", "CT log base URI") matchSubjectRegex = flag.String("match_subject_regex", ".*", "Regex to match CN/SAN") matchIssuerRegex = flag.String("match_issuer_regex", "", "Regex to match in issuer CN") precertsOnly = flag.Bool("precerts_only", false, "Only match precerts") serialNumber = flag.String("serial_number", "", "Serial number of certificate of interest") sctTimestamp = flag.Uint64("sct_timestamp_ms", 0, "Timestamp of logged SCT") parseErrors = flag.Bool("parse_errors", false, "Only match certificates with parse errors") nfParseErrors = flag.Bool("non_fatal_errors", false, "Treat non-fatal parse errors as also matching (with --parse_errors)") validateErrors = flag.Bool("validate_errors", false, "Only match certificates with validation errors") batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries") numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers") parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches") startIndex = flag.Int64("start_index", 0, "Log index to start scanning at") endIndex = flag.Int64("end_index", 0, "Log index to end scanning at (non-inclusive, 0 = end of log)") printChains = flag.Bool("print_chains", false, "If true prints the whole chain rather than a summary") dumpDir = flag.String("dump_dir", "", "Directory to store matched certificates in") ) func dumpData(entry *ct.RawLogEntry) { if *dumpDir == "" { return } prefix := "unknown" suffix := "unknown" switch eType := entry.Leaf.TimestampedEntry.EntryType; eType { case ct.X509LogEntryType: prefix = "cert" suffix = "leaf" case ct.PrecertLogEntryType: prefix = "precert" suffix = "precert" default: log.Printf("Unknown log entry type %d", eType) } if len(entry.Cert.Data) > 0 { name := fmt.Sprintf("%s-%014d-%s.der", prefix, entry.Index, suffix) filename := path.Join(*dumpDir, name) if err := os.WriteFile(filename, entry.Cert.Data, 0644); err != nil { log.Printf("Failed to dump data for %s at index %d: %v", prefix, entry.Index, err) } } for ii := 0; ii < len(entry.Chain); ii++ { name := fmt.Sprintf("%s-%014d-%02d.der", prefix, entry.Index, ii) filename := path.Join(*dumpDir, name) if err := os.WriteFile(filename, entry.Chain[ii].Data, 0644); err != nil { log.Printf("Failed to dump data for CA at index %d: %v", entry.Index, err) } } } // Prints out a short bit of info about |cert|, found at |index| in the // specified log func logCertInfo(entry *ct.RawLogEntry) { parsedEntry, err := entry.ToLogEntry() if x509.IsFatal(err) || parsedEntry.X509Cert == nil { log.Printf("Process cert at index %d: ", entry.Index, err) } else { log.Printf("Process cert at index %d: CN: '%s'", entry.Index, parsedEntry.X509Cert.Subject.CommonName) } dumpData(entry) } // Prints out a short bit of info about |precert|, found at |index| in the // specified log func logPrecertInfo(entry *ct.RawLogEntry) { parsedEntry, err := entry.ToLogEntry() if x509.IsFatal(err) || parsedEntry.Precert == nil { log.Printf("Process precert at index %d: ", entry.Index, err) } else { log.Printf("Process precert at index %d: CN: '%s' Issuer: %s", entry.Index, parsedEntry.Precert.TBSCertificate.Subject.CommonName, parsedEntry.Precert.TBSCertificate.Issuer.CommonName) } dumpData(entry) } func chainToString(certs []ct.ASN1Cert) string { var output []byte for _, cert := range certs { output = append(output, cert.Data...) } return base64.StdEncoding.EncodeToString(output) } func logFullChain(entry *ct.RawLogEntry) { log.Printf("Index %d: Chain: %s", entry.Index, chainToString(entry.Chain)) } func createRegexes(regexValue string) (*regexp.Regexp, *regexp.Regexp) { // Make a regex matcher var certRegex *regexp.Regexp precertRegex := regexp.MustCompile(regexValue) switch *precertsOnly { case true: certRegex = regexp.MustCompile(matchesNothingRegex) case false: certRegex = precertRegex } return certRegex, precertRegex } func createMatcherFromFlags(logClient *client.LogClient) (interface{}, error) { if *parseErrors { return scanner.CertParseFailMatcher{MatchNonFatalErrs: *nfParseErrors}, nil } if *validateErrors { matcher := scanner.CertVerifyFailMatcher{} matcher.PopulateRoots(context.TODO(), logClient) return matcher, nil } if *matchIssuerRegex != "" { certRegex, precertRegex := createRegexes(*matchIssuerRegex) return scanner.MatchIssuerRegex{ CertificateIssuerRegex: certRegex, PrecertificateIssuerRegex: precertRegex}, nil } if *serialNumber != "" { log.Printf("Using SerialNumber matcher on %s", *serialNumber) var sn big.Int _, success := sn.SetString(*serialNumber, 0) if !success { return nil, fmt.Errorf("invalid serialNumber %s", *serialNumber) } return scanner.MatchSerialNumber{SerialNumber: sn}, nil } if *sctTimestamp != 0 { log.Printf("Using SCT Timestamp matcher on %d (%v)", *sctTimestamp, time.Unix(0, int64(*sctTimestamp*1000000))) return scanner.MatchSCTTimestamp{Timestamp: *sctTimestamp}, nil } certRegex, precertRegex := createRegexes(*matchSubjectRegex) return scanner.MatchSubjectRegex{ CertificateSubjectRegex: certRegex, PrecertificateSubjectRegex: precertRegex}, nil } func main() { flag.Parse() logClient, err := client.New(*logURI, &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ TLSHandshakeTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, }, jsonclient.Options{UserAgent: "ct-go-scanlog/1.0"}) if err != nil { log.Fatal(err) } matcher, err := createMatcherFromFlags(logClient) if err != nil { log.Fatal(err) } opts := scanner.ScannerOptions{ FetcherOptions: scanner.FetcherOptions{ BatchSize: *batchSize, ParallelFetch: *parallelFetch, StartIndex: *startIndex, EndIndex: *endIndex, }, Matcher: matcher, NumWorkers: *numWorkers, } s := scanner.NewScanner(logClient, opts) ctx := context.Background() if *printChains { if err := s.Scan(ctx, logFullChain, logFullChain); err != nil { log.Fatal(err) } } else { if err := s.Scan(ctx, logCertInfo, logPrecertInfo); err != nil { log.Fatal(err) } } } google-certificate-transparency-go-2308f62/scanner/scanner.go000066400000000000000000000245411462611535200242600ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package scanner holds code for iterating through the contents of a CT log. package scanner import ( "context" "fmt" "sync" "sync/atomic" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/x509" "k8s.io/klog/v2" ) // ScannerOptions holds configuration options for the Scanner. type ScannerOptions struct { // nolint:revive FetcherOptions // Custom matcher for x509 Certificates, functor will be called for each // Certificate found during scanning. Should be a Matcher or LeafMatcher // implementation. Matcher interface{} // Match precerts only (Matcher still applies to precerts). PrecertOnly bool // Number of concurrent matchers to run. NumWorkers int // Number of fetched entries to buffer on their way to the callbacks. BufferSize int } // DefaultScannerOptions returns a new ScannerOptions with sensible defaults. func DefaultScannerOptions() *ScannerOptions { return &ScannerOptions{ FetcherOptions: *DefaultFetcherOptions(), Matcher: &MatchAll{}, PrecertOnly: false, NumWorkers: 1, } } // Scanner is a tool to scan all the entries in a CT Log. type Scanner struct { // N.B. 64-bit fields must be first due to // https://golang.org/pkg/sync/atomic/#pkg-note-BUG // Counters of the number of certificates scanned and matched. certsProcessed int64 certsMatched int64 // Counter of the number of precertificates encountered during the scan. precertsSeen int64 unparsableEntries int64 entriesWithNonFatalErrors int64 fetcher *Fetcher // Configuration options for this Scanner instance. opts ScannerOptions } // entryInfo represents information about a log entry. type entryInfo struct { // The index of the entry containing the LeafInput in the log. index int64 // The log entry returned by the log server. entry ct.LeafEntry } // Takes the error returned by either x509.ParseCertificate() or // x509.ParseTBSCertificate() and determines if it's non-fatal or otherwise. // In the case of non-fatal errors, the error will be logged, // entriesWithNonFatalErrors will be incremented, and the return value will be // false. // Fatal errors will cause the function to return true. // When err is nil, this method does nothing. func (s *Scanner) isCertErrorFatal(err error, logEntry *ct.LogEntry, index int64) bool { if err == nil { // No error to handle. return false } else if !x509.IsFatal(err) { atomic.AddInt64(&s.entriesWithNonFatalErrors, 1) // We'll make a note, but continue. klog.V(1).Infof("Non-fatal error in %v at index %d: %v", logEntry.Leaf.TimestampedEntry.EntryType, index, err) return false } return true } // Processes the given entry in the specified log. func (s *Scanner) processEntry(info entryInfo, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) error { atomic.AddInt64(&s.certsProcessed, 1) switch matcher := s.opts.Matcher.(type) { case Matcher: return s.processMatcherEntry(matcher, info, foundCert, foundPrecert) case LeafMatcher: return s.processMatcherLeafEntry(matcher, info, foundCert, foundPrecert) default: return fmt.Errorf("unexpected matcher type %T", matcher) } } func (s *Scanner) processMatcherEntry(matcher Matcher, info entryInfo, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) error { rawLogEntry, err := ct.RawLogEntryFromLeaf(info.index, &info.entry) if err != nil { return fmt.Errorf("failed to build raw log entry %d: %v", info.index, err) } // Matcher instances need the parsed [pre-]certificate. logEntry, err := rawLogEntry.ToLogEntry() if s.isCertErrorFatal(err, logEntry, info.index) { return fmt.Errorf("failed to parse [pre-]certificate in MerkleTreeLeaf[%d]: %v", info.index, err) } switch { case logEntry.X509Cert != nil: if s.opts.PrecertOnly { // Only interested in precerts and this is an X.509 cert, early-out. return nil } if matcher.CertificateMatches(logEntry.X509Cert) { atomic.AddInt64(&s.certsMatched, 1) foundCert(rawLogEntry) } case logEntry.Precert != nil: if matcher.PrecertificateMatches(logEntry.Precert) { atomic.AddInt64(&s.certsMatched, 1) foundPrecert(rawLogEntry) } atomic.AddInt64(&s.precertsSeen, 1) default: return fmt.Errorf("saw unknown entry type: %v", logEntry.Leaf.TimestampedEntry.EntryType) } return nil } func (s *Scanner) processMatcherLeafEntry(matcher LeafMatcher, info entryInfo, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) error { if !matcher.Matches(&info.entry) { return nil } rawLogEntry, err := ct.RawLogEntryFromLeaf(info.index, &info.entry) if rawLogEntry == nil { return fmt.Errorf("failed to build raw log entry %d: %v", info.index, err) } switch eType := rawLogEntry.Leaf.TimestampedEntry.EntryType; eType { case ct.X509LogEntryType: if s.opts.PrecertOnly { // Only interested in precerts and this is an X.509 cert, early-out. return nil } foundCert(rawLogEntry) case ct.PrecertLogEntryType: foundPrecert(rawLogEntry) atomic.AddInt64(&s.precertsSeen, 1) default: return fmt.Errorf("saw unknown entry type: %v", eType) } return nil } // Worker function to match certs. // Accepts MatcherJobs over the entries channel, and processes them. // Returns true over the done channel when the entries channel is closed. func (s *Scanner) matcherJob(entries <-chan entryInfo, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) { for e := range entries { if err := s.processEntry(e, foundCert, foundPrecert); err != nil { atomic.AddInt64(&s.unparsableEntries, 1) klog.Errorf("Failed to parse entry at index %d: %s", e.index, err.Error()) } } } // Pretty prints the passed in duration into a human readable string. func humanTime(dur time.Duration) string { hours := int(dur / time.Hour) dur %= time.Hour minutes := int(dur / time.Minute) dur %= time.Minute seconds := int(dur / time.Second) s := "" if hours > 0 { s += fmt.Sprintf("%d hours ", hours) } if minutes > 0 { s += fmt.Sprintf("%d minutes ", minutes) } if seconds > 0 || len(s) == 0 { s += fmt.Sprintf("%d seconds ", seconds) } return s } func (s *Scanner) logThroughput(treeSize int64, stop <-chan bool) { const wndSize = 15 wnd := make([]int64, wndSize) wndTotal := int64(0) ticker := time.NewTicker(time.Second) defer ticker.Stop() for slot, filled, prevCnt := 0, 0, int64(0); ; slot = (slot + 1) % wndSize { select { case <-stop: return case <-ticker.C: certsCnt := atomic.LoadInt64(&s.certsProcessed) certsMatched := atomic.LoadInt64(&s.certsMatched) slotValue := certsCnt - prevCnt wndTotal += slotValue - wnd[slot] wnd[slot], prevCnt = slotValue, certsCnt if filled < wndSize { filled++ } throughput := float64(wndTotal) / float64(filled) remainingCerts := treeSize - int64(s.opts.StartIndex) - certsCnt remainingSeconds := int(float64(remainingCerts) / throughput) remainingString := humanTime(time.Duration(remainingSeconds) * time.Second) klog.V(1).Infof("Processed: %d certs (to index %d), matched %d (%2.2f%%). Throughput (last %ds): %3.2f ETA: %s\n", certsCnt, s.opts.StartIndex+certsCnt, certsMatched, (100.0*float64(certsMatched))/float64(certsCnt), filled, throughput, remainingString) } } } // Scan performs a scan against the Log. Blocks until the scan is complete. // // For each x509 certificate found, calls foundCert with the corresponding // LogEntry, which includes the index of the entry and the certificate. // For each precert found, calls foundPrecert with the corresponding LogEntry, // which includes the index of the entry and the precert. func (s *Scanner) Scan(ctx context.Context, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) error { _, err := s.ScanLog(ctx, foundCert, foundPrecert) return err } // ScanLog performs a scan against the Log, returning the count of scanned entries. func (s *Scanner) ScanLog(ctx context.Context, foundCert func(*ct.RawLogEntry), foundPrecert func(*ct.RawLogEntry)) (int64, error) { klog.V(1).Infof("Starting up Scanner...") s.certsProcessed = 0 s.certsMatched = 0 s.precertsSeen = 0 s.unparsableEntries = 0 s.entriesWithNonFatalErrors = 0 sth, err := s.fetcher.Prepare(ctx) if err != nil { return -1, err } startTime := time.Now() stop := make(chan bool) go s.logThroughput(int64(sth.TreeSize), stop) defer func() { stop <- true close(stop) }() // Start matcher workers. var wg sync.WaitGroup entries := make(chan entryInfo, s.opts.BufferSize) for w, cnt := 0, s.opts.NumWorkers; w < cnt; w++ { wg.Add(1) go func(idx int) { defer wg.Done() klog.V(1).Infof("Matcher %d starting", idx) s.matcherJob(entries, foundCert, foundPrecert) klog.V(1).Infof("Matcher %d finished", idx) }(w) } flatten := func(b EntryBatch) { for i, e := range b.Entries { entries <- entryInfo{index: b.Start + int64(i), entry: e} } } err = s.fetcher.Run(ctx, flatten) close(entries) // Causes matcher workers to terminate. wg.Wait() // Wait until they terminate. if err != nil { return -1, err } klog.V(1).Infof("Completed %d certs in %s", atomic.LoadInt64(&s.certsProcessed), humanTime(time.Since(startTime))) klog.V(1).Infof("Saw %d precerts", atomic.LoadInt64(&s.precertsSeen)) klog.V(1).Infof("Saw %d unparsable entries", atomic.LoadInt64(&s.unparsableEntries)) klog.V(1).Infof("Saw %d non-fatal errors", atomic.LoadInt64(&s.entriesWithNonFatalErrors)) return int64(s.fetcher.opts.EndIndex), nil } // NewScanner creates a Scanner instance using client to talk to the log, // taking configuration options from opts. func NewScanner(client LogClient, opts ScannerOptions) *Scanner { var scanner Scanner scanner.opts = opts scanner.fetcher = NewFetcher(client, &scanner.opts.FetcherOptions) // Set a default match-everything regex if none was provided. if opts.Matcher == nil { opts.Matcher = &MatchAll{} } return &scanner } google-certificate-transparency-go-2308f62/scanner/scanner_test.go000066400000000000000000000200541462611535200253120ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scanner import ( "container/list" "context" "log" "net/http" "net/http/httptest" "regexp" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/x509" ) func TestScannerMatchAll(t *testing.T) { var cert x509.Certificate m := &MatchAll{} if !m.CertificateMatches(&cert) { t.Fatal("MatchAll didn't match!") } } func TestScannerMatchNone(t *testing.T) { var cert x509.Certificate m := &MatchNone{} if m.CertificateMatches(&cert) { t.Fatal("MatchNone matched!") } } func TestScannerMatchSubjectRegexMatchesCertificateCommonName(t *testing.T) { const SubjectName = "www.example.com" const SubjectRegEx = ".*example.com" var cert x509.Certificate cert.Subject.CommonName = SubjectName m := MatchSubjectRegex{regexp.MustCompile(SubjectRegEx), nil} if !m.CertificateMatches(&cert) { t.Fatal("MatchSubjectRegex failed to match on Cert Subject CommonName") } } func TestScannerMatchSubjectRegexIgnoresDifferentCertificateCommonName(t *testing.T) { const SubjectName = "www.google.com" const SubjectRegEx = ".*example.com" var cert x509.Certificate cert.Subject.CommonName = SubjectName m := MatchSubjectRegex{regexp.MustCompile(SubjectRegEx), nil} if m.CertificateMatches(&cert) { t.Fatal("MatchSubjectRegex incorrectly matched on Cert Subject CommonName") } } func TestScannerMatchSubjectRegexIgnoresDifferentCertificateSAN(t *testing.T) { const SubjectName = "www.google.com" const SubjectRegEx = ".*example.com" var cert x509.Certificate cert.Subject.CommonName = SubjectName m := MatchSubjectRegex{regexp.MustCompile(SubjectRegEx), nil} cert.Subject.CommonName = "Wibble" // Doesn't match cert.DNSNames = append(cert.DNSNames, "Wibble") // Nor this cert.DNSNames = append(cert.DNSNames, SubjectName) if m.CertificateMatches(&cert) { t.Fatal("MatchSubjectRegex incorrectly matched on Cert SubjectAlternativeName") } } func TestScannerMatchSubjectRegexMatchesCertificateSAN(t *testing.T) { const SubjectName = "www.example.com" const SubjectRegEx = ".*example.com" var cert x509.Certificate cert.Subject.CommonName = SubjectName m := MatchSubjectRegex{regexp.MustCompile(SubjectRegEx), nil} cert.Subject.CommonName = "Wibble" // Doesn't match cert.DNSNames = append(cert.DNSNames, "Wibble") // Nor this cert.DNSNames = append(cert.DNSNames, SubjectName) if !m.CertificateMatches(&cert) { t.Fatal("MatchSubjectRegex failed to match on Cert SubjectAlternativeName") } } func TestScannerMatchSubjectRegexMatchesPrecertificateCommonName(t *testing.T) { const SubjectName = "www.example.com" const SubjectRegEx = ".*example.com" var precert ct.Precertificate precert.TBSCertificate = &x509.Certificate{} precert.TBSCertificate.Subject.CommonName = SubjectName m := MatchSubjectRegex{nil, regexp.MustCompile(SubjectRegEx)} if !m.PrecertificateMatches(&precert) { t.Fatal("MatchSubjectRegex failed to match on Precert Subject CommonName") } } func TestScannerMatchSubjectRegexIgnoresDifferentPrecertificateCommonName(t *testing.T) { const SubjectName = "www.google.com" const SubjectRegEx = ".*example.com" var precert ct.Precertificate precert.TBSCertificate = &x509.Certificate{} precert.TBSCertificate.Subject.CommonName = SubjectName m := MatchSubjectRegex{nil, regexp.MustCompile(SubjectRegEx)} if m.PrecertificateMatches(&precert) { t.Fatal("MatchSubjectRegex incorrectly matched on Precert Subject CommonName") } } func TestScannerMatchSubjectRegexIgnoresDifferentPrecertificateSAN(t *testing.T) { const SubjectName = "www.google.com" const SubjectRegEx = ".*example.com" var precert ct.Precertificate precert.TBSCertificate = &x509.Certificate{} precert.TBSCertificate.Subject.CommonName = SubjectName m := MatchSubjectRegex{nil, regexp.MustCompile(SubjectRegEx)} precert.TBSCertificate.Subject.CommonName = "Wibble" // Doesn't match precert.TBSCertificate.DNSNames = append(precert.TBSCertificate.DNSNames, "Wibble") // Nor this precert.TBSCertificate.DNSNames = append(precert.TBSCertificate.DNSNames, SubjectName) if m.PrecertificateMatches(&precert) { t.Fatal("MatchSubjectRegex incorrectly matched on Precert SubjectAlternativeName") } } func TestScannerMatchSubjectRegexMatchesPrecertificateSAN(t *testing.T) { const SubjectName = "www.example.com" const SubjectRegEx = ".*example.com" var precert ct.Precertificate precert.TBSCertificate = &x509.Certificate{} precert.TBSCertificate.Subject.CommonName = SubjectName m := MatchSubjectRegex{nil, regexp.MustCompile(SubjectRegEx)} precert.TBSCertificate.Subject.CommonName = "Wibble" // Doesn't match precert.TBSCertificate.DNSNames = append(precert.TBSCertificate.DNSNames, "Wibble") // Nor this precert.TBSCertificate.DNSNames = append(precert.TBSCertificate.DNSNames, SubjectName) if !m.PrecertificateMatches(&precert) { t.Fatal("MatchSubjectRegex failed to match on Precert SubjectAlternativeName") } } func TestScannerEndToEnd(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/ct/v1/get-sth": log.Printf("GetSTH") if _, err := w.Write([]byte(FourEntrySTH)); err != nil { t.Fatal("Failed to write get-sth response") } case "/ct/v1/get-entries": log.Printf("GetEntries %s", r.URL.RawQuery) if _, err := w.Write([]byte(FourEntries)); err != nil { t.Fatal("Failed to write get-sth response") } default: t.Fatal("Unexpected request") } })) defer ts.Close() logClient, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatal(err) } opts := ScannerOptions{ FetcherOptions: FetcherOptions{ BatchSize: 10, ParallelFetch: 1, StartIndex: 0, }, Matcher: &MatchSubjectRegex{regexp.MustCompile(`.*\.google\.com`), nil}, NumWorkers: 1, } scanner := NewScanner(logClient, opts) var matchedCerts list.List var matchedPrecerts list.List ctx := context.Background() err = scanner.Scan(ctx, func(re *ct.RawLogEntry) { // Annoyingly we can't t.Fatal() in here, as this is run in another go // routine e, _ := re.ToLogEntry() if e.X509Cert == nil { return } matchedCerts.PushBack(*e.X509Cert) }, func(re *ct.RawLogEntry) { e, _ := re.ToLogEntry() if e.X509Cert == nil { return } matchedPrecerts.PushBack(*e.Precert) }) if err != nil { t.Fatal(err) } if matchedPrecerts.Len() != 0 { t.Fatal("Found unexpected Precert") } switch matchedCerts.Len() { case 0: t.Fatal("Failed to find mail.google.com cert") case 1: if matchedCerts.Front().Value.(x509.Certificate).Subject.CommonName != "mail.google.com" { t.Fatal("Matched unexpected cert") } default: t.Fatal("Found unexpected number of certs") } } func TestDefaultScannerOptions(t *testing.T) { opts := DefaultScannerOptions() switch opts.Matcher.(type) { case *MatchAll: // great default: t.Fatalf("Default Matcher is a %T, expected MatchAll.", opts.Matcher) } if opts.PrecertOnly { t.Fatal("Expected PrecertOnly to be false.") } if opts.BatchSize < 1 { t.Fatalf("Insane BatchSize %d", opts.BatchSize) } if opts.NumWorkers < 1 { t.Fatalf("Insane NumWorkers %d", opts.NumWorkers) } if opts.ParallelFetch < 1 { t.Fatalf("Insane ParallelFetch %d", opts.ParallelFetch) } if opts.StartIndex != 0 { t.Fatalf("Expected StartIndex to be 0, but was %d", opts.StartIndex) } } google-certificate-transparency-go-2308f62/scanner/scanner_test_data.go000066400000000000000000000722431462611535200263120ustar00rootroot00000000000000// Copyright 2014 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scanner import ( "crypto/sha256" "encoding/base64" "log" ) const ( // FourEntrySTH is an STH for size 4. // TODO(alcutter): this signature is syntactically correct, but invalid. FourEntrySTH = "{" + "\"tree_size\":4,\"timestamp\":1396877652123,\"sha256_root_hash\":\"0JBu0CkZnKXc1niEndDaqqgCRHucCfVt1/WBAXs/5T8=\",\"tree_head_signature\":\"AAAACXNpZ25hdHVyZQ==\"}" // FourEntries holds 4 entries. FourEntries = "{\"entries\":[{\"leaf_input\":\"AAAAAAE9pCDoYwAAAAOGMIIDgjCCAuu" + "gAwIBAgIKFIT5BQAAAAB9PDANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29" + "vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTAeFw0xMzAyMjAxMzM0NTF" + "aFw0xMzA2MDcxOTQzMjdaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQ" + "HEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRgwFgYDVQQDEw9tYWlsLmdvb2dsZS5" + "jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOD1FbMyG0IT8JOi2El6RVciBJp4ENfTkpJ2vn/" + "HUq+gjprmUNxLSvcK+D8vBpkq8N41Qv+82PyTuZIB0pg2CJfs07C5+ZAQnwm01DiQjM/j2jKb5GegOBR" + "YngbRkAPSGCufzJy+QBWbd1htqceIREEI/JH7pUGgg90XUQgBddBbAgMBAAGjggFSMIIBTjAdBgNVHSU" + "EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFAgZmgKeyK8PXIGOAU+/5r/xNy5hMB8GA1U" + "dIwQYMBaAFL/AMOv1QxE+Z7qekfv8atrjaxIkMFsGA1UdHwRUMFIwUKBOoEyGSmh0dHA6Ly93d3cuZ3N" + "0YXRpYy5jb20vR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J" + "sMGYGCCsGAQUFBwEBBFowWDBWBggrBgEFBQcwAoZKaHR0cDovL3d3dy5nc3RhdGljLmNvbS9Hb29nbGV" + "JbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS5jcnQwDAYDVR0TAQH/BAIwADA" + "aBgNVHREEEzARgg9tYWlsLmdvb2dsZS5jb20wDQYJKoZIhvcNAQEFBQADgYEAX0lVXCymPXGdCwvn2kp" + "qJw5Q+Hf8gzGhxDG6aMlO5wj2wf8qPWABDRwHdb4mdSmRMuwhzCJhE3PceXLNf3pOlR/Prt18mDY/r6c" + "LwfldIXgTOYkw/uckGwvb0BwMsEi2FDE/T3d3SOo+lHvqPX9sOVa2uyA0wmIYnbT+5uQY6m0AAA==\"," + "\"extra_data\":\"AAXeAAK0MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgN" + "VBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F" + "0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3WjBGMQswCQYDVQQGEwJVUzE" + "TMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzA" + "NBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy" + "6rWxJfoNfNFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrbqeXUWaU" + "r/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCBoDAOBgNVHQ8BAf8EBAMCAQY" + "wHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIkMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzO" + "YkJ/UMBIGA1UdEwEB/wQIMAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnV" + "zdC5jb20vY3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHdeBZqrocb" + "6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN0rTw8Ktx5YtaiScRhKqOv5n" + "wnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfmlUUIuOss4jHg7y/j7lYe8vJD5UDIAAyQwggMgMII" + "CiaADAgECAgQ13vTPMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF" + "4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNOTgwODIyMTY" + "0MTUxWhcNMTgwODIyMTY0MTUxWjBOMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1U" + "ECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4G" + "NADCBiQKBgQDBXbFYZwhi7qCaLR8IbZEUaJgKHv7aBG8ThGIhw9F8zp8F4LgB8E407OKKlQRkrPFrU18" + "Fs8tngL9CAo7+3QEJ7OEAFE/8+/AM3UO6WyvhH4BwmRVXkxbxD5dqt8JoIxzMTVkwrFEeO68r1u5jRXv" + "F2V9Q0uNQDzqI578U/eDHuQIDAQABo4IBCTCCAQUwcAYDVR0fBGkwZzBloGOgYaRfMF0xCzAJBgNVBAY" + "TAlVTMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSB" + "BdXRob3JpdHkxDTALBgNVBAMTBENSTDEwGgYDVR0QBBMwEYEPMjAxODA4MjIxNjQxNTFaMAsGA1UdDwQ" + "EAwIBBjAfBgNVHSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHQ4EFgQUSOZo+SvSspXXR9g" + "jIBBPM5iQn9QwDAYDVR0TBAUwAwEB/zAaBgkqhkiG9n0HQQAEDTALGwVWMy4wYwMCBsAwDQYJKoZIhvc" + "NAQEFBQADgYEAWM4p6vz33rXOArkXtYXRuePglcwlMQ0AppJuf7aSY55QldGab+QR3mOFbpjuqP9ayNN" + "VsmZxV97AIes9KqcjSQEEhkJ7/O5/ohZStWdn00DbOyZYsih3Pa4Ud2HW+ipmJ6AN+qdzXOpw8ZQhZUR" + "f+vzvKWipood573nvT6wHdzg=\"},{\"leaf_input\":\"AAAAAAE9pe0GcwAAAATWMIIE0jCCA7qgA" + "wIBAgIDAPY6MA0GCSqGSIb3DQEBBQUAMEAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5HZW9UcnVzdCwgS" + "W5jLjEYMBYGA1UEAxMPR2VvVHJ1c3QgU1NMIENBMB4XDTExMTAyMTExMDUwNloXDTEzMTEyMjA0MzI0N" + "1owgc4xKTAnBgNVBAUTIFRqbGZoUTB0cXp3WmtNa0svNXFNdGZqbjJ6aWRVNzRoMQswCQYDVQQGEwJVU" + "zEXMBUGA1UECBMOU291dGggQ2Fyb2xpbmExEzARBgNVBAcTCkNoYXJsZXN0b24xFzAVBgNVBAoTDkJsY" + "WNrYmF1ZCBJbmMuMRAwDgYDVQQLEwdIb3N0aW5nMTswOQYDVQQDEzJ3d3cuc3RydWxlYXJ0c2NlbnRyZ" + "S5wdXJjaGFzZS10aWNrZXRzLW9ubGluZS5jby51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCg" + "gEBAJtkbcF8x3TtIARHC8BDRtoIAdh9HO9fo+5UUDtoc8f4xq7Rb2xbWOiEX29JqZOdsuucYTuYbbDf0" + "uBYcJpkwhEg4Vg5skyfp0jAd6pXm1euQ+RiRShzEQYKJ8y4/IjZHttA/8HSzEKWJnuidsYrl/twFhlX5" + "WIZq3BUVQ9GVqGe9n1r2eIFTs6FxYUpaVzTkc6OLh1qSz+cnDDPigLUoUOK/KqN7ybmJxSefJw9WpFW/" + "pIn6M0gFAbu0egFgDybQ3JwUAEh8ddzpKRCqGq1mdZAKpKFHcqmi5nG5aFD4p1NFmPjDVQXohXLQvwtm" + "wwKS2Zo+tnulPnEe9jjET/f+MUCAwEAAaOCAUQwggFAMB8GA1UdIwQYMBaAFEJ5VBthzVUrPmPVPEhX9" + "Z/7Rc5KMA4GA1UdDwEB/wQEAwIEsDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0RB" + "DYwNIIyd3d3LnN0cnVsZWFydHNjZW50cmUucHVyY2hhc2UtdGlja2V0cy1vbmxpbmUuY28udWswPQYDV" + "R0fBDYwNDAyoDCgLoYsaHR0cDovL2d0c3NsLWNybC5nZW90cnVzdC5jb20vY3Jscy9ndHNzbC5jcmwwH" + "QYDVR0OBBYEFDIdT1lJ84lcDpGuBOuAXrP0AlBVMAwGA1UdEwEB/wQCMAAwQwYIKwYBBQUHAQEENzA1M" + "DMGCCsGAQUFBzAChidodHRwOi8vZ3Rzc2wtYWlhLmdlb3RydXN0LmNvbS9ndHNzbC5jcnQwDQYJKoZIh" + "vcNAQEFBQADggEBAFhFfVTB5NWG3rVaq1jM72uGneGCjGk4qV4uKtEFn+zTJe9W2N/u8V2+mLvWQfDGP" + "r8X5u8KzBOQ+fl6aRxvI71EM3kjMu6UuJkUwXsoocK1c/iVBwWSpqem20t/2Z2n5oIN54QsKZX6tQd9J" + "HQ95YwtlyC7H4VeDKtJZ5x9UhJi8v35C+UgYPmiU5PdeoTdwxCf285FoQL9fBAPbv+EGek1XVaVg2yJK" + "ptG2OeM8AaynHsFcK/OcZJtsiGhtu2s9F910OBpoU+lhnPylwxOf4k35JcLaqHJ3BbLUtybbduNqtf3+" + "sYhkvp5IcCypoJy/Rk4fHgD8VTNiNWj7KGuHRYAAA==\",\"extra_data\":\"AAqLAAPdMIID2TCCA" + "sGgAwIBAgIDAjbQMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzd" + "CBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMTAwMjE5MjIzOTI2WhcNMjAwMjE4M" + "jIzOTI2WjBAMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xGDAWBgNVBAMTD0dlb" + "1RydXN0IFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCzgMHk5UatcGA9uuUU3" + "Z6KXot1WubKbUGlI+g5hSZ6p1V3mkihkn46HhrxJ6ujTDnMyz1Hr4GuFmpcN+9FQf37mpc8oEOdxt8XI" + "dGKolbCA0mEEoE+yQpUYGa5jFTk+eb5lPHgX3UR8im55IaisYmtph6DKWOy8FQchQt65+EuDa+kvc3ns" + "VrXjAVaDktzKIt1XTTYdwvhdGLicTBi2LyKBeUxY0pUiWozeKdOVSQdl+8a5BLGDzAYtDRN4dgjOyFbL" + "TAZJQ5096QhS6CkIMlszZhWwPKoXz4mdaAN+DaIiixafWcwqQ/RmXAueOFRJq9VeiS+jDkNd53eAsMMv" + "R8CAwEAAaOB2TCB1jAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEJ5VBthzVUrPmPVPEhX9Z/7Rc5KM" + "B8GA1UdIwQYMBaAFMB6mGiNifurBWQMEX2qfWW4ysxOMBIGA1UdEwEB/wQIMAYBAf8CAQAwOgYDVR0fB" + "DMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBB" + "QUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20wDQYJKoZIhvcNAQEFB" + "QADggEBANTvU4ToGr2hiwTAqfVfoRB4RV2yV2pOJMtlTjGXkZrUJPjiJ2ZwMZzBYlQG55cdOprApClIC" + "q8kx6jEmlTBfEx4TCtoLF0XplR4TEbigMMfOHES0tdT41SFULgCy+5jOvhWiU1Vuy7AyBh3hjELC3Dwf" + "jWDpCoTZFZnNF0WX3OsewYk2k9QbSqr0E1TQcKOu3EDSSmGGM8hQkx0YlEVxW+o78Qn5Rsz3VqI138S0" + "adhJR/V4NwdzxoQ2KDLX4z6DOW/cf/lXUQdpj6HR/oaToODEj+IZpWYeZqF6wJHzSXj8gYETpnKXKBue" + "rvdo5AaRTPvvz7SBMS24CqFZUE+ENQAA4EwggN9MIIC5qADAgECAgMSu+YwDQYJKoZIhvcNAQEFBQAwT" + "jELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlc" + "nRpZmljYXRlIEF1dGhvcml0eTAeFw0wMjA1MjEwNDAwMDBaFw0xODA4MjEwNDAwMDBaMEIxCzAJBgNVB" + "AYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0Ewg" + "gEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDazBhjMP30FyMaVn5b3zxsOORxt3iR1Lyh2Ez4q" + "EO2A+lNIQcIiNpYL2Y5Kb0FeIudOOgFt2p+caTmxGCmsO+A5IkoD54l1u2D862mkceYyUIYNRSdrZhGk" + "i5PyvGHQ8EWlVctUO+JLYB6V63y7l9r0gCNuRT4FBU12cBGo3tyyJG/yVUrzdCXPpwmZMzfzoMZccpO5" + "tTVe6kZzVXeyOzSXjhT5VxPjC3+UCM2/Gbmy46kORkAt5UCOZELDv44LtEdBZr2TT5vDwcdrywej2A54" + "vo2UxM51F4mK9s9qBS9MusYAyhSBHHlqzM94Ti7BzaEYpx56hYw9F/AK+hxa+T5AgMBAAGjgfAwge0wH" + "wYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFMB6mGiNifurBWQMEX2qfWW4y" + "sxOMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6L" + "y9jcmwuZ2VvdHJ1c3QuY29tL2NybHMvc2VjdXJlY2EuY3JsME4GA1UdIARHMEUwQwYEVR0gADA7MDkGC" + "CsGAQUFBwIBFi1odHRwczovL3d3dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL3JlcG9zaXRvcnkwDQYJK" + "oZIhvcNAQEFBQADgYEAduESbk5LFhKGMAaygQjP8AjHx3F+Zu7C7dQ7H//w8MhO1kM4sLkwfRjQVYOia" + "ss2EZzoSGajbX+4E9RH/otaXHP8rtkbMhk4q5c0FKqW0uujHBQISba75ZHvgzbrHVZvytq8c2OQ5H97P" + "iLLPQftXzh0nOMDUE6hr5juYfKEPxIAAyQwggMgMIICiaADAgECAgQ13vTPMA0GCSqGSIb3DQEBBQUAM" + "E4xCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZ" + "XJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNOTgwODIyMTY0MTUxWhcNMTgwODIyMTY0MTUxWjBOMQswCQYDV" + "QQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhd" + "GUgQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBXbFYZwhi7qCaLR8IbZEUaJgKH" + "v7aBG8ThGIhw9F8zp8F4LgB8E407OKKlQRkrPFrU18Fs8tngL9CAo7+3QEJ7OEAFE/8+/AM3UO6WyvhH" + "4BwmRVXkxbxD5dqt8JoIxzMTVkwrFEeO68r1u5jRXvF2V9Q0uNQDzqI578U/eDHuQIDAQABo4IBCTCCA" + "QUwcAYDVR0fBGkwZzBloGOgYaRfMF0xCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDV" + "QQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEwGgYDV" + "R0QBBMwEYEPMjAxODA4MjIxNjQxNTFaMAsGA1UdDwQEAwIBBjAfBgNVHSMEGDAWgBRI5mj5K9KylddH2" + "CMgEE8zmJCf1DAdBgNVHQ4EFgQUSOZo+SvSspXXR9gjIBBPM5iQn9QwDAYDVR0TBAUwAwEB/zAaBgkqh" + "kiG9n0HQQAEDTALGwVWMy4wYwMCBsAwDQYJKoZIhvcNAQEFBQADgYEAWM4p6vz33rXOArkXtYXRuePgl" + "cwlMQ0AppJuf7aSY55QldGab+QR3mOFbpjuqP9ayNNVsmZxV97AIes9KqcjSQEEhkJ7/O5/ohZStWdn0" + "0DbOyZYsih3Pa4Ud2HW+ipmJ6AN+qdzXOpw8ZQhZURf+vzvKWipood573nvT6wHdzg=\"},{\"leaf_i" + "nput\":\"AAAAAAE9pe0GcwAAAATjMIIE3zCCA8egAwIBAgIUCimKXmNJ+wiDS2zJvg6LC2cvrvQwDQY" + "JKoZIhvcNAQEFBQAwWjELMAkGA1UEBhMCSlAxIzAhBgNVBAoMGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCB" + "MdGQuMSYwJAYDVQQDDB1DeWJlcnRydXN0IEphcGFuIFB1YmxpYyBDQSBHMjAeFw0xMjAzMTkwMzE0MzN" + "aFw0xNTAzMzExNDU5MDBaMIGKMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEDAOBgNVBAcTB0N" + "odW8ta3UxHjAcBgNVBAoTFU5ldCBEcmVhbWVycyBDby4sTHRkLjEeMBwGA1UECxMVTWVnYSBNZWRpYSB" + "EZXBhcnRtZW50MRkwFwYDVQQDExB3d3cubmV0a2VpYmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8" + "AMIIBCgKCAQEA2to03F4GdlRiGljXrSmT08/WrY59UWaoe/H4wQN6S5eQKVtaLjBWUF5Ro4sm/kND7au" + "fyDqXUePxiZkphupV+VO7PeKp9e5yqEijK4z2XoFQhrCH5kkn1GDrTNzonxyAQtiBJ/k6gVTJV5fn4s7" + "I6bZ2aXiJLIlTCFwMDNkrB3fj9py86WwymXaypSHkmo9Sx6PFiIOwPH6vXRK4UyAfFpXPiLGJENEWOY2" + "AtzMJiIoupgAuyvmoY0G0Vk34mA9gOIOrKE2QmVSR3AtA31UpNZ33qvimfz96rHtCeiZj5HNxZRBMGBs" + "HTlu5e49xypiYCCV41jQvmfZOShan3R3o2QIDAQABo4IBajCCAWYwCQYDVR0TBAIwADCBuAYDVR0gBIG" + "wMIGtMIGqBggqgwiMmxEBATCBnTBXBggrBgEFBQcCAjBLGklGb3IgbW9yZSBkZXRhaWxzLCBwbGVhc2U" + "gdmlzaXQgb3VyIHdlYnNpdGUgaHR0cHM6Ly93d3cuY3liZXJ0cnVzdC5uZS5qcCAuMEIGCCsGAQUFBwI" + "BFjZodHRwczovL3d3dy5jeWJlcnRydXN0Lm5lLmpwL3NzbC9yZXBvc2l0b3J5L2luZGV4Lmh0bWwwGwY" + "DVR0RBBQwEoIQd3d3Lm5ldGtlaWJhLmNvbTALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwE" + "GCCsGAQUFBwMCMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly9zdXJlc2VyaWVzLWNybC5jeWJlcnRydXN" + "0Lm5lLmpwL1N1cmVTZXJ2ZXIvY3RqcHViY2FnMi9jZHAuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAw8sX" + "P2ecKp5QGXtzcxKwkkznqocaddzoG69atcyzwshySLfo0ElMHP5WG9TpVrb6XSh2a1edwduAWBVAHQsH" + "i4bt4wX9e9DBMnQx/jelcJevABQsXJPGc86diisXYDkHKQesi+8CvWvE0GmbVJRoq0RDo14WASQszuqT" + "NW993walCzNTg88s7MniFgmgFd8n31SVls6QhY2Fmlr13JLDtzVDQDbj6MCPuwG8DdmR1bCM/ugcnk0a" + "7ZVy3d4yTjdhKpocToFklhHtHg0AINghPXIqU0njjUsy3ujNYIYo1TaZ3835Bo0lDwdvKK68Jka24Cfc" + "m+vfUfHKB56sIzquxAAA=\",\"extra_data\":\"AArbAAQ4MIIENDCCAxygAwIBAgIEBydcJjANBgk" + "qhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJ" + "lclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTExMDgxODE4MzYzM1o" + "XDTE4MDgwOTE4MzU0OVowWjELMAkGA1UEBhMCSlAxIzAhBgNVBAoMGkN5YmVydHJ1c3QgSmFwYW4gQ28" + "uLCBMdGQuMSYwJAYDVQQDDB1DeWJlcnRydXN0IEphcGFuIFB1YmxpYyBDQSBHMjCCASIwDQYJKoZIhvc" + "NAQEBBQADggEPADCCAQoCggEBALbcdvu5RPsSfFSwu0F1dPA1R54nukNERWAZzUQKsnjl+h4kOwIfaHd" + "g9OsiBQo3btv3FSC7PVPU0BGO1OtnvtjdBTeUQSUj75oQo8P3AL26JpJngVCpT56RPE4gulJ//0xNjqq" + "tTl+8J5cCKf2Vg0m/CrqxNRg1qXOIYlGsFBc0UOefxvOTXbnFAE83kHqBD9T1cinojGKscTvzLt8qXOm" + "+51Ykgiiavz39cUL9xXtrNwlHUD5ykao7xU+dEm49gANUSUEVPPKGRHQo9bmjG9t2x+oDiaBg6VH2oWQ" + "+dJvbKssYPMHnaBiJ7Ks4LlC5b24VMygdL9WAF4Yi8x0M4IcCAwEAAaOCAQAwgf0wEgYDVR0TAQH/BAg" + "wBgEB/wIBADBTBgNVHSAETDBKMEgGCSsGAQQBsT4BADA7MDkGCCsGAQUFBwIBFi1odHRwOi8vY3liZXJ" + "0cnVzdC5vbW5pcm9vdC5jb20vcmVwb3NpdG9yeS5jZm0wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBa" + "AFOWdWTCCR1jMrPoIVDaGezq1BE3wMEIGA1UdHwQ7MDkwN6A1oDOGMWh0dHA6Ly9jZHAxLnB1YmxpYy1" + "0cnVzdC5jb20vQ1JML09tbmlyb290MjAyNS5jcmwwHQYDVR0OBBYEFBvkje86cWsSZWjPtpG8OUMBjXX" + "JMA0GCSqGSIb3DQEBBQUAA4IBAQBtK+3pj7Yp1rYwuuZttcNT0sm4Ck5In/E/Oiq0+3SW5r0YvKd5wHj" + "BObog406A0iTVpXt/YqPa1A8NqZ2qxem8CMlIZpiewPneq23lsDPCcNCW1x5vmAQVY0i7moVdG2nztE/" + "zpnAWDyEZf62wAzlJhoyic06T3CEBaLDvDXAaeqKyzCJCkVS9rHAEjUxc/Dqikvb5KhJAzXa3ZvTX0qv" + "ejizZ3Qk1NydWC662rpqDYPBff/Ctsxz6uHRfx+zADq3Yw8+f0jAOXFEfPhniwdKpkA/mV7mvBHai8gg" + "EJQo1u3MEMdCYRn82wWEWo4qMmd4QBfLe7aUJZJeEj0KoeyLEAAQ8MIIEODCCA6GgAwIBAgIEBydtuTA" + "NBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQY" + "DVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN" + "0IEdsb2JhbCBSb290MB4XDTEwMTEzMDE2MzUyMVoXDTE4MDgxMDE1MzQyNlowWjELMAkGA1UEBhMCSUU" + "xEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3J" + "lIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZ" + "ymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi" + "1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dn" + "KM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xX" + "tabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqd" + "NkNwnGjkCAwEAAaOCAWowggFmMBIGA1UdEwEB/wQIMAYBAf8CAQMwTgYDVR0gBEcwRTBDBgRVHSAAMDs" + "wOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0b3J5LmNmbTA" + "OBgNVHQ8BAf8EBAMCAQYwgYkGA1UdIwSBgTB/oXmkdzB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1R" + "FIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgN" + "VBAMTGkdURSBDeWJlclRydXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8" + "vd3d3LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3JsMB0GA1UdDgQWBBTlnVk" + "wgkdYzKz6CFQ2hns6tQRN8DANBgkqhkiG9w0BAQUFAAOBgQAWtCzJ8V7honubeCB6SnBwhhkAtwUq6Mk" + "lOQ/DZDx1CdmJFYAHwo28KaVkUM9xdUcjvU3Yf3eaURBuTh8gPEecQ3R/loQQTBNDvvjgci7/v648CgN" + "ggktv+ZrFHvavkDufYTs+3psFGsYsPFchCA9U+ihjbOgbnA/P3TBEE7lX/gACXjCCAlowggHDAgIBpTA" + "NBgkqhkiG9w0BAQQFADB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQY" + "DVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN" + "0IEdsb2JhbCBSb290MB4XDTk4MDgxMzAwMjkwMFoXDTE4MDgxMzIzNTkwMFowdTELMAkGA1UEBhMCVVM" + "xGDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEnMCUGA1UECxMeR1RFIEN5YmVyVHJ1c3QgU29sdXRpb25" + "zLCBJbmMuMSMwIQYDVQQDExpHVEUgQ3liZXJUcnVzdCBHbG9iYWwgUm9vdDCBnzANBgkqhkiG9w0BAQE" + "FAAOBjQAwgYkCgYEAlQ+gtvBQnOh6x4jN3RcOLrCU0Bs9DvaUwIqUxwbIkJfIuGQaen5sPFPhNyhzYH+" + "yl1MHn1P5bViU0q+NbYhngObtspXPcjHKpRxyulwC52RC5/mpLNY6DayNQqokATnmnD8BhVcNWIdF+NO" + "FqpNpJoVwSIA/EhXHebQfBS87YpkCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBt6xsJ6V7ZUdtnImGkKjx" + "Id+OgfKbec6IUA4U9+6sOMMWDFjOBEwieezRO30DIdNe5fdz0dlV9m2NUGOnw6vNcsdmLQh65wJVOuvr" + "V4nz1aGG/juwFl19bsNejhTTEJKcND5WT78uU2J4fnVyFbceqrk8fIrXNla26p8z5qwt6fw==\"},{\"" + "leaf_input\":\"AAAAAAE9pe0GdAAAAAWmMIIFojCCBIqgAwIBAgISESE1Pz3s7WxTnxbUXmwjh7QhM" + "A0GCSqGSIb3DQEBBQUAMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wL" + "QYDVQQDEyZHbG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBHMjAeFw0xMTEwMTAxNDE2M" + "zdaFw0xMzEwMTAxNDE2MzdaMIHpMR0wGwYDVQQPDBRQcml2YXRlIE9yZ2FuaXphdGlvbjERMA8GA1UEB" + "RMIMDIzOTczNzMxEzARBgsrBgEEAYI3PAIBAxMCR0IxCzAJBgNVBAYTAkdCMRQwEgYDVQQIEwtPeGZvc" + "mRzaGlyZTEPMA0GA1UEBxMGT3hmb3JkMRgwFgYDVQQJEw9CZWF1bW9udCBTdHJlZXQxCzAJBgNVBAsTA" + "klUMSMwIQYDVQQKExpUaGUgT3hmb3JkIFBsYXlob3VzZSBUcnVzdDEgMB4GA1UEAxMXd3d3Lm94Zm9yZ" + "HBsYXlob3VzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2VgUJx+QIlEn4vMq5Y" + "ajmJEk1Lv5Kwc95oqEb2EbQMVhCJct0OA0wKJbnFGaNIo5DJHIouuz98JoHixMB54EwZi5I64wvqyq1o" + "hquTrUk4CS/4Y4odDw61dIqE2UZCxJYui9y4fTkptjNWmTaytw3LpGkt4Yx+AIcB+Oc7c7IPjTZEvR6L" + "5lK9WqfZmrS/Y+Tgflz6W79rpgUb2CyfqLUX0Hxohw5/Zp197y4XhOwou/f+Vaju3j/Gt1WBAbWrKxpK" + "AROVesfqT/H7Y/iOJ6jkPt5rqrLosStbGMpPUNNGRY0a8F1HBAUUzjTrRAE6CGZAPgBbcloYFc1zUsxP" + "LcZAgMBAAGjggHRMIIBzTAOBgNVHQ8BAf8EBAMCBaAwTAYDVR0gBEUwQzBBBgkrBgEEAaAyAQEwNDAyB" + "ggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wNwYDVR0RBDAwL" + "oIXd3d3Lm94Zm9yZHBsYXlob3VzZS5jb22CE294Zm9yZHBsYXlob3VzZS5jb20wCQYDVR0TBAIwADAdB" + "gNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC5nb" + "G9iYWxzaWduLmNvbS9ncy9nc2V4dGVuZHZhbGcyLmNybDCBiAYIKwYBBQUHAQEEfDB6MEEGCCsGAQUFB" + "zAChjVodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2V4dGVuZHZhbGcyLmNydDA1B" + "ggrBgEFBQcwAYYpaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzZXh0ZW5kdmFsZzIwHQYDVR0OB" + "BYEFNp+MVYdHILBfTE6JM8O6Ul+Xwx3MB8GA1UdIwQYMBaAFLCwSv0cdSj4HGGqE/b6wZA9axajMA0GC" + "SqGSIb3DQEBBQUAA4IBAQALHuvJlSvi3OqKwDiXBrsx0zb7DGGLAzwQCyr60iwJuc1S8SkWURlM0CKIq" + "0Qupj5vYIAY2g6gDWxdf/JFMh/Rxzv90JE/xZm9YlnMh2Evz3glLLQ5y2x1ddc0RU9YFoeOmJcgDOROI" + "8aQvhcn9Jdj1Yk7BkKhbQv/pM9ETqtSro3Xbv/qcwPTG/oRysMCrN/DUxedUr95dFjrS3zpo+6Hr7Jab" + "TcaAak40ksY+vHEQWbqm4YluJ4/c+6qfpsTTUih6//7xs92UxObeSMtWPaxySxedXekTPYrGt5X8XXPY" + "oTKJnuJrxlkEBv0K7wozbn5Km2dpOqCAaqbf8WKa3mvAAA=\",\"extra_data\":\"AAgjAARfMIIEW" + "zCCA0OgAwIBAgILBAAAAAABL07hW2MwDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xvYmFsU2lnb" + "iBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNM" + "TEwNDEzMTAwMDAwWhcNMjIwNDEzMTAwMDAwWjBZMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU" + "2lnbiBudi1zYTEvMC0GA1UEAxMmR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIC0gRzIwg" + "gEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNoUbMUpq4pbR/WNnN2EugcgyXW6aIIMO5PUbc0" + "FxSMPb6WU+FX7DbiLSpXysjSKyr9ZJ4FLYyD/tcaoVbAJDgu2X1WvlPZ37HbCnsk8ArysRe2LDb1r4/m" + "wvAj6ldrvcAAqT8umYROHf+IyAlVRDFvYK5TLFoxuJwe4NcE2fBofN8C6iZmtDimyUxyCuNQPZSY7Ggr" + "Vou9Xk2bTUsDt0F5NDiB0i3KF4r1VjVbNAMoQFGAVqPxq9kx1UBXeHRxmxQJaAFrQCrDI1la93rwnJUy" + "Q88ABeHIu/buYZ4FlGud9mmKE3zWI2DZ7k0JZscUYBR84OSaqOuR5rW5IsbwO2xAgMBAAGjggEvMIIBK" + "zAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUsLBK/Rx1KPgcYaoT9" + "vrBkD1rFqMwRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jhb" + "HNpZ24uY29tL3JlcG9zaXRvcnkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnb" + "i5uZXQvcm9vdC1yMi5jcmwwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5nb" + "G9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuM" + "A0GCSqGSIb3DQEBBQUAA4IBAQBfKJAMLekgsjB8iKtABfqxnVwik9WdyjUx+izqHZNZGcSgDfsJQDHaZ" + "FbNUr7nGGbobQmbstuUPu42RR4kVLYgBZO1MRq4ZFfm0ywBTDmWef63BJgS77cuWnf+R/N5mELdFr5ba" + "SvJJsgpaHfmrPZOkBMoZwTsciUf16cKUH84DnIYsSm4/66h1FS4Zk2g1c/T76kyKsWXYtKEzLCg2Jipy" + "jjkzEQ1b2EmsC6Ycvk4Mg20oWIKIWIV3rttkxA2UztKIXvC9b4u9gIT6a5McOkq9h/Di+Wf4I0qKOgZL" + "LNl3ffxb5c1ntuSNWOB1yfkK2Kq+mKhcZKMCha3PbVKZVsCAAO+MIIDujCCAqKgAwIBAgILBAAAAAABD" + "4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARB" + "gNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExM" + "jE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvY" + "mFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBA" + "KbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isU" + "oh7SqbKSaZeqKeMWhG8eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfX" + "klqtTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc" + "5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgABy" + "Ur6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/" + "zAdBgNVHQ4EFgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2Nyb" + "C5nbG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGL" + "jANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp9" + "Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiP" + "qFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMN" + "YxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7TBj0/VLZjmmx6" + "BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==\"}]}" // Entry0 is first entry. Entry0 = "AAAAAAE9pCDoYwAAAAOGMIIDgjCCAuugAwIBAgIKFIT5BQAA" + "AAB9PDANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAG" + "A1UEAxMZR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTAeFw0xMzAyMjAxMzM0NTFaFw0xMzA2MDcxOTQz" + "MjdaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW" + "aWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRgwFgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wgZ8wDQYJKoZI" + "hvcNAQEBBQADgY0AMIGJAoGBAOD1FbMyG0IT8JOi2El6RVciBJp4ENfTkpJ2vn/HUq+gjprmUNxLSvcK" + "+D8vBpkq8N41Qv+82PyTuZIB0pg2CJfs07C5+ZAQnwm01DiQjM/j2jKb5GegOBRYngbRkAPSGCufzJy+" + "QBWbd1htqceIREEI/JH7pUGgg90XUQgBddBbAgMBAAGjggFSMIIBTjAdBgNVHSUEFjAUBggrBgEFBQcD" + "AQYIKwYBBQUHAwIwHQYDVR0OBBYEFAgZmgKeyK8PXIGOAU+/5r/xNy5hMB8GA1UdIwQYMBaAFL/AMOv1" + "QxE+Z7qekfv8atrjaxIkMFsGA1UdHwRUMFIwUKBOoEyGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29v" + "Z2xlSW50ZXJuZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3JsMGYGCCsGAQUFBwEB" + "BFowWDBWBggrBgEFBQcwAoZKaHR0cDovL3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhv" + "cml0eS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS5jcnQwDAYDVR0TAQH/BAIwADAaBgNVHREEEzARgg9t" + "YWlsLmdvb2dsZS5jb20wDQYJKoZIhvcNAQEFBQADgYEAX0lVXCymPXGdCwvn2kpqJw5Q+Hf8gzGhxDG6" + "aMlO5wj2wf8qPWABDRwHdb4mdSmRMuwhzCJhE3PceXLNf3pOlR/Prt18mDY/r6cLwfldIXgTOYkw/uck" + "Gwvb0BwMsEi2FDE/T3d3SOo+lHvqPX9sOVa2uyA0wmIYnbT+5uQY6m0AAA==" // Entry1 is second entry. Entry1 = "AAAAAAE9pe0GcwAAAATWMIIE0jCCA7qgAwIBAgIDAPY6MA0GCS" + "qGSIb3DQEBBQUAMEAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5HZW9UcnVzdCwgSW5jLjEYMBYGA1UEAx" + "MPR2VvVHJ1c3QgU1NMIENBMB4XDTExMTAyMTExMDUwNloXDTEzMTEyMjA0MzI0N1owgc4xKTAnBgNVBA" + "UTIFRqbGZoUTB0cXp3WmtNa0svNXFNdGZqbjJ6aWRVNzRoMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOU2" + "91dGggQ2Fyb2xpbmExEzARBgNVBAcTCkNoYXJsZXN0b24xFzAVBgNVBAoTDkJsYWNrYmF1ZCBJbmMuMR" + "AwDgYDVQQLEwdIb3N0aW5nMTswOQYDVQQDEzJ3d3cuc3RydWxlYXJ0c2NlbnRyZS5wdXJjaGFzZS10aW" + "NrZXRzLW9ubGluZS5jby51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJtkbcF8x3TtIA" + "RHC8BDRtoIAdh9HO9fo+5UUDtoc8f4xq7Rb2xbWOiEX29JqZOdsuucYTuYbbDf0uBYcJpkwhEg4Vg5sk" + "yfp0jAd6pXm1euQ+RiRShzEQYKJ8y4/IjZHttA/8HSzEKWJnuidsYrl/twFhlX5WIZq3BUVQ9GVqGe9n" + "1r2eIFTs6FxYUpaVzTkc6OLh1qSz+cnDDPigLUoUOK/KqN7ybmJxSefJw9WpFW/pIn6M0gFAbu0egFgD" + "ybQ3JwUAEh8ddzpKRCqGq1mdZAKpKFHcqmi5nG5aFD4p1NFmPjDVQXohXLQvwtmwwKS2Zo+tnulPnEe9" + "jjET/f+MUCAwEAAaOCAUQwggFAMB8GA1UdIwQYMBaAFEJ5VBthzVUrPmPVPEhX9Z/7Rc5KMA4GA1UdDw" + "EB/wQEAwIEsDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0RBDYwNIIyd3d3LnN0cn" + "VsZWFydHNjZW50cmUucHVyY2hhc2UtdGlja2V0cy1vbmxpbmUuY28udWswPQYDVR0fBDYwNDAyoDCgLo" + "YsaHR0cDovL2d0c3NsLWNybC5nZW90cnVzdC5jb20vY3Jscy9ndHNzbC5jcmwwHQYDVR0OBBYEFDIdT1" + "lJ84lcDpGuBOuAXrP0AlBVMAwGA1UdEwEB/wQCMAAwQwYIKwYBBQUHAQEENzA1MDMGCCsGAQUFBzAChi" + "dodHRwOi8vZ3Rzc2wtYWlhLmdlb3RydXN0LmNvbS9ndHNzbC5jcnQwDQYJKoZIhvcNAQEFBQADggEBAF" + "hFfVTB5NWG3rVaq1jM72uGneGCjGk4qV4uKtEFn+zTJe9W2N/u8V2+mLvWQfDGPr8X5u8KzBOQ+fl6aR" + "xvI71EM3kjMu6UuJkUwXsoocK1c/iVBwWSpqem20t/2Z2n5oIN54QsKZX6tQd9JHQ95YwtlyC7H4VeDK" + "tJZ5x9UhJi8v35C+UgYPmiU5PdeoTdwxCf285FoQL9fBAPbv+EGek1XVaVg2yJKptG2OeM8AaynHsFcK" + "/OcZJtsiGhtu2s9F910OBpoU+lhnPylwxOf4k35JcLaqHJ3BbLUtybbduNqtf3+sYhkvp5IcCypoJy/R" + "k4fHgD8VTNiNWj7KGuHRYAAA==" // Entry2 is third entry. Entry2 = "AAAAAAE9pe0GcwAAAATjMIIE3zCCA8egAwIBAgIUCimKXmNJ+wiDS2zJvg6LC2cvr" + "vQwDQYJKoZIhvcNAQEFBQAwWjELMAkGA1UEBhMCSlAxIzAhBgNVBAoMGkN5YmVydHJ1c3QgSmFwYW4gQ" + "28uLCBMdGQuMSYwJAYDVQQDDB1DeWJlcnRydXN0IEphcGFuIFB1YmxpYyBDQSBHMjAeFw0xMjAzMTkwM" + "zE0MzNaFw0xNTAzMzExNDU5MDBaMIGKMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEDAOBgNVB" + "AcTB0NodW8ta3UxHjAcBgNVBAoTFU5ldCBEcmVhbWVycyBDby4sTHRkLjEeMBwGA1UECxMVTWVnYSBNZ" + "WRpYSBEZXBhcnRtZW50MRkwFwYDVQQDExB3d3cubmV0a2VpYmEuY29tMIIBIjANBgkqhkiG9w0BAQEFA" + "AOCAQ8AMIIBCgKCAQEA2to03F4GdlRiGljXrSmT08/WrY59UWaoe/H4wQN6S5eQKVtaLjBWUF5Ro4sm/" + "kND7aufyDqXUePxiZkphupV+VO7PeKp9e5yqEijK4z2XoFQhrCH5kkn1GDrTNzonxyAQtiBJ/k6gVTJV" + "5fn4s7I6bZ2aXiJLIlTCFwMDNkrB3fj9py86WwymXaypSHkmo9Sx6PFiIOwPH6vXRK4UyAfFpXPiLGJE" + "NEWOY2AtzMJiIoupgAuyvmoY0G0Vk34mA9gOIOrKE2QmVSR3AtA31UpNZ33qvimfz96rHtCeiZj5HNxZ" + "RBMGBsHTlu5e49xypiYCCV41jQvmfZOShan3R3o2QIDAQABo4IBajCCAWYwCQYDVR0TBAIwADCBuAYDV" + "R0gBIGwMIGtMIGqBggqgwiMmxEBATCBnTBXBggrBgEFBQcCAjBLGklGb3IgbW9yZSBkZXRhaWxzLCBwb" + "GVhc2UgdmlzaXQgb3VyIHdlYnNpdGUgaHR0cHM6Ly93d3cuY3liZXJ0cnVzdC5uZS5qcCAuMEIGCCsGA" + "QUFBwIBFjZodHRwczovL3d3dy5jeWJlcnRydXN0Lm5lLmpwL3NzbC9yZXBvc2l0b3J5L2luZGV4Lmh0b" + "WwwGwYDVR0RBBQwEoIQd3d3Lm5ldGtlaWJhLmNvbTALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBB" + "QUHAwEGCCsGAQUFBwMCMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly9zdXJlc2VyaWVzLWNybC5jeWJlc" + "nRydXN0Lm5lLmpwL1N1cmVTZXJ2ZXIvY3RqcHViY2FnMi9jZHAuY3JsMA0GCSqGSIb3DQEBBQUAA4IBA" + "QAw8sXP2ecKp5QGXtzcxKwkkznqocaddzoG69atcyzwshySLfo0ElMHP5WG9TpVrb6XSh2a1edwduAWB" + "VAHQsHi4bt4wX9e9DBMnQx/jelcJevABQsXJPGc86diisXYDkHKQesi+8CvWvE0GmbVJRoq0RDo14WAS" + "QszuqTNW993walCzNTg88s7MniFgmgFd8n31SVls6QhY2Fmlr13JLDtzVDQDbj6MCPuwG8DdmR1bCM/u" + "gcnk0a7ZVy3d4yTjdhKpocToFklhHtHg0AINghPXIqU0njjUsy3ujNYIYo1TaZ3835Bo0lDwdvKK68Jk" + "a24Cfcm+vfUfHKB56sIzquxAAA=" // Entry3 is fourth entry. Entry3 = "AAAAAAE9pe0GdAAAAAWmMIIFojCCBIqgAwIBAgISESE1Pz3s7WxTnxbUXmwjh7Q" + "hMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8" + "wLQYDVQQDEyZHbG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBHMjAeFw0xMTEwMTAxNDE" + "2MzdaFw0xMzEwMTAxNDE2MzdaMIHpMR0wGwYDVQQPDBRQcml2YXRlIE9yZ2FuaXphdGlvbjERMA8GA1U" + "EBRMIMDIzOTczNzMxEzARBgsrBgEEAYI3PAIBAxMCR0IxCzAJBgNVBAYTAkdCMRQwEgYDVQQIEwtPeGZ" + "vcmRzaGlyZTEPMA0GA1UEBxMGT3hmb3JkMRgwFgYDVQQJEw9CZWF1bW9udCBTdHJlZXQxCzAJBgNVBAs" + "TAklUMSMwIQYDVQQKExpUaGUgT3hmb3JkIFBsYXlob3VzZSBUcnVzdDEgMB4GA1UEAxMXd3d3Lm94Zm9" + "yZHBsYXlob3VzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2VgUJx+QIlEn4vMq" + "5YajmJEk1Lv5Kwc95oqEb2EbQMVhCJct0OA0wKJbnFGaNIo5DJHIouuz98JoHixMB54EwZi5I64wvqyq" + "1ohquTrUk4CS/4Y4odDw61dIqE2UZCxJYui9y4fTkptjNWmTaytw3LpGkt4Yx+AIcB+Oc7c7IPjTZEvR" + "6L5lK9WqfZmrS/Y+Tgflz6W79rpgUb2CyfqLUX0Hxohw5/Zp197y4XhOwou/f+Vaju3j/Gt1WBAbWrKx" + "pKAROVesfqT/H7Y/iOJ6jkPt5rqrLosStbGMpPUNNGRY0a8F1HBAUUzjTrRAE6CGZAPgBbcloYFc1zUs" + "xPLcZAgMBAAGjggHRMIIBzTAOBgNVHQ8BAf8EBAMCBaAwTAYDVR0gBEUwQzBBBgkrBgEEAaAyAQEwNDA" + "yBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wNwYDVR0RBDA" + "wLoIXd3d3Lm94Zm9yZHBsYXlob3VzZS5jb22CE294Zm9yZHBsYXlob3VzZS5jb20wCQYDVR0TBAIwADA" + "dBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC5" + "nbG9iYWxzaWduLmNvbS9ncy9nc2V4dGVuZHZhbGcyLmNybDCBiAYIKwYBBQUHAQEEfDB6MEEGCCsGAQU" + "FBzAChjVodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2V4dGVuZHZhbGcyLmNydDA" + "1BggrBgEFBQcwAYYpaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzZXh0ZW5kdmFsZzIwHQYDVR0" + "OBBYEFNp+MVYdHILBfTE6JM8O6Ul+Xwx3MB8GA1UdIwQYMBaAFLCwSv0cdSj4HGGqE/b6wZA9axajMA0" + "GCSqGSIb3DQEBBQUAA4IBAQALHuvJlSvi3OqKwDiXBrsx0zb7DGGLAzwQCyr60iwJuc1S8SkWURlM0CK" + "Iq0Qupj5vYIAY2g6gDWxdf/JFMh/Rxzv90JE/xZm9YlnMh2Evz3glLLQ5y2x1ddc0RU9YFoeOmJcgDOR" + "OI8aQvhcn9Jdj1Yk7BkKhbQv/pM9ETqtSro3Xbv/qcwPTG/oRysMCrN/DUxedUr95dFjrS3zpo+6Hr7J" + "abTcaAak40ksY+vHEQWbqm4YluJ4/c+6qfpsTTUih6//7xs92UxObeSMtWPaxySxedXekTPYrGt5X8XX" + "PYoTKJnuJrxlkEBv0K7wozbn5Km2dpOqCAaqbf8WKa3mvAAA=" ) func makeParent(a []byte, b []byte) [sha256.Size]byte { if len(a) != len(b) { log.Fatalf("a & b are different lengths: %d vs %d", len(a), len(b)) } if len(a) != sha256.Size { log.Fatalf("a & b incorrect length for Sha256 hash") } var r [sha256.Size * 2]byte copy(r[0:31], a) copy(r[32:63], b) return sha256.Sum256(r[:]) } // CalcRootHash calculates the root hash for the test data. func CalcRootHash() { e0, err := base64.StdEncoding.DecodeString(Entry0) if err != nil { log.Fatal(err) } h0 := sha256.Sum256(e0) e1, err := base64.StdEncoding.DecodeString(Entry1) if err != nil { log.Fatal(err) } h1 := sha256.Sum256(e1) e2, err := base64.StdEncoding.DecodeString(Entry2) if err != nil { log.Fatal(err) } h2 := sha256.Sum256(e2) e3, err := base64.StdEncoding.DecodeString(Entry3) if err != nil { log.Fatal(err) } h3 := sha256.Sum256(e3) hash01 := makeParent(h0[:], h1[:]) hash23 := makeParent(h2[:], h3[:]) root := makeParent(hash01[:], hash23[:]) log.Println(base64.StdEncoding.EncodeToString(root[:])) } google-certificate-transparency-go-2308f62/schedule/000077500000000000000000000000001462611535200224355ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/schedule/schedule.go000066400000000000000000000022501462611535200245570ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package schedule provides support for periodically running a function. package schedule import ( "context" "time" ) // Every will call f periodically. // The first call will be made immediately. // Calls are made synchronously, so f will not be executed concurrently. func Every(ctx context.Context, period time.Duration, f func(context.Context)) { if ctx.Err() != nil { return } // Run f immediately, then periodically call it again. t := time.NewTicker(period) defer t.Stop() f(ctx) for { select { case <-t.C: f(ctx) case <-ctx.Done(): return } } } google-certificate-transparency-go-2308f62/schedule/schedule_test.go000066400000000000000000000033451462611535200256240ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schedule import ( "context" "sync/atomic" "testing" "time" ) func TestEvery(t *testing.T) { for _, test := range []struct { name string period time.Duration timeout time.Duration wantExecutions uint32 }{ { name: "0 runs", period: 100 * time.Millisecond, timeout: 0, wantExecutions: 0, }, { name: "1 run", period: 100 * time.Millisecond, timeout: 50 * time.Millisecond, wantExecutions: 1, }, { name: "3 runs 100ms apart", period: 100 * time.Millisecond, timeout: 250 * time.Millisecond, wantExecutions: 3, }, } { t.Run(test.name, func(t *testing.T) { test := test t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), test.timeout) defer cancel() var counter uint32 Every(ctx, test.period, func(ctx context.Context) { atomic.AddUint32(&counter, 1) }) if got, want := atomic.LoadUint32(&counter), test.wantExecutions; got != want { t.Fatalf("Every(%v, f): executed f %d times, want %d times", test.period, got, want) } }) } } google-certificate-transparency-go-2308f62/scripts/000077500000000000000000000000001462611535200223305ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/scripts/check_license.sh000077500000000000000000000015701462611535200254510ustar00rootroot00000000000000#!/bin/bash # # Checks that source files (.go and .proto) have the Apache License header. # Automatically skips generated files. set -eu check_license() { local path="$1" if head -1 "$path" | grep -iq 'generated by'; then return 0 fi # Look for "Apache License" on the file header if ! head -10 "$path" | grep -q 'Apache License'; then # Format: $path:$line:$message echo "$path:10:license header not found" return 1 fi } main() { if [[ $# -lt 1 ]]; then echo "Usage: $0 " exit 1 fi local code=0 while [[ $# -gt 0 ]]; do local path="$1" if [[ -d "$path" ]]; then for f in "$path"/*.{go,proto}; do if [[ ! -f "$f" ]]; then continue # Empty glob fi check_license "$f" || code=1 done else check_license "$path" || code=1 fi shift done exit $code } main "$@" google-certificate-transparency-go-2308f62/scripts/presubmit.sh000077500000000000000000000077271462611535200247160ustar00rootroot00000000000000#!/bin/bash # # Presubmit checks for certificate-transparency-go. # # Checks for lint errors, spelling, licensing, correct builds / tests and so on. # Flags may be specified to allow suppressing of checks or automatic fixes, try # `scripts/presubmit.sh --help` for details. # # Globals: # GO_TEST_PARALLELISM: max processes to use for Go tests. Optional (defaults # to 10). # GO_TEST_TIMEOUT: timeout for 'go test'. Optional (defaults to 5m). set -eu check_pkg() { local cmd="$1" local pkg="$2" check_cmd "$cmd" "try running 'go get -u $pkg'" } check_cmd() { local cmd="$1" local msg="$2" if ! type -p "${cmd}" > /dev/null; then echo "${cmd} not found, ${msg}" return 1 fi } usage() { echo "$0 [--coverage] [--fix] [--no-build] [--no-linters] [--no-generate]" } main() { local coverage=0 local fix=0 local run_build=1 local run_lint=1 local run_generate=1 while [[ $# -gt 0 ]]; do case "$1" in --coverage) coverage=1 ;; --fix) fix=1 ;; --help) usage exit 0 ;; --no-build) run_build=0 ;; --no-linters) run_lint=0 ;; --no-generate) run_generate=0 ;; *) usage exit 1 ;; esac shift 1 done cd "$(dirname "$0")" # at scripts/ cd .. # at top level go_srcs="$(find . -name '*.go' | \ grep -v vendor/ | \ grep -v mock_ | \ grep -v .pb.go | \ grep -v x509/ | \ grep -v asn1/ | \ tr '\n' ' ')" # Prevent the creation of proto files with .txt extensions. bad_protos="$(find . -name '*.pb.txt' -o -name '*.proto.txt' -print)" if [[ "${bad_protos}" != "" ]]; then echo "Text-based protos must use the .textproto extension:" echo $bad_protos exit 1 fi if [[ "$fix" -eq 1 ]]; then check_pkg goimports golang.org/x/tools/cmd/goimports || exit 1 echo 'running gofmt' gofmt -s -w ${go_srcs} echo 'running goimports' goimports -w ${go_srcs} fi if [[ "${run_build}" -eq 1 ]]; then echo 'running go build' go build ./... echo 'running go test' # Individual package profiles are written to "$profile.out" files under # /tmp/ct_profile. # An aggregate profile is created at /tmp/coverage.txt. mkdir -p /tmp/ct_profile rm -f /tmp/ct_profile/* for d in $(go list ./...); do # Create a different -coverprofile for each test (if enabled) local coverflags= if [[ ${coverage} -eq 1 ]]; then # Transform $d to a smaller, valid file name. # For example: # * github.com/google/certificate-transparency-go becomes c-t-go.out # * github.com/google/certificate-transparency-go/cmd/createtree/keys becomes # c-t-go-cmd-createtree-keys.out local profile="${d}.out" profile="${profile#github.com/*/}" profile="${profile//\//-}" profile="${profile/certificate-transparency-go/c-t-go}" coverflags="-covermode=atomic -coverprofile='/tmp/ct_profile/${profile}'" fi # Do not run go test in the loop, instead echo it so we can use xargs to # add some parallelism. echo go test \ -short \ -timeout=${GO_TEST_TIMEOUT:-5m} \ ${coverflags} \ "$d" done | xargs -I '{}' -P ${GO_TEST_PARALLELISM:-10} bash -c '{}' || exit 1 [[ ${coverage} -eq 1 ]] && \ cat /tmp/ct_profile/*.out > coverage.txt fi if [[ "${run_lint}" -eq 1 ]]; then check_cmd golangci-lint \ 'have you installed github.com/golangci/golangci-lint?' || exit 1 echo 'running golangci-lint' golangci-lint run --deadline=5m echo 'checking license headers' ./scripts/check_license.sh ${go_srcs} fi if [[ "${run_generate}" -eq 1 ]]; then check_cmd protoc 'have you installed protoc?' check_pkg mockgen github.com/golang/mock/mockgen || exit 1 echo 'running go generate' go generate -run="protoc" ./... go generate -run="mockgen" ./... fi } main "$@" google-certificate-transparency-go-2308f62/scripts/resetctdb.sh000077500000000000000000000064231462611535200246530ustar00rootroot00000000000000#!/bin/bash set -e usage() { cat < /dev/stderr exit 1 } collect_vars() { # set unset environment variables to defaults [ -z ${MYSQL_ROOT_USER+x} ] && MYSQL_ROOT_USER="root" [ -z ${MYSQL_HOST+x} ] && MYSQL_HOST="localhost" [ -z ${MYSQL_PORT+x} ] && MYSQL_PORT="3306" [ -z ${MYSQL_DATABASE+x} ] && MYSQL_DATABASE="cttest" [ -z ${MYSQL_USER+x} ] && MYSQL_USER="cttest" [ -z ${MYSQL_PASSWORD+x} ] && MYSQL_PASSWORD="beeblebrox" [ -z ${MYSQL_USER_HOST+x} ] && MYSQL_USER_HOST="localhost" FLAGS=() # handle flags FORCE=false VERBOSE=false while [[ $# -gt 0 ]]; do case "$1" in --force) FORCE=true ;; --verbose) VERBOSE=true ;; --help) usage; exit ;; *) FLAGS+=("$1") esac shift 1 done FLAGS+=(-u "${MYSQL_ROOT_USER}") FLAGS+=(--host "${MYSQL_HOST}") FLAGS+=(--port "${MYSQL_PORT}") # Optionally print flags (before appending password) [[ ${VERBOSE} = 'true' ]] && echo "- Using MySQL Flags: ${FLAGS[@]}" # append password if supplied [ -z ${MYSQL_ROOT_PASSWORD+x} ] || FLAGS+=(-p"${MYSQL_ROOT_PASSWORD}") } main() { collect_vars "$@" readonly CT_GO_PATH=$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go) echo "Warning: about to destroy and reset database '${MYSQL_DATABASE}'" [[ ${FORCE} = true ]] || read -p "Are you sure? [Y/N]: " -n 1 -r echo # Print newline following the above prompt if [ -z ${REPLY+x} ] || [[ $REPLY =~ ^[Yy]$ ]] then echo "Resetting DB..." mysql "${FLAGS[@]}" -e "DROP DATABASE IF EXISTS ${MYSQL_DATABASE};" || \ die "Error: Failed to drop database '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -e "CREATE DATABASE ${MYSQL_DATABASE};" || \ die "Error: Failed to create database '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -e "CREATE USER IF NOT EXISTS ${MYSQL_USER}@'${MYSQL_USER_HOST}' IDENTIFIED BY '${MYSQL_PASSWORD}';" || \ die "Error: Failed to create user '${MYSQL_USER}@${MYSQL_USER_HOST}'." mysql "${FLAGS[@]}" -e "GRANT ALL ON ${MYSQL_DATABASE}.* TO ${MYSQL_USER}@'${MYSQL_USER_HOST}';" || \ die "Error: Failed to grant '${MYSQL_USER}' user all privileges on '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -e "FLUSH PRIVILEGES;" || \ die "Error: Failed to flush privileges." mysql "${FLAGS[@]}" -D ${MYSQL_DATABASE} < ${CT_GO_PATH}/trillian/ctfe/storage/mysql/schema.sql || \ die "Error: Failed to import schema in '${MYSQL_DATABASE}' database." echo "Reset Complete" fi } main "$@" google-certificate-transparency-go-2308f62/serialization.go000066400000000000000000000252231462611535200240510ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct import ( "crypto" "crypto/sha256" "fmt" "time" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) // SerializeSCTSignatureInput serializes the passed in sct and log entry into // the correct format for signing. func SerializeSCTSignatureInput(sct SignedCertificateTimestamp, entry LogEntry) ([]byte, error) { switch sct.SCTVersion { case V1: input := CertificateTimestamp{ SCTVersion: sct.SCTVersion, SignatureType: CertificateTimestampSignatureType, Timestamp: sct.Timestamp, EntryType: entry.Leaf.TimestampedEntry.EntryType, Extensions: sct.Extensions, } switch entry.Leaf.TimestampedEntry.EntryType { case X509LogEntryType: input.X509Entry = entry.Leaf.TimestampedEntry.X509Entry case PrecertLogEntryType: input.PrecertEntry = &PreCert{ IssuerKeyHash: entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash, TBSCertificate: entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate, } default: return nil, fmt.Errorf("unsupported entry type %s", entry.Leaf.TimestampedEntry.EntryType) } return tls.Marshal(input) default: return nil, fmt.Errorf("unknown SCT version %d", sct.SCTVersion) } } // SerializeSTHSignatureInput serializes the passed in STH into the correct // format for signing. func SerializeSTHSignatureInput(sth SignedTreeHead) ([]byte, error) { switch sth.Version { case V1: if len(sth.SHA256RootHash) != crypto.SHA256.Size() { return nil, fmt.Errorf("invalid TreeHash length, got %d expected %d", len(sth.SHA256RootHash), crypto.SHA256.Size()) } input := TreeHeadSignature{ Version: sth.Version, SignatureType: TreeHashSignatureType, Timestamp: sth.Timestamp, TreeSize: sth.TreeSize, SHA256RootHash: sth.SHA256RootHash, } return tls.Marshal(input) default: return nil, fmt.Errorf("unsupported STH version %d", sth.Version) } } // CreateX509MerkleTreeLeaf generates a MerkleTreeLeaf for an X509 cert func CreateX509MerkleTreeLeaf(cert ASN1Cert, timestamp uint64) *MerkleTreeLeaf { return &MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ Timestamp: timestamp, EntryType: X509LogEntryType, X509Entry: &cert, }, } } // MerkleTreeLeafFromRawChain generates a MerkleTreeLeaf from a chain (in DER-encoded form) and timestamp. func MerkleTreeLeafFromRawChain(rawChain []ASN1Cert, etype LogEntryType, timestamp uint64) (*MerkleTreeLeaf, error) { // Need at most 3 of the chain count := 3 if count > len(rawChain) { count = len(rawChain) } chain := make([]*x509.Certificate, count) for i := range chain { cert, err := x509.ParseCertificate(rawChain[i].Data) if x509.IsFatal(err) { return nil, fmt.Errorf("failed to parse chain[%d] cert: %v", i, err) } chain[i] = cert } return MerkleTreeLeafFromChain(chain, etype, timestamp) } // MerkleTreeLeafFromChain generates a MerkleTreeLeaf from a chain and timestamp. func MerkleTreeLeafFromChain(chain []*x509.Certificate, etype LogEntryType, timestamp uint64) (*MerkleTreeLeaf, error) { leaf := MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ EntryType: etype, Timestamp: timestamp, }, } if etype == X509LogEntryType { leaf.TimestampedEntry.X509Entry = &ASN1Cert{Data: chain[0].Raw} return &leaf, nil } if etype != PrecertLogEntryType { return nil, fmt.Errorf("unknown LogEntryType %d", etype) } // Pre-certs are more complicated. First, parse the leaf pre-cert and its // putative issuer. if len(chain) < 2 { return nil, fmt.Errorf("no issuer cert available for precert leaf building") } issuer := chain[1] cert := chain[0] var preIssuer *x509.Certificate if IsPreIssuer(issuer) { // Replace the cert's issuance information with details from the pre-issuer. preIssuer = issuer // The issuer of the pre-cert is not going to be the issuer of the final // cert. Change to use the final issuer's key hash. if len(chain) < 3 { return nil, fmt.Errorf("no issuer cert available for pre-issuer") } issuer = chain[2] } // Next, post-process the DER-encoded TBSCertificate, to remove the CT poison // extension and possibly update the issuer field. defangedTBS, err := x509.BuildPrecertTBS(cert.RawTBSCertificate, preIssuer) if err != nil { return nil, fmt.Errorf("failed to remove poison extension: %v", err) } leaf.TimestampedEntry.EntryType = PrecertLogEntryType leaf.TimestampedEntry.PrecertEntry = &PreCert{ IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo), TBSCertificate: defangedTBS, } return &leaf, nil } // MerkleTreeLeafForEmbeddedSCT generates a MerkleTreeLeaf from a chain and an // SCT timestamp, where the leaf certificate at chain[0] is a certificate that // contains embedded SCTs. It is assumed that the timestamp provided is from // one of the SCTs embedded within the leaf certificate. func MerkleTreeLeafForEmbeddedSCT(chain []*x509.Certificate, timestamp uint64) (*MerkleTreeLeaf, error) { // For building the leaf for a certificate and SCT where the SCT is embedded // in the certificate, we need to build the original precertificate TBS // data. First, parse the leaf cert and its issuer. if len(chain) < 2 { return nil, fmt.Errorf("no issuer cert available for precert leaf building") } issuer := chain[1] cert := chain[0] // Next, post-process the DER-encoded TBSCertificate, to remove the SCTList // extension. tbs, err := x509.RemoveSCTList(cert.RawTBSCertificate) if err != nil { return nil, fmt.Errorf("failed to remove SCT List extension: %v", err) } return &MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ EntryType: PrecertLogEntryType, Timestamp: timestamp, PrecertEntry: &PreCert{ IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo), TBSCertificate: tbs, }, }, }, nil } // LeafHashForLeaf returns the leaf hash for a Merkle tree leaf. func LeafHashForLeaf(leaf *MerkleTreeLeaf) ([sha256.Size]byte, error) { leafData, err := tls.Marshal(*leaf) if err != nil { return [sha256.Size]byte{}, fmt.Errorf("failed to tls-encode MerkleTreeLeaf: %s", err) } data := append([]byte{TreeLeafPrefix}, leafData...) leafHash := sha256.Sum256(data) return leafHash, nil } // IsPreIssuer indicates whether a certificate is a pre-cert issuer with the specific // certificate transparency extended key usage. func IsPreIssuer(issuer *x509.Certificate) bool { for _, eku := range issuer.ExtKeyUsage { if eku == x509.ExtKeyUsageCertificateTransparency { return true } } return false } // RawLogEntryFromLeaf converts a LeafEntry object (which has the raw leaf data // after JSON parsing) into a RawLogEntry object (i.e. a TLS-parsed structure). func RawLogEntryFromLeaf(index int64, entry *LeafEntry) (*RawLogEntry, error) { ret := RawLogEntry{Index: index} if rest, err := tls.Unmarshal(entry.LeafInput, &ret.Leaf); err != nil { return nil, fmt.Errorf("failed to unmarshal MerkleTreeLeaf: %v", err) } else if len(rest) > 0 { return nil, fmt.Errorf("MerkleTreeLeaf: trailing data %d bytes", len(rest)) } switch eType := ret.Leaf.TimestampedEntry.EntryType; eType { case X509LogEntryType: var certChain CertificateChain if rest, err := tls.Unmarshal(entry.ExtraData, &certChain); err != nil { return nil, fmt.Errorf("failed to unmarshal CertificateChain: %v", err) } else if len(rest) > 0 { return nil, fmt.Errorf("CertificateChain: trailing data %d bytes", len(rest)) } ret.Cert = *ret.Leaf.TimestampedEntry.X509Entry ret.Chain = certChain.Entries case PrecertLogEntryType: var precertChain PrecertChainEntry if rest, err := tls.Unmarshal(entry.ExtraData, &precertChain); err != nil { return nil, fmt.Errorf("failed to unmarshal PrecertChainEntry: %v", err) } else if len(rest) > 0 { return nil, fmt.Errorf("PrecertChainEntry: trailing data %d bytes", len(rest)) } ret.Cert = precertChain.PreCertificate ret.Chain = precertChain.CertificateChain default: // TODO(pavelkalinnikov): Section 4.6 of RFC6962 implies that unknown types // are not errors. We should revisit how we process this case. return nil, fmt.Errorf("unknown entry type: %v", eType) } return &ret, nil } // ToLogEntry converts RawLogEntry to a LogEntry, which includes an x509-parsed // (pre-)certificate. // // Note that this function may return a valid LogEntry object and a non-nil // error value, when the error indicates a non-fatal parsing error. func (rle *RawLogEntry) ToLogEntry() (*LogEntry, error) { var err error entry := LogEntry{Index: rle.Index, Leaf: rle.Leaf, Chain: rle.Chain} switch eType := rle.Leaf.TimestampedEntry.EntryType; eType { case X509LogEntryType: entry.X509Cert, err = rle.Leaf.X509Certificate() if x509.IsFatal(err) { return nil, fmt.Errorf("failed to parse certificate: %v", err) } case PrecertLogEntryType: var tbsCert *x509.Certificate tbsCert, err = rle.Leaf.Precertificate() if x509.IsFatal(err) { return nil, fmt.Errorf("failed to parse precertificate: %v", err) } entry.Precert = &Precertificate{ Submitted: rle.Cert, IssuerKeyHash: rle.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash, TBSCertificate: tbsCert, } default: return nil, fmt.Errorf("unknown entry type: %v", eType) } // err may be non-nil for a non-fatal error. return &entry, err } // LogEntryFromLeaf converts a LeafEntry object (which has the raw leaf data // after JSON parsing) into a LogEntry object (which includes x509.Certificate // objects, after TLS and ASN.1 parsing). // // Note that this function may return a valid LogEntry object and a non-nil // error value, when the error indicates a non-fatal parsing error. func LogEntryFromLeaf(index int64, leaf *LeafEntry) (*LogEntry, error) { rle, err := RawLogEntryFromLeaf(index, leaf) if err != nil { return nil, err } return rle.ToLogEntry() } // TimestampToTime converts a timestamp in the style of RFC 6962 (milliseconds // since UNIX epoch) to a Go Time. func TimestampToTime(ts uint64) time.Time { secs := int64(ts / 1000) msecs := int64(ts % 1000) return time.Unix(secs, msecs*1000000) } google-certificate-transparency-go-2308f62/serialization_test.go000066400000000000000000001032461462611535200251120ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct import ( "bytes" "encoding/hex" "encoding/pem" "os" "reflect" "strings" "testing" "github.com/google/certificate-transparency-go/tls" ) func dh(h string) []byte { r, err := hex.DecodeString(h) if err != nil { panic(err) } return r } const ( defaultSCTLogIDString string = "iamapublickeyshatwofivesixdigest" defaultSCTTimestamp uint64 = 1234 defaultSCTSignatureString string = "\x04\x03\x00\x09signature" defaultCertifictateString string = "certificate" defaultPrecertIssuerHashString string = "iamapublickeyshatwofivesixdigest" defaultPrecertTBSString string = "tbs" defaultCertificateSCTSignatureInputHexString string = // version, 1 byte "00" + // signature type, 1 byte "00" + // timestamp, 8 bytes "00000000000004d2" + // entry type, 2 bytes "0000" + // leaf certificate length, 3 bytes "00000b" + // leaf certificate, 11 bytes "6365727469666963617465" + // extensions length, 2 bytes "0000" + // extensions, 0 bytes "" defaultPrecertSCTSignatureInputHexString string = // version, 1 byte "00" + // signature type, 1 byte "00" + // timestamp, 8 bytes "00000000000004d2" + // entry type, 2 bytes "0001" + // issuer key hash, 32 bytes "69616d617075626c69636b657973686174776f66697665736978646967657374" + // tbs certificate length, 3 bytes "000003" + // tbs certificate, 3 bytes "746273" + // extensions length, 2 bytes "0000" + // extensions, 0 bytes "" defaultSTHSignedHexString string = // version, 1 byte "00" + // signature type, 1 byte "01" + // timestamp, 8 bytes "0000000000000929" + // tree size, 8 bytes "0000000000000006" + // root hash, 32 bytes "696d757374626565786163746c7974686972747974776f62797465736c6f6e67" defaultSCTHexString string = // version, 1 byte "00" + // keyid, 32 bytes "69616d617075626c69636b657973686174776f66697665736978646967657374" + // timestamp, 8 bytes "00000000000004d2" + // extensions length, 2 bytes "0000" + // extensions, 0 bytes // hash algo, sig algo, 2 bytes "0403" + // signature length, 2 bytes "0009" + // signature, 9 bytes "7369676e6174757265" ) func defaultSCTLogID() LogID { var id LogID copy(id.KeyID[:], defaultSCTLogIDString) return id } func defaultSCTSignature() DigitallySigned { var ds DigitallySigned if _, err := tls.Unmarshal([]byte(defaultSCTSignatureString), &ds); err != nil { panic(err) } return ds } func defaultSCT() SignedCertificateTimestamp { return SignedCertificateTimestamp{ SCTVersion: V1, LogID: defaultSCTLogID(), Timestamp: defaultSCTTimestamp, Extensions: []byte{}, Signature: defaultSCTSignature()} } func defaultCertificate() []byte { return []byte(defaultCertifictateString) } func defaultCertificateSCTSignatureInput(t *testing.T) []byte { t.Helper() r, err := hex.DecodeString(defaultCertificateSCTSignatureInputHexString) if err != nil { t.Fatalf("failed to decode defaultCertificateSCTSignatureInputHexString: %v", err) } return r } func defaultCertificateLogEntry() LogEntry { return LogEntry{ Index: 1, Leaf: MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ Timestamp: defaultSCTTimestamp, EntryType: X509LogEntryType, X509Entry: &ASN1Cert{Data: defaultCertificate()}, }, }, } } func defaultPrecertSCTSignatureInput(t *testing.T) []byte { t.Helper() r, err := hex.DecodeString(defaultPrecertSCTSignatureInputHexString) if err != nil { t.Fatalf("failed to decode defaultPrecertSCTSignatureInputHexString: %v", err) } return r } func defaultPrecertTBS() []byte { return []byte(defaultPrecertTBSString) } func defaultPrecertIssuerHash() [32]byte { var b [32]byte copy(b[:], []byte(defaultPrecertIssuerHashString)) return b } func defaultPrecertLogEntry() LogEntry { return LogEntry{ Index: 1, Leaf: MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ Timestamp: defaultSCTTimestamp, EntryType: PrecertLogEntryType, PrecertEntry: &PreCert{ IssuerKeyHash: defaultPrecertIssuerHash(), TBSCertificate: defaultPrecertTBS(), }, }, }, } } func defaultSTH() SignedTreeHead { var root SHA256Hash copy(root[:], "imustbeexactlythirtytwobyteslong") return SignedTreeHead{ TreeSize: 6, Timestamp: 2345, SHA256RootHash: root, TreeHeadSignature: DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.ECDSA}, Signature: []byte("tree_signature"), }, } } ////////////////////////////////////////////////////////////////////////////////// // Tests start here: ////////////////////////////////////////////////////////////////////////////////// func TestSerializeV1SCTSignatureInputForCertificateKAT(t *testing.T) { serialized, err := SerializeSCTSignatureInput(defaultSCT(), defaultCertificateLogEntry()) if err != nil { t.Fatalf("Failed to serialize SCT for signing: %v", err) } if !bytes.Equal(serialized, defaultCertificateSCTSignatureInput(t)) { t.Fatalf("Serialized certificate signature input doesn't match expected answer:\n%v\n%v", serialized, defaultCertificateSCTSignatureInput(t)) } } func TestSerializeV1SCTSignatureInputForPrecertKAT(t *testing.T) { serialized, err := SerializeSCTSignatureInput(defaultSCT(), defaultPrecertLogEntry()) if err != nil { t.Fatalf("Failed to serialize SCT for signing: %v", err) } if !bytes.Equal(serialized, defaultPrecertSCTSignatureInput(t)) { t.Fatalf("Serialized precertificate signature input doesn't match expected answer:\n%v\n%v", serialized, defaultPrecertSCTSignatureInput(t)) } } func TestSerializeV1STHSignatureKAT(t *testing.T) { b, err := SerializeSTHSignatureInput(defaultSTH()) if err != nil { t.Fatalf("Failed to serialize defaultSTH: %v", err) } if !bytes.Equal(b, mustDehex(t, defaultSTHSignedHexString)) { t.Fatalf("defaultSTH incorrectly serialized, expected:\n%v\ngot:\n%v", mustDehex(t, defaultSTHSignedHexString), b) } } func TestMarshalDigitallySigned(t *testing.T) { b, err := tls.Marshal( DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA512, Signature: tls.ECDSA}, Signature: []byte("signature")}) if err != nil { t.Fatalf("Failed to marshal DigitallySigned struct: %v", err) } if b[0] != byte(tls.SHA512) { t.Fatalf("Expected b[0] == SHA512, but found %v", tls.HashAlgorithm(b[0])) } if b[1] != byte(tls.ECDSA) { t.Fatalf("Expected b[1] == ECDSA, but found %v", tls.SignatureAlgorithm(b[1])) } if b[2] != 0x00 || b[3] != 0x09 { t.Fatalf("Found incorrect length bytes, expected (0x00, 0x09) found %v", b[2:3]) } if string(b[4:]) != "signature" { t.Fatalf("Found incorrect signature bytes, expected %v, found %v", []byte("signature"), b[4:]) } } func TestUnmarshalDigitallySigned(t *testing.T) { var ds DigitallySigned if _, err := tls.Unmarshal([]byte("\x01\x02\x00\x0aSiGnAtUrE!"), &ds); err != nil { t.Fatalf("Failed to unmarshal DigitallySigned: %v", err) } if ds.Algorithm.Hash != tls.MD5 { t.Fatalf("Expected HashAlgorithm %v, but got %v", tls.MD5, ds.Algorithm.Hash) } if ds.Algorithm.Signature != tls.DSA { t.Fatalf("Expected SignatureAlgorithm %v, but got %v", tls.DSA, ds.Algorithm.Signature) } if string(ds.Signature) != "SiGnAtUrE!" { t.Fatalf("Expected Signature %v, but got %v", []byte("SiGnAtUrE!"), ds.Signature) } } func TestMarshalUnmarshalSCTRoundTrip(t *testing.T) { sctIn := defaultSCT() b, err := tls.Marshal(sctIn) if err != nil { t.Fatalf("tls.Marshal(SCT)=nil,%v; want no error", err) } var sctOut SignedCertificateTimestamp if _, err := tls.Unmarshal(b, &sctOut); err != nil { t.Errorf("tls.Unmarshal(%s)=nil,%v; want %+v,nil", hex.EncodeToString(b), err, sctIn) } else if !reflect.DeepEqual(sctIn, sctOut) { t.Errorf("tls.Unmarshal(%s)=%v,nil; want %+v,nil", hex.EncodeToString(b), sctOut, sctIn) } } func TestMarshalSCT(t *testing.T) { b, err := tls.Marshal(defaultSCT()) if err != nil { t.Errorf("tls.Marshal(defaultSCT)=nil,%v; want %s", err, defaultSCTHexString) } else if !bytes.Equal(dh(defaultSCTHexString), b) { t.Errorf("tls.Marshal(defaultSCT)=%s,nil; want %s", hex.EncodeToString(b), defaultSCTHexString) } } func TestUnmarshalSCT(t *testing.T) { want := defaultSCT() var got SignedCertificateTimestamp if _, err := tls.Unmarshal(dh(defaultSCTHexString), &got); err != nil { t.Errorf("tls.Unmarshal(%s)=nil,%v; want %+v,nil", defaultSCTHexString, err, want) } else if !reflect.DeepEqual(got, want) { t.Errorf("tls.Unmarshal(%s)=%+v,nil; want %+v,nil", defaultSCTHexString, got, want) } } func TestX509MerkleTreeLeafHash(t *testing.T) { certFile := "./testdata/test-cert.pem" sctFile := "./testdata/test-cert.proof" certB, err := os.ReadFile(certFile) if err != nil { t.Fatalf("Failed to read file %s: %v", certFile, err) } certDER, _ := pem.Decode(certB) sctB, err := os.ReadFile(sctFile) if err != nil { t.Fatalf("Failed to read file %s: %v", sctFile, err) } var sct SignedCertificateTimestamp if _, err := tls.Unmarshal(sctB, &sct); err != nil { t.Fatalf("Failed to deserialize SCT: %v", err) } leaf := CreateX509MerkleTreeLeaf(ASN1Cert{Data: certDER.Bytes}, sct.Timestamp) b, err := tls.Marshal(*leaf) if err != nil { t.Fatalf("Failed to Serialize x509 leaf: %v", err) } leafBytes := dh("00000000013ddb27ded900000002ce308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b300906035504061302474231243022060355040a131b4365727469666963617465205472616e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040713074572772057656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052310b30090603550406130247423121301f060355040a13184365727469666963617465205472616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d0603551d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d230476307480145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b300906035504061302474231243022060355040a131b4365727469666963617465205472616e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040713074572772057656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171cd84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cbf667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c69727d24c4fccd249295795814d1dac0e60000") if !bytes.Equal(b, leafBytes) { t.Errorf("CreateX509MerkleTreeLeaf(): got\n %x, want\n%x", b, sctB) } } func TestLogEntryFromLeaf(t *testing.T) { const ( // Cert example taken from entry #1 in argon2018 log leafDER = "308204ef308202d7a00302010202070556658a503cca300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303132343331355a170d3138303333313038333231375a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233363839393537353331363230820122300d06092a864886f70d01010105000382010f003082010a0282010100a2fb53365dfbcefea77e1d65bc40f34f7919fcae85d82d3003428199f0c893fca95ba139156fd5e9a3bd84dc6dab8e74151fde6dd25b31526c85719bbf8990f3d6b21bb7f321306f6ddc50b96e8917fa103b388a00e1e954ee0232a9f9fb2efa8c9f9196a7fe84dad1f66b5d36127c71c9dcf25a04acd7bfda7866dfb77498c63a7ae9e7d0772fe9ba938a9ff6c0209196988158e6ea055fe967dd7599ef4bd7f306ded231cca10d89b4d6de40916e615d1d4cc6032585822a650743e34735d464fc0d544d1fad8c293df22f4a55ce3fbfb55d90cdc5ab84695a5a13d46f3176f143d9d28f60dca841eac603d30cec830a62feec091c927e6c781df330f14ca10203010001a3818b30818830130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e041604142f3948061fe546939f5e8dbc3fe4c0a1fbaab6b7300d06092a864886f70d01010b0500038202010052a2480c754c51cfdc9f99a82a8eb7c34e5e2bdcdfdd7543fadadb578083416d34ebb87fea3c90baf97f06be5baf5c41101ad1bdd2a2f554de6a3e8cd5ff3d78354badad01032a007d2eaf03590ee5397e223b2936f0c8b59c0407079c8975ffb34eb1cfe784cf3bc45e198a601473537f1ef382e0b5311d2ddf430ade7cfd28900ec9d91c1db49a6b2fb1b9e13b94135fed978d646e048b2fa9dc36ef5821cea8ebbed38d4c2d7811e9660f23d7636b295caccdc945a010a4c364fd7e7480aa5282d28fc46ce7f4f636ef2cc57c8bb1aee5da79bc6107205d4abcd3fb09a1db023ba4e8e9f34ae36ff5b2672fc2a14af8d23d67a437b3eb507ca90f73121841ab1498ab712d18063244dc3514bfffbaf6d45acdfc5316a248589a04b79b2abbca454e2e21f9b487e21eea21565c99ebc1013b87253c91f43ac6d2d2dea7090877c2a7404bce2545662ce005dc12eb57b1efe7145af8070b5dfa86736664a644a9c0f7e7c38d715cf874b818d519927eddc69b55c781b6e0a6eef8f3e46b9e059105b7932a978704e924904dbaa9583f3dd606467f4cc41589b702f1a02d517d3cd93b55d67d0b379e2527fded951be9dfb86d473613e6d9b8399ef5174d3e03185bd3cb4ea859b465c6c49010d4119d613a60878c0e453f17cfa3ce925e10f6e0a5adb745cebe218c3c82627a120e2907eeb9ec5307664474093cbc92d65fc7" leafCA = "308205c8308203b0a00302010202021001300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132323633305a170d3139303731363132323633305a307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d656469617465203130820222300d06092a864886f70d01010105000382020f003082020a0282020100c1e874feff9aeef303bbfa63453881faaf8dc1c22c09641daf430381f33bc157bf6c4c8a8d57b1abc792859d20f2191509c597c437b14673dea5af4bea14396dd436dc620555d7953e0ede01f7ffb44f3ff7cde64ed245634e0df0aafce9c3ac5eb63d8de1d969cac8854195403a9f9d1d4c3dceedf1351edd945743bc54ab745b204fb5259fe3edf695c2cf90b886c48ff680b744fec2691b4345242b31c36f3118b727c5de5c25ec1aa30a4c2461c5119ef6bb90d816e6e44c5b9955bfa2ed3416bf6e4a53c92fafac0d1f0b8bf3d35be0d4f61ca05d28fc662dfa588fba3ee0380470c012ded9e51bbf1e7a25efa745784c49d05eaf8dcee0527361ec913126005e972cf4b863914f8361582ed24563ff9e03c52e8a9ca3264c56c186b4ec52b7e695ce42ae17ec7ae0257131e1dbf48f2dde242e6e91ea304988135a15482b05fc091355328b39e586e8dd3a4a3a14cb97eef68f9f69728c291f2195d2cce73d4ae90845b1bfc5fae040b94fc359a29511981b9966aeb56d3a7c5e48f8eca815e5be86b3d36e6a27e0e2c4dee6e30f12a7c936b8c98cad5928aca238dfc39cf9f2c5246cbbbb280cb6f99eb49bfd1d78089539072c164c7083371746dedbc4dec1cb9439073af3f2e60f8c505f067961a8c539454fc5341158eccc78532f3e39c3187c9439fc0ff88ee957131d478df063dd50b2ad3fe7a070e905e3868b0203010001a350304e301d0603551d0e04160414e93c04e1802fc284132d26709ef2fd1acfaafec6301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d010105050003820201000858cbd545f2e92e09906ac39f3d55c13607a651436386c6e90f128773a0eb3f4725d8af35fb7f880436b481f2cf47801825de54f13f8f5920bf3d916e753141de5e59d2debbfc3fc226721f15a16d3b7a4618ea0551639f1d2cb7b9faad1b7e070f23a6e8197c3d7549bba6553fd5db419ce399477f6a0481b90f51c9d307d82cb05cf967828a1ace65207cf86b6d16792245dcf24b4c179f91184736e7e2fcb863a4b5c89b0ac2f368390a10594b95c856e259c77564316898cf87a6817d18585fc976d681d9d510ef2ad37e8ad0e49f5bd499c9ec7fe8f43b17dffb9b7d0dfd8300c1c5389c9ea0be4370dcbf78bd3efc2308d250b866bbca031c0c49ff77a7a5420daa1f1b6a444d366653974c2d179c3009871ee6c89140fca9efdf23bd4b88c6ebaeb9286f58f3cfc21e4874f182d1ecd6058919b03b18db7b795be9cb25fc5166a945ef8e1133cd60312a3234f4649df6166407cbb5ecc838e9e118c05dee7c896a9987655ae7e349cd8166e68d34dea3b4ae892a9f2385053271e860b542be3650503974f3bb6f2688375ab28487da6751d0f2c3a35e78efe30b19d57808ebea2c4453990ad81eb96289c0f99c5080f82092bc6123a340c63a617f3bc4adc1298d88a278a693ef93688611d3b4eded0b6d023ed9f6c2ea8836483197525b1b1bce70a90c3403b094d5f412aa1141b9965ab8314c52f772deffc1008c" rootCA = "308205cd308203b5a0030201020209009ed3ccb1d12ca272300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132303534335a170d3431313230323132303534335a307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f7430820222300d06092a864886f70d01010105000382020f003082020a0282020100aa161cf2205ed81ac565483cda426a3db2e588fdb758b17b93ea8d68495d534a01ba4f6cd1c0fc0a128af79c066dc54c3f437e05ba275ee61dbf9cbdb2928183738139397b6189ae738fef2b9b609a6dd8e0b0d0e20b243db936c029cdc2220af2c0e1a5e4aa41a006af458957e2b1178d27156ef0cb717e16d54025d97f43e9916fb240fb85f7d579462fa0ac76c76256843750bf1ccdfeb76c8c47886477644d5ec3235628adf6a09c8488bfa5036de717908151a6b585f273dd9fb5332b9af76e8fbfa91eaf4311816dde27c5c44f2fd06cc2204d7147f77ba6b16a2a5fca470023614729538bee6b3cb07264713832aec161550eb501906802215223acc2564ad1f98bb5934924eb56d383fc7598be45c89d995281c0efb0d206d29a6d25a10a48fe235332379c5ca69e83599faa677dd20823f5c84a961255eca5d4871d54ca1df0774aa117b0f42cd6e9fda7e8a48a53923c5f94043353544e644b5a6562e5cef9fc2bd2fcfcce3323335cf7fe7c4d83c1b7f839c4790192d3ba9aa9f32093aa8ee7cbe708059d538dc663cca1b825331aa836754a0d13de63bf65b6e2044dcdf041f1a0c5a9c3c38fe74cf576d451c23eaa519db32ef9e039bd848a194c3b5e41a55642dc283ddbd73d1dd97ae6951de18ad89d005007fae7e88bc7a3cce8b7ccc49603a0db67c76d58a28d4b77aa7460801e34377d0c5e4606c2e25b0203010001a350304e301d0603551d0e04160414f35f7b7549e37841396a20b67c6b4c5cc93d5841301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d01010505000382020100771cfea34579a97520d8c2423d68ecd07891f8c7f1c38bf3cd30ea9d3637bfc5d373532ec7656558bef4950646750c6fe085c52d9ffc09e66ebfa2b067de7727cb381d2b25db58b9c2197fd5eb53e020f429b86a3b1f37a07a761a66a5b3ecd797c46695a37ff2d47c54126be6bd28a2a103357227c6b73f7f689b09b48927e6e9a52267a728a115d4bcbb477533dc28f3fc57da735a3ec54fbc36990b17febb7e46b324208c1fa7425a0cba48bdc0381ea8285215261c3c483f2fa6d1da0dba4949107189f32d728a7ff395d43430af3b8ce4be5075bcf67d66661941dc8be37340f8f9282b2d2aadd69065932ad49769f8bc7fc9e5f6e79ff392420ba78de3172778e2b67e4df18440dd561e5a7844c8efa06c0e5f5b8695fa069114a00518fb4c19f9d7855831b5eeebced14b8598daffa49f2dcf505bff6417d84b28e83599d4e0371ef64b2d82ffa068a31044f7322fee2f654ec357c9c121f3458a509728c37f5673412ad0d5e76aa6b4eb1582181a8be404d3dc35e71edd83ee388087d6147c4d86f1cacacface0104df1f4b100c2ceb1be4d1851c4f31e7c4409262185878f23cceb30790143f0d5bd80d2c0ed4260aaa312597d950aaf3c8bcfc812d9a56e8d160dd772a494743710a7e478a046d8a5d505ee6b8cc37fec09dfd4cb57c6c4d8e8ef2a22e1d9e50957852633138d722253e51ba77b0069d38812207a" noExts = "0000" // Precert example taken from entry #2 in argon2018 log issuerKeyHash = "e37689003073a0c649cc656de946c03174d25c566fe3c3805b846f5236943798" precertTBS = "0002db308202d7a0030201020207055667377e8bcc300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303134343331365a170d3138303432363134323430315a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233373631393632313337303830820122300d06092a864886f70d01010105000382010f003082010a0282010100dea100ff02f31ae6f76f9c26525afcdd0ef6eef780d72b4b1c0ff14fc7ac021852d6f34af20713d05fea2c2e1a4b488b3849c2511cf30fcd2e61d9e7392557498a4ff600c8fdc912f05c8a583a5f2b6a3d3320c6cc10b7eed502de392d11b3c4d57fa6e3ddc69d3f73305bb6441a0359bd526272784523ae5319cffd2993abba54d26c4c1b760c8660b65161a349e415207a6fbb20d02ce13054e6ffa7776bccfd26c3e4220e0e504f102f352260aaa9864411f7eeae8f6071c9ba54b83d11af43ab58dcb6a7d9053654b98b165fb84a27c78361d957c70a064f45bf4501ef744302e497839a34222e94b3018e587c70c130208976d9f80cf6741a95abfa6b810203010001a3818b30818830130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e04160414df25c220250d548e08341c26cadc5effc177841c" precertDER = "30820504308202eca0030201020207055667377e8bcc300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303134343331365a170d3138303432363134323430315a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233373631393632313337303830820122300d06092a864886f70d01010105000382010f003082010a0282010100dea100ff02f31ae6f76f9c26525afcdd0ef6eef780d72b4b1c0ff14fc7ac021852d6f34af20713d05fea2c2e1a4b488b3849c2511cf30fcd2e61d9e7392557498a4ff600c8fdc912f05c8a583a5f2b6a3d3320c6cc10b7eed502de392d11b3c4d57fa6e3ddc69d3f73305bb6441a0359bd526272784523ae5319cffd2993abba54d26c4c1b760c8660b65161a349e415207a6fbb20d02ce13054e6ffa7776bccfd26c3e4220e0e504f102f352260aaa9864411f7eeae8f6071c9ba54b83d11af43ab58dcb6a7d9053654b98b165fb84a27c78361d957c70a064f45bf4501ef744302e497839a34222e94b3018e587c70c130208976d9f80cf6741a95abfa6b810203010001a381a030819d30130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e04160414df25c220250d548e08341c26cadc5effc177841c3013060a2b06010401d6790204030101ff04020500300d06092a864886f70d01010b05000382020100ae9ca16ec19bb469d08628b1296f50e3e15b362e2b18c691b11eef3af9ce655bf74e0c21c84f6091132851ba78465c3ae97a1409ee7505395d4e7e0318189029a12bf1c3ba2b6f3231c7aac13dbbfaade8d56f0fbe91d32440ad0ab816184c72392154275ead8418cc62e4e2b08de1b14acb6b27c0f36fa586feb875666f46d232a32ef022440d52cdd8bd31a42de55bfa77de8742816f086830b07eedbde545af5a2b9dd17bd49ded508589a0673f6e0d55f210818422093fd10939f0c81521ca654958e6e01b76ef8c7380bdb331e67d44ccb18a83ed04d97d463c37c7cbc592768e2373e198a1d64be3bd22d1833994706797461d05a85e779cd6e2b4b2b14e81d1eca454f29780c47a7366041ace1a48319eff3f1f04bbd471d5125774ef050e47bf664a98101b7be3337bb786b760a92be46488c6a15f72972a4b7c932c736311f0ac1d40920580329657f00e26cfb6d3b1db1eb7a95952fbcbfcbaf9d17587f03aeb9c3b403d1dccad895316658d35fe385fc5a62b60db36e3f07c4798314936aeb3f40094aee9ec1350ea8f68d1aeb41b211ecd0c9e29c5fa2d6576bcb2ad5ec8cc936e1f5a127afa0de3ae490b914adaa733f18ed9348d497e10ba4aa3008f84deec6976292dc0d3c2aa523602188916dd468b47f3d571e71fe51cd293c805d1280a53ab9f519a616d889303be461354edfc29dc3cc85d8570264cf4" precertCA = "308205c8308203b0a00302010202021001300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132323633305a170d3139303731363132323633305a307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d656469617465203130820222300d06092a864886f70d01010105000382020f003082020a0282020100c1e874feff9aeef303bbfa63453881faaf8dc1c22c09641daf430381f33bc157bf6c4c8a8d57b1abc792859d20f2191509c597c437b14673dea5af4bea14396dd436dc620555d7953e0ede01f7ffb44f3ff7cde64ed245634e0df0aafce9c3ac5eb63d8de1d969cac8854195403a9f9d1d4c3dceedf1351edd945743bc54ab745b204fb5259fe3edf695c2cf90b886c48ff680b744fec2691b4345242b31c36f3118b727c5de5c25ec1aa30a4c2461c5119ef6bb90d816e6e44c5b9955bfa2ed3416bf6e4a53c92fafac0d1f0b8bf3d35be0d4f61ca05d28fc662dfa588fba3ee0380470c012ded9e51bbf1e7a25efa745784c49d05eaf8dcee0527361ec913126005e972cf4b863914f8361582ed24563ff9e03c52e8a9ca3264c56c186b4ec52b7e695ce42ae17ec7ae0257131e1dbf48f2dde242e6e91ea304988135a15482b05fc091355328b39e586e8dd3a4a3a14cb97eef68f9f69728c291f2195d2cce73d4ae90845b1bfc5fae040b94fc359a29511981b9966aeb56d3a7c5e48f8eca815e5be86b3d36e6a27e0e2c4dee6e30f12a7c936b8c98cad5928aca238dfc39cf9f2c5246cbbbb280cb6f99eb49bfd1d78089539072c164c7083371746dedbc4dec1cb9439073af3f2e60f8c505f067961a8c539454fc5341158eccc78532f3e39c3187c9439fc0ff88ee957131d478df063dd50b2ad3fe7a070e905e3868b0203010001a350304e301d0603551d0e04160414e93c04e1802fc284132d26709ef2fd1acfaafec6301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d010105050003820201000858cbd545f2e92e09906ac39f3d55c13607a651436386c6e90f128773a0eb3f4725d8af35fb7f880436b481f2cf47801825de54f13f8f5920bf3d916e753141de5e59d2debbfc3fc226721f15a16d3b7a4618ea0551639f1d2cb7b9faad1b7e070f23a6e8197c3d7549bba6553fd5db419ce399477f6a0481b90f51c9d307d82cb05cf967828a1ace65207cf86b6d16792245dcf24b4c179f91184736e7e2fcb863a4b5c89b0ac2f368390a10594b95c856e259c77564316898cf87a6817d18585fc976d681d9d510ef2ad37e8ad0e49f5bd499c9ec7fe8f43b17dffb9b7d0dfd8300c1c5389c9ea0be4370dcbf78bd3efc2308d250b866bbca031c0c49ff77a7a5420daa1f1b6a444d366653974c2d179c3009871ee6c89140fca9efdf23bd4b88c6ebaeb9286f58f3cfc21e4874f182d1ecd6058919b03b18db7b795be9cb25fc5166a945ef8e1133cd60312a3234f4649df6166407cbb5ecc838e9e118c05dee7c896a9987655ae7e349cd8166e68d34dea3b4ae892a9f2385053271e860b542be3650503974f3bb6f2688375ab28487da6751d0f2c3a35e78efe30b19d57808ebea2c4453990ad81eb96289c0f99c5080f82092bc6123a340c63a617f3bc4adc1298d88a278a693ef93688611d3b4eded0b6d023ed9f6c2ea8836483197525b1b1bce70a90c3403b094d5f412aa1141b9965ab8314c52f772deffc1008c" precertRoot = "308205cd308203b5a0030201020209009ed3ccb1d12ca272300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132303534335a170d3431313230323132303534335a307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f7430820222300d06092a864886f70d01010105000382020f003082020a0282020100aa161cf2205ed81ac565483cda426a3db2e588fdb758b17b93ea8d68495d534a01ba4f6cd1c0fc0a128af79c066dc54c3f437e05ba275ee61dbf9cbdb2928183738139397b6189ae738fef2b9b609a6dd8e0b0d0e20b243db936c029cdc2220af2c0e1a5e4aa41a006af458957e2b1178d27156ef0cb717e16d54025d97f43e9916fb240fb85f7d579462fa0ac76c76256843750bf1ccdfeb76c8c47886477644d5ec3235628adf6a09c8488bfa5036de717908151a6b585f273dd9fb5332b9af76e8fbfa91eaf4311816dde27c5c44f2fd06cc2204d7147f77ba6b16a2a5fca470023614729538bee6b3cb07264713832aec161550eb501906802215223acc2564ad1f98bb5934924eb56d383fc7598be45c89d995281c0efb0d206d29a6d25a10a48fe235332379c5ca69e83599faa677dd20823f5c84a961255eca5d4871d54ca1df0774aa117b0f42cd6e9fda7e8a48a53923c5f94043353544e644b5a6562e5cef9fc2bd2fcfcce3323335cf7fe7c4d83c1b7f839c4790192d3ba9aa9f32093aa8ee7cbe708059d538dc663cca1b825331aa836754a0d13de63bf65b6e2044dcdf041f1a0c5a9c3c38fe74cf576d451c23eaa519db32ef9e039bd848a194c3b5e41a55642dc283ddbd73d1dd97ae6951de18ad89d005007fae7e88bc7a3cce8b7ccc49603a0db67c76d58a28d4b77aa7460801e34377d0c5e4606c2e25b0203010001a350304e301d0603551d0e04160414f35f7b7549e37841396a20b67c6b4c5cc93d5841301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d01010505000382020100771cfea34579a97520d8c2423d68ecd07891f8c7f1c38bf3cd30ea9d3637bfc5d373532ec7656558bef4950646750c6fe085c52d9ffc09e66ebfa2b067de7727cb381d2b25db58b9c2197fd5eb53e020f429b86a3b1f37a07a761a66a5b3ecd797c46695a37ff2d47c54126be6bd28a2a103357227c6b73f7f689b09b48927e6e9a52267a728a115d4bcbb477533dc28f3fc57da735a3ec54fbc36990b17febb7e46b324208c1fa7425a0cba48bdc0381ea8285215261c3c483f2fa6d1da0dba4949107189f32d728a7ff395d43430af3b8ce4be5075bcf67d66661941dc8be37340f8f9282b2d2aadd69065932ad49769f8bc7fc9e5f6e79ff392420ba78de3172778e2b67e4df18440dd561e5a7844c8efa06c0e5f5b8695fa069114a00518fb4c19f9d7855831b5eeebced14b8598daffa49f2dcf505bff6417d84b28e83599d4e0371ef64b2d82ffa068a31044f7322fee2f654ec357c9c121f3458a509728c37f5673412ad0d5e76aa6b4eb1582181a8be404d3dc35e71edd83ee388087d6147c4d86f1cacacface0104df1f4b100c2ceb1be4d1851c4f31e7c4409262185878f23cceb30790143f0d5bd80d2c0ed4260aaa312597d950aaf3c8bcfc812d9a56e8d160dd772a494743710a7e478a046d8a5d505ee6b8cc37fec09dfd4cb57c6c4d8e8ef2a22e1d9e50957852633138d722253e51ba77b0069d38812207a" ) corruptedLeafDER := "aaaaaaaaaa" + leafDER[10:] corruptedPrecertTBS := precertTBS[:6] + "aaaaaaaaaa" + precertTBS[16:] var tests = []struct { leaf LeafEntry wantCert bool wantPrecert bool wantErr string }{ { leaf: LeafEntry{}, wantErr: "failed to unmarshal MerkleTreeLeaf", }, { leaf: LeafEntry{ // {version + leaf_type + timestamp + entry_type + len + cert + exts} LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts), ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA), }, wantCert: true, }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + corruptedLeafDER + noExts), ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA), }, wantErr: "failed to parse certificate", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts + "ff"), ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA), }, wantErr: "MerkleTreeLeaf: trailing data", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts), ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA + "00"), }, wantErr: "CertificateChain: trailing data", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts), }, wantErr: "failed to unmarshal CertificateChain", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "8000" + "0004f3" + leafDER + noExts), }, wantErr: "unknown entry type", }, { leaf: LeafEntry{ // version + leaf_type + timestamp + entry_type + key_hash + tbs + exts LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts), ExtraData: dh("000508" + precertDER + ("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot)), }, wantPrecert: true, }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + corruptedPrecertTBS + noExts), ExtraData: dh("000508" + precertDER + ("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot)), }, wantErr: "failed to parse precertificate", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts), ExtraData: dh("000508" + precertDER + ("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot) + "ff"), }, wantErr: "PrecertChainEntry: trailing data", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts + "ff"), ExtraData: dh("000508" + precertDER + ("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot)), }, wantErr: "MerkleTreeLeaf: trailing data", }, { leaf: LeafEntry{ LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts), }, wantErr: "failed to unmarshal PrecertChainEntry", }, } for i, test := range tests { got, err := LogEntryFromLeaf(int64(i), &test.leaf) if err != nil { if test.wantErr == "" { t.Errorf("LogEntryFromLeaf(%d) = _, %v; want _, nil", i, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("LogEntryFromLeaf(%d) = _, %v; want _, err containing %q", i, err, test.wantErr) } } else if test.wantErr != "" { t.Errorf("LogEntryFromLeaf(%d) = _, nil; want _, err containing %q", i, test.wantErr) } if gotCert := got != nil && got.X509Cert != nil; gotCert != test.wantCert { t.Errorf("LogEntryFromLeaf(%d).X509Cert = %v; want %v", i, gotCert, test.wantCert) } if gotPrecert := got != nil && got.Precert != nil; gotPrecert != test.wantPrecert { t.Errorf("LogEntryFromLeaf(%d).Precert = %v; want %v", i, gotPrecert, test.wantPrecert) } } } google-certificate-transparency-go-2308f62/signatures.go000066400000000000000000000071101462611535200233530ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "crypto/sha256" "encoding/base64" "encoding/pem" "fmt" "log" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) // AllowVerificationWithNonCompliantKeys may be set to true in order to allow // SignatureVerifier to use keys which are technically non-compliant with // RFC6962. var AllowVerificationWithNonCompliantKeys = false // PublicKeyFromPEM parses a PEM formatted block and returns the public key contained within and any remaining unread bytes, or an error. func PublicKeyFromPEM(b []byte) (crypto.PublicKey, SHA256Hash, []byte, error) { p, rest := pem.Decode(b) if p == nil { return nil, [sha256.Size]byte{}, rest, fmt.Errorf("no PEM block found in %s", string(b)) } k, err := x509.ParsePKIXPublicKey(p.Bytes) return k, sha256.Sum256(p.Bytes), rest, err } // PublicKeyFromB64 parses a base64-encoded public key. func PublicKeyFromB64(b64PubKey string) (crypto.PublicKey, error) { der, err := base64.StdEncoding.DecodeString(b64PubKey) if err != nil { return nil, fmt.Errorf("error decoding public key: %s", err) } return x509.ParsePKIXPublicKey(der) } // SignatureVerifier can verify signatures on SCTs and STHs type SignatureVerifier struct { PubKey crypto.PublicKey } // NewSignatureVerifier creates a new SignatureVerifier using the passed in PublicKey. func NewSignatureVerifier(pk crypto.PublicKey) (*SignatureVerifier, error) { switch pkType := pk.(type) { case *rsa.PublicKey: if pkType.N.BitLen() < 2048 { e := fmt.Errorf("public key is RSA with < 2048 bits (size:%d)", pkType.N.BitLen()) if !AllowVerificationWithNonCompliantKeys { return nil, e } log.Printf("WARNING: %v", e) } case *ecdsa.PublicKey: params := *(pkType.Params()) if params != *elliptic.P256().Params() { e := fmt.Errorf("public is ECDSA, but not on the P256 curve") if !AllowVerificationWithNonCompliantKeys { return nil, e } log.Printf("WARNING: %v", e) } default: return nil, fmt.Errorf("unsupported public key type %v", pkType) } return &SignatureVerifier{PubKey: pk}, nil } // VerifySignature verifies the given signature sig matches the data. func (s SignatureVerifier) VerifySignature(data []byte, sig tls.DigitallySigned) error { return tls.VerifySignature(s.PubKey, data, sig) } // VerifySCTSignature verifies that the SCT's signature is valid for the given LogEntry. func (s SignatureVerifier) VerifySCTSignature(sct SignedCertificateTimestamp, entry LogEntry) error { sctData, err := SerializeSCTSignatureInput(sct, entry) if err != nil { return err } return s.VerifySignature(sctData, tls.DigitallySigned(sct.Signature)) } // VerifySTHSignature verifies that the STH's signature is valid. func (s SignatureVerifier) VerifySTHSignature(sth SignedTreeHead) error { sthData, err := SerializeSTHSignatureInput(sth) if err != nil { return err } return s.VerifySignature(sthData, tls.DigitallySigned(sth.TreeHeadSignature)) } google-certificate-transparency-go-2308f62/signatures_test.go000066400000000000000000000404501462611535200244160ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct import ( "crypto" "crypto/dsa" //nolint:staticcheck "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/hex" mrand "math/rand" "testing" "github.com/google/certificate-transparency-go/tls" ) const ( sigTestDERCertString = "308202ca30820233a003020102020102300d06092a864886f70d01010505003055310b300" + "906035504061302474231243022060355040a131b4365727469666963617465205472616e" + "73706172656e6379204341310e300c0603550408130557616c65733110300e06035504071" + "3074572772057656e301e170d3132303630313030303030305a170d323230363031303030" + "3030305a3052310b30090603550406130247423121301f060355040a13184365727469666" + "963617465205472616e73706172656e6379310e300c0603550408130557616c6573311030" + "0e060355040713074572772057656e30819f300d06092a864886f70d010101050003818d0" + "030818902818100b8742267898b99ba6bfd6e6f7ada8e54337f58feb7227c46248437ba5f" + "89b007cbe1ecb4545b38ed23fddbf6b9742cafb638157f68184776a1b38ab39318ddd7344" + "89b4d750117cd83a220a7b52f295d1e18571469a581c23c68c57d973761d9787a091fb586" + "4936b166535e21b427e3c6d690b2e91a87f36b7ec26f59ce53b50203010001a381ac3081a" + "9301d0603551d0e041604141184e1187c87956dffc31dd0521ff564efbeae8d307d060355" + "1d23047630748014a3b8d89ba2690dfb48bbbf87c1039ddce56256c6a159a4573055310b3" + "00906035504061302474231243022060355040a131b436572746966696361746520547261" + "6e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040" + "713074572772057656e82010030090603551d1304023000300d06092a864886f70d010105" + "050003818100292ecf6e46c7a0bcd69051739277710385363341c0a9049637279707ae23c" + "c5128a4bdea0d480ed0206b39e3a77a2b0c49b0271f4140ab75c1de57aba498e09459b479" + "cf92a4d5d5dd5cbe3f0a11e25f04078df88fc388b61b867a8de46216c0e17c31fc7d8003e" + "cc37be22292f84242ab87fb08bd4dfa3c1b9ce4d3ee6667da" sigTestSCTTimestamp = 1348589665525 sigTestCertSCTSignatureEC = "0403" + "0048" + "3046022100d3f7690e7ee80d9988a54a3821056393e9eb0c686ad67fbae3686c888fb1a3c" + "e022100f9a51c6065bbba7ad7116a31bea1c31dbed6a921e1df02e4b403757fae3254ae" sigTestEC256PublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES0AfBkjr7b8b19p5Gk8plSAN16wW\n" + "XZyhYsH6FMCEUK60t7pem/ckoPX8hupuaiJzJS0ZQ0SEoJGlFxkUFwft5g==\n" + "-----END PUBLIC KEY-----\n" sigTestEC256PublicKey2PEM = "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHT\n" + "DM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==\n" + "-----END PUBLIC KEY-----\n" sigTestRSAPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxy7llbig9kL0wo5AyV1F\n" + "hmJLvWTWxzAMwGdhG1h1CqQpaWutXGI9WKRDJSZ/9dr9vgvqdRX2QsnUdJbJ3cz5\n" + "Z1ie/RdT/mSVO7ZEqvJS93PIHnquFZXxNnIerGnQ7guC+Zm9BlQ2DIhYpnvVRRVy\n" + "D/D8KT92R7qOu3JACduoMrF1synknL8rb8lZvCej8tbhJ38yibMWTmkxsFS+a29X\n" + "qk8pkhgwIwvUZqcMaqZo+4/iCuKLbVc85V98SvbcnmsX3gqeQnyRtxlctlclcbvH\n" + "mJt5U+3yF1UtcuiyZf1gjcAqnOgvZZYzsodXi0KGV7NRQhTPvwH0C8In2qL+v4qW\n" + "AQIDAQAB\n" + "-----END PUBLIC KEY-----\n" sigTestCertSCTSignatureRSA = "0401" + "0100" + "6bc1fecfe9052036e31278cd7eded90d000b127f2b657831baf5ecb31ee3" + "c17497abd9562df6319928a36df0ab1a1a917b3f4530e1ca0000ae6c4a0c" + "0efada7df83beb95da8eea98f1a27c70afa1ccaa7a0245e1db785b1c0d9f" + "ee307e926e14bed1eac0d01c34939e659360432a9552c02b89c3ef3c44aa" + "22fc31f2444522975ee83989dd7af1ab05b91bbf0985ca4d04245b68a683" + "01d300f0c976ce13d58618dad1b49c0ec5cdc4352016823fc88c479ef214" + "76c5f19923af207dbb1b2cff72d4e1e5ee77dd420b85d0f9dcc30a0f617c" + "2d3c916eb77f167323500d1b53dc4253321a106e441af343cf2f68630873" + "abd43ca52629c586107eb7eb85f2c3ee" sigTestCertSCTSignatureUnsupportedSignatureAlgorithm = "0402" + "0000" sigTestCertSCTSignatureUnsupportedHashAlgorithm = "0303" + "0000" // Some time in September 2012. sigTestDefaultSTHTimestamp = 1348589667204 sigTestDefaultTreeSize = 42 // *Some* hash that we pretend is a valid root hash. sigTestDefaultRootHash = "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23" sigTestDefaultSTHSignature = "0403" + "0048" + "3046022100befd8060563763a5e49ba53e6443c13f7624fd6403178113736e16012aca983" + "e022100f572568dbfe9a86490eb915c4ee16ad5ecd708fed35ed4e5cd1b2c3f087b4130" sigTestKeyIDEC = "b69d879e3f2c4402556dcda2f6b2e02ff6b6df4789c53000e14f4b125ae847aa" ) func mustDehex(t *testing.T, h string) []byte { t.Helper() r, err := hex.DecodeString(h) if err != nil { t.Fatalf("Failed to decode hex string (%s): %v", h, err) } return r } func sigTestSCTWithSignature(t *testing.T, sig, keyID string) SignedCertificateTimestamp { t.Helper() var ds DigitallySigned if _, err := tls.Unmarshal(mustDehex(t, sig), &ds); err != nil { t.Fatalf("Failed to unmarshal sigTestCertSCTSignatureEC: %v", err) } var id LogID copy(id.KeyID[:], mustDehex(t, keyID)) return SignedCertificateTimestamp{ SCTVersion: V1, LogID: id, Timestamp: sigTestSCTTimestamp, Signature: ds, } } func sigTestSCTEC(t *testing.T) SignedCertificateTimestamp { t.Helper() return sigTestSCTWithSignature(t, sigTestCertSCTSignatureEC, sigTestKeyIDEC) } func sigTestSCTRSA(t *testing.T) SignedCertificateTimestamp { t.Helper() return sigTestSCTWithSignature(t, sigTestCertSCTSignatureRSA, sigTestKeyIDEC) } func sigTestECPublicKey(t *testing.T) crypto.PublicKey { t.Helper() pk, _, _, err := PublicKeyFromPEM([]byte(sigTestEC256PublicKeyPEM)) if err != nil { t.Fatalf("Failed to parse sigTestEC256PublicKey: %v", err) } return pk } func sigTestECPublicKey2(t *testing.T) crypto.PublicKey { t.Helper() pk, _, _, err := PublicKeyFromPEM([]byte(sigTestEC256PublicKey2PEM)) if err != nil { t.Fatalf("Failed to parse sigTestEC256PublicKey2: %v", err) } return pk } func sigTestRSAPublicKey(t *testing.T) crypto.PublicKey { t.Helper() pk, _, _, err := PublicKeyFromPEM([]byte(sigTestRSAPublicKeyPEM)) if err != nil { t.Fatalf("Failed to parse sigTestRSAPublicKey: %v", err) } return pk } func sigTestCertLogEntry(t *testing.T) LogEntry { t.Helper() return LogEntry{ Index: 0, Leaf: MerkleTreeLeaf{ Version: V1, LeafType: TimestampedEntryLeafType, TimestampedEntry: &TimestampedEntry{ Timestamp: sigTestSCTTimestamp, EntryType: X509LogEntryType, X509Entry: &ASN1Cert{Data: mustDehex(t, sigTestDERCertString)}, }, }, } } func sigTestDefaultSTH(t *testing.T) SignedTreeHead { t.Helper() var ds DigitallySigned if _, err := tls.Unmarshal(mustDehex(t, sigTestDefaultSTHSignature), &ds); err != nil { t.Fatalf("Failed to unmarshal sigTestCertSCTSignatureEC: %v", err) } var rootHash SHA256Hash copy(rootHash[:], mustDehex(t, sigTestDefaultRootHash)) return SignedTreeHead{ Version: V1, Timestamp: sigTestDefaultSTHTimestamp, TreeSize: sigTestDefaultTreeSize, SHA256RootHash: rootHash, TreeHeadSignature: ds, } } func mustCreateSignatureVerifier(t *testing.T, pk crypto.PublicKey) SignatureVerifier { t.Helper() sv, err := NewSignatureVerifier(pk) if err != nil { t.Fatalf("Failed to create SignatureVerifier: %v", err) } return *sv } func corruptByteAt(b []byte, pos int) { b[pos] ^= byte(mrand.Intn(255) + 1) } func corruptBytes(b []byte) { corruptByteAt(b, mrand.Intn(len(b))) } func expectVerifySCTToFail(t *testing.T, sv SignatureVerifier, sct SignedCertificateTimestamp, msg string) { t.Helper() if err := sv.VerifySCTSignature(sct, sigTestCertLogEntry(t)); err == nil { t.Fatal(msg) } } func TestVerifySCTSignatureEC(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) if err := v.VerifySCTSignature(sigTestSCTEC(t), sigTestCertLogEntry(t)); err != nil { t.Fatalf("Failed to verify signature on SCT: %v", err) } } func TestVerifySCTSignatureRSA(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t)) if err := v.VerifySCTSignature(sigTestSCTRSA(t), sigTestCertLogEntry(t)); err != nil { t.Fatalf("Failed to verify signature on SCT: %v", err) } } func TestVerifySCTSignatureFailsForMismatchedSignatureAlgorithm(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) expectVerifySCTToFail(t, v, sigTestSCTRSA(t), "Successfully verified with mismatched signature algorithm") } func TestVerifySCTSignatureFailsForUnknownSignatureAlgorithm(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) expectVerifySCTToFail(t, v, sigTestSCTWithSignature(t, sigTestCertSCTSignatureUnsupportedSignatureAlgorithm, sigTestKeyIDEC), "Successfully verified signature with unsupported signature algorithm") } func TestVerifySCTSignatureFailsForUnknownHashAlgorithm(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) expectVerifySCTToFail(t, v, sigTestSCTWithSignature(t, sigTestCertSCTSignatureUnsupportedHashAlgorithm, sigTestKeyIDEC), "Successfully verified signature with unsupported hash algorithm") } func testVerifySCTSignatureFailsForIncorrectLeafBytes(t *testing.T, sct SignedCertificateTimestamp, sv SignatureVerifier) { t.Helper() entry := sigTestCertLogEntry(t) for i := range entry.Leaf.TimestampedEntry.X509Entry.Data { old := entry.Leaf.TimestampedEntry.X509Entry.Data[i] corruptByteAt(entry.Leaf.TimestampedEntry.X509Entry.Data, i) if err := sv.VerifySCTSignature(sct, entry); err == nil { t.Fatalf("Incorrectly verified signature over corrupted leaf data, uncovered byte at %d?", i) } entry.Leaf.TimestampedEntry.X509Entry.Data[i] = old } // Ensure we were only corrupting one byte at a time, should be correct again now. if err := sv.VerifySCTSignature(sct, entry); err != nil { t.Fatalf("Input data appears to still be corrupt, bug? %v", err) } } func testVerifySCTSignatureFailsForIncorrectSignature(t *testing.T, sct SignedCertificateTimestamp, sv SignatureVerifier) { t.Helper() corruptBytes(sct.Signature.Signature) expectVerifySCTToFail(t, sv, sct, "Incorrectly verified corrupt signature") } func TestVerifySCTSignatureECFailsForIncorrectLeafBytes(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) testVerifySCTSignatureFailsForIncorrectLeafBytes(t, sigTestSCTEC(t), v) } func TestVerifySCTSignatureECFailsForIncorrectTimestamp(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sct := sigTestSCTEC(t) sct.Timestamp++ expectVerifySCTToFail(t, v, sct, "Incorrectly verified signature with incorrect SCT timestamp.") } func TestVerifySCTSignatureECFailsForIncorrectVersion(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sct := sigTestSCTEC(t) sct.SCTVersion++ expectVerifySCTToFail(t, v, sct, "Incorrectly verified signature with incorrect SCT Version.") } func TestVerifySCTSignatureECFailsForIncorrectSignature(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v) } func TestVerifySCTSignatureRSAFailsForIncorrectLeafBytes(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t)) testVerifySCTSignatureFailsForIncorrectLeafBytes(t, sigTestSCTRSA(t), v) } func TestVerifySCTSignatureRSAFailsForIncorrectSignature(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t)) testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTRSA(t), v) } func TestVerifySCTSignatureFailsForSignatureCreatedWithDifferentAlgorithm(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t)) testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v) } func TestVerifySCTSignatureFailsForSignatureCreatedWithDifferentKey(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey2(t)) testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v) } func expectVerifySTHToPass(t *testing.T, v SignatureVerifier, sth SignedTreeHead) { t.Helper() if err := v.VerifySTHSignature(sth); err != nil { t.Fatalf("Incorrectly failed to verify STH signature: %v", err) } } func expectVerifySTHToFail(t *testing.T, v SignatureVerifier, sth SignedTreeHead) { t.Helper() if err := v.VerifySTHSignature(sth); err == nil { t.Fatal("Incorrectly verified STH signature") } } func TestVerifyValidSTH(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) expectVerifySTHToPass(t, v, sth) } func TestVerifySTHCatchesCorruptSignature(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) corruptBytes(sth.TreeHeadSignature.Signature) expectVerifySTHToFail(t, v, sth) } func TestVerifySTHCatchesCorruptRootHash(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) for i := range sth.SHA256RootHash { old := sth.SHA256RootHash[i] corruptByteAt(sth.SHA256RootHash[:], i) expectVerifySTHToFail(t, v, sth) sth.SHA256RootHash[i] = old } // ensure we were only testing one corrupt byte at a time - should be correct again now. expectVerifySTHToPass(t, v, sth) } func TestVerifySTHCatchesCorruptTimestamp(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) sth.Timestamp++ expectVerifySTHToFail(t, v, sth) } func TestVerifySTHCatchesCorruptVersion(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) sth.Version++ expectVerifySTHToFail(t, v, sth) } func TestVerifySTHCatchesCorruptTreeSize(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t)) sth := sigTestDefaultSTH(t) sth.TreeSize++ expectVerifySTHToFail(t, v, sth) } func TestVerifySTHFailsToVerifyForKeyWithDifferentAlgorithm(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t)) sth := sigTestDefaultSTH(t) expectVerifySTHToFail(t, v, sth) } func TestVerifySTHFailsToVerifyForDifferentKey(t *testing.T) { v := mustCreateSignatureVerifier(t, sigTestECPublicKey2(t)) sth := sigTestDefaultSTH(t) expectVerifySTHToFail(t, v, sth) } func TestNewSignatureVerifierFailsWithUnsupportedKeyType(t *testing.T) { var k dsa.PrivateKey if err := dsa.GenerateParameters(&k.Parameters, rand.Reader, dsa.L1024N160); err != nil { t.Fatalf("Failed to generate DSA key parameters: %v", err) } if err := dsa.GenerateKey(&k, rand.Reader); err != nil { t.Fatalf("Failed to generate DSA key: %v", err) } if _, err := NewSignatureVerifier(k); err == nil { t.Fatal("Creating a SignatureVerifier with a DSA key unexpectedly succeeded") } } func TestNewSignatureVerifierFailsWithBadKeyParametersForEC(t *testing.T) { k, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) if err != nil { t.Fatalf("Failed to generate ECDSA key on P224: %v", err) } if _, err := NewSignatureVerifier(k); err == nil { t.Fatal("Incorrectly created new SignatureVerifier with EC P224 key.") } } func TestNewSignatureVerifierFailsWithBadKeyParametersForRSA(t *testing.T) { k, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { t.Fatalf("Failed to generate 1024 bit RSA key: %v", err) } if _, err := NewSignatureVerifier(k); err == nil { t.Fatal("Incorrectly created new SignatureVerifier with 1024 bit RSA key.") } } func TestWillAllowNonCompliantECKeyWithOverride(t *testing.T) { AllowVerificationWithNonCompliantKeys = true k, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) if err != nil { t.Fatalf("Failed to generate EC key on P224: %v", err) } if _, err := NewSignatureVerifier(k.Public()); err != nil { t.Fatalf("Incorrectly disallowed P224 EC key with override set: %v", err) } } func TestWillAllowNonCompliantRSAKeyWithOverride(t *testing.T) { AllowVerificationWithNonCompliantKeys = true k, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { t.Fatalf("Failed to generate 1024 bit RSA key: %v", err) } if _, err := NewSignatureVerifier(k.Public()); err != nil { t.Fatalf("Incorrectly disallowed 1024 bit RSA key with override set: %v", err) } } google-certificate-transparency-go-2308f62/submission/000077500000000000000000000000001462611535200230345ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/distributor.go000066400000000000000000000353301462611535200257410ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "errors" "fmt" "net/http" "net/url" "strconv" "sync" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/ctpolicy" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian/monitoring" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) var ( ErrDistributorNotEnoughCompatibleLogs = errors.New("distributor does not have enough compatible Logs to comply with the policy") ErrDistributorUnableToProcessEmptyChain = errors.New("distributor unable to process empty chain") ) var ( // Metrics are per-log, per-endpoint and some per-response-status code. distOnce sync.Once reqsCounter monitoring.Counter // logurl, ep => value rspsCounter monitoring.Counter // logurl, ep, sc => value errCounter monitoring.Counter // logurl, ep, status => value logRspLatency monitoring.Histogram // logurl, ep => value // Per-log lastGetRootsSuccess monitoring.Gauge // Unix time ) // distInitMetrics initializes all the exported metrics. func distInitMetrics(mf monitoring.MetricFactory) { reqsCounter = mf.NewCounter("http_reqs", "Number of requests", "logurl", "ep") rspsCounter = mf.NewCounter("http_rsps", "Number of responses", "logurl", "ep", "httpstatus") errCounter = mf.NewCounter("err_count", "Number of errors", "logurl", "ep", "errtype") logRspLatency = mf.NewHistogram("http_log_latency", "Latency of responses in seconds", "logurl", "ep") lastGetRootsSuccess = mf.NewGauge("last_get_roots_success", "Unix timestamp for last successful get-roots request", "logurl") } const ( // GetRootsTimeout timeout used for external requests within root-updates. getRootsTimeout = time.Second * 10 ) // pendingLogsPolicy is policy stub used for spreading submissions across // Pending and Qualified Logs. type pendingLogsPolicy struct { } func (stubP pendingLogsPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist3.LogList) (ctpolicy.LogPolicyData, error) { baseGroup, err := ctpolicy.BaseGroupFor(approved, 1) groups := ctpolicy.LogPolicyData{baseGroup.Name: baseGroup} return groups, err } func (stubP pendingLogsPolicy) Name() string { return "Pending/Qualified Policy" } // Distributor operates policy-based submission across Logs. type Distributor struct { ll *loglist3.LogList usableLl *loglist3.LogList pendingQualifiedLl *loglist3.LogList mu sync.RWMutex // helper structs produced out of ll during init. logClients map[string]client.AddLogClient logRoots loglist3.LogRoots rootPool *x509util.PEMCertPool rootDataFull bool policy ctpolicy.CTPolicy pendingLogsPolicy ctpolicy.CTPolicy // rootCompatibilityCheckDisabled being set to true disables the root // compatibility checking the distributor does before sending a // certificate to a given CT log. rootCompatibilityCheckDisabled bool } // RefreshRoots requests roots from Logs and updates local copy. // Returns error map keyed by log-URL for any Log experiencing roots retrieval // problems // If at least one root was successfully parsed for a log, log roots set gets // the update. func (d *Distributor) RefreshRoots(ctx context.Context) map[string]error { if d.rootCompatibilityCheckDisabled { return nil } type RootsResult struct { LogURL string Roots *x509util.PEMCertPool Err error } ch := make(chan RootsResult, len(d.logClients)) rctx, cancel := context.WithTimeout(ctx, getRootsTimeout) defer cancel() for logURL, lc := range d.logClients { go func(logURL string, lc client.AddLogClient) { res := RootsResult{LogURL: logURL} roots, err := lc.GetAcceptedRoots(rctx) if err != nil { res.Err = fmt.Errorf("roots refresh for %s: couldn't collect roots. %s", logURL, err) ch <- res return } res.Roots = x509util.NewPEMCertPool() for _, r := range roots { parsed, err := x509.ParseCertificate(r.Data) if x509.IsFatal(err) { errS := fmt.Errorf("roots refresh for %s: unable to parse root cert: %s", logURL, err) if res.Err != nil { res.Err = fmt.Errorf("%s\n%s", res.Err, errS) } else { res.Err = errS } continue } res.Roots.AddCert(parsed) } ch <- res }(logURL, lc) } // Collect get-roots results for every Log-client. freshRoots := make(loglist3.LogRoots) errors := make(map[string]error) for range d.logClients { r := <-ch // update roots if r.Err != nil { errors[r.LogURL] = r.Err } // Roots get update even if some returned roots couldn't get parsed. if r.Roots != nil { freshRoots[r.LogURL] = r.Roots lastGetRootsSuccess.Set(float64(time.Now().Unix()), r.LogURL) } } d.mu.Lock() defer d.mu.Unlock() d.logRoots = freshRoots d.rootDataFull = len(d.logRoots) == len(d.logClients) // Merge individual root-pools into a unified one d.rootPool = x509util.NewPEMCertPool() for _, pool := range d.logRoots { for _, c := range pool.RawCertificates() { d.rootPool.AddCert(c) } } return errors } // incRspsCounter extracts HTTP status code and increments corresponding rspsCounter. func incRspsCounter(logURL string, endpoint string, rspErr error) { status := http.StatusOK if rspErr != nil { status = http.StatusBadRequest // default to this if status code unavailable if err, ok := rspErr.(client.RspError); ok { status = err.StatusCode } } rspsCounter.Inc(logURL, endpoint, strconv.Itoa(status)) } // incErrCounter increments corresponding errCounter if any error occurred during // submission to a Log. func incErrCounter(logURL string, endpoint string, rspErr error) { if rspErr == nil { return } err, ok := rspErr.(client.RspError) switch { case !ok: klog.Errorf("unknown_error (%s, %s) => %v", logURL, endpoint, rspErr) errCounter.Inc(logURL, endpoint, "unknown_error") case err.Err != nil && err.StatusCode == http.StatusOK: klog.Errorf("invalid_sct (%s, %s) => HTTP details: status=%d, body:\n%s", logURL, endpoint, err.StatusCode, err.Body) errCounter.Inc(logURL, endpoint, "invalid_sct") case err.Err != nil: // err.StatusCode != http.StatusOK. klog.Errorf("connection_error (%s, %s) => HTTP details: status=%d, body:\n%s", logURL, endpoint, err.StatusCode, err.Body) errCounter.Inc(logURL, endpoint, "connection_error") } } // SubmitToLog implements Submitter interface. func (d *Distributor) SubmitToLog(ctx context.Context, logURL string, chain []ct.ASN1Cert, asPreChain bool) (*ct.SignedCertificateTimestamp, error) { lc, ok := d.logClients[logURL] if !ok { return nil, fmt.Errorf("no client registered for Log with URL %q", logURL) } // endpoint used for metrics endpoint := string(ctfe.AddChainName) if asPreChain { endpoint = string(ctfe.AddPreChainName) } defer func(start time.Time) { logRspLatency.Observe(time.Since(start).Seconds(), logURL, endpoint) }(time.Now()) reqsCounter.Inc(logURL, endpoint) addChain := lc.AddChain if asPreChain { addChain = lc.AddPreChain } sct, err := addChain(ctx, chain) incRspsCounter(logURL, endpoint, err) incErrCounter(logURL, endpoint, err) return sct, err } // parseRawChain reads cert chain from bytes into x509.Certificate format. func parseRawChain(rawChain [][]byte) ([]*x509.Certificate, error) { parsedChain := make([]*x509.Certificate, 0, len(rawChain)) for _, certBytes := range rawChain { cert, err := x509.ParseCertificate(certBytes) if x509.IsFatal(err) { return nil, fmt.Errorf("distributor unable to parse cert-chain %v", err) } parsedChain = append(parsedChain, cert) } return parsedChain, nil } // addSomeChain is helper calling one of AddChain or AddPreChain based // on asPreChain param. func (d *Distributor) addSomeChain(ctx context.Context, rawChain [][]byte, loadPendingLogs bool, asPreChain bool) ([]*AssignedSCT, error) { if len(rawChain) == 0 { return nil, ErrDistributorUnableToProcessEmptyChain } // Helper function establishing responsibility of locking while determining log list and root chain. compatibleLogsAndChain := func() (loglist3.LogList, []*x509.Certificate, error) { parsedChain, err := parseRawChain(rawChain) if err != nil { return loglist3.LogList{}, nil, fmt.Errorf("distributor unable to parse cert-chain: %v", err) } if d.rootCompatibilityCheckDisabled { return d.usableLl.Compatible(parsedChain[0], nil, loglist3.LogRoots{}), parsedChain, nil } d.mu.RLock() defer d.mu.RUnlock() vOpts := ctfe.NewCertValidationOpts(d.rootPool, time.Time{}, false, false, nil, nil, false, nil) rootedChain, err := ctfe.ValidateChain(rawChain, vOpts) if err == nil { return d.usableLl.Compatible(rootedChain[0], rootedChain[len(rootedChain)-1], d.logRoots), rootedChain, nil } if d.rootDataFull { // Could not verify the chain while root info for logs is complete. return loglist3.LogList{}, nil, fmt.Errorf("distributor unable to process cert-chain: %w", err) } // Chain might be rooted to the Log which has no root-info yet. return d.usableLl.Compatible(parsedChain[0], nil, d.logRoots), parsedChain, nil } compatibleLogs, parsedChain, err := compatibleLogsAndChain() if err != nil { return nil, err } // Distinguish between precerts and certificates. isPrecert, err := ctfe.IsPrecertificate(parsedChain[0]) if err != nil { return nil, fmt.Errorf("distributor unable to check certificate %v: \n%v", parsedChain[0], err) } if isPrecert != asPreChain { var methodType, inputType string if asPreChain { methodType = "pre-" } if isPrecert { inputType = "pre-" } return nil, fmt.Errorf("add-%schain method expected %scertificate, got %scertificate", methodType, methodType, inputType) } // Set up policy structs. groups, err := d.policy.LogsByGroup(parsedChain[0], &compatibleLogs) if err != nil { return nil, fmt.Errorf("%w: %w", ErrDistributorNotEnoughCompatibleLogs, err) } chain := make([]ct.ASN1Cert, len(parsedChain)) for i, c := range parsedChain { chain[i] = ct.ASN1Cert{Data: c.Raw} } if loadPendingLogs { go func() { pendingGroup, err := d.pendingLogsPolicy.LogsByGroup(parsedChain[0], d.pendingQualifiedLl) if err != nil { return } if _, err := GetSCTs(ctx, d, chain, asPreChain, pendingGroup); err != nil { klog.Errorf("GetSCTs(): %v", err) } }() } return GetSCTs(ctx, d, chain, asPreChain, groups) } // AddPreChain runs add-pre-chain calls across subset of logs according to // Distributor's policy. May emit both SCTs array and error when SCTs // collected do not satisfy the policy. func (d *Distributor) AddPreChain(ctx context.Context, rawChain [][]byte, loadPendingLogs bool) ([]*AssignedSCT, error) { return d.addSomeChain(ctx, rawChain, loadPendingLogs, true) } // AddChain runs add-chain calls across subset of logs according to // Distributor's policy. May emit both SCTs array and error when SCTs // collected do not satisfy the policy. func (d *Distributor) AddChain(ctx context.Context, rawChain [][]byte, loadPendingLogs bool) ([]*AssignedSCT, error) { return d.addSomeChain(ctx, rawChain, loadPendingLogs, false) } // LogClientBuilder builds client-interface instance for a given Log. type LogClientBuilder func(*loglist3.Log) (client.AddLogClient, error) // BuildLogClient is default (non-mock) LogClientBuilder. func BuildLogClient(log *loglist3.Log) (client.AddLogClient, error) { u, err := url.Parse(log.URL) if err != nil { return nil, err } if u.Scheme == "" { u.Scheme = "https" } hc := &http.Client{Timeout: time.Second * 10} return client.New(u.String(), hc, jsonclient.Options{PublicKeyDER: log.Key}) } // NewDistributor creates and inits a Distributor instance. // The Distributor will asynchronously fetch the latest roots from all of the // logs when active. Call Run() to fetch roots and init regular updates to keep // the local copy of the roots up-to-date. func NewDistributor(ll *loglist3.LogList, plc ctpolicy.CTPolicy, lcBuilder LogClientBuilder, mf monitoring.MetricFactory, distributorOptions ...DistributorOption) (*Distributor, error) { var d Distributor // Divide Logs by statuses. d.ll = ll usableStat := []loglist3.LogStatus{loglist3.UsableLogStatus} active := ll.SelectByStatus(usableStat) d.usableLl = &active pendingQualifiedStat := []loglist3.LogStatus{ loglist3.PendingLogStatus, loglist3.QualifiedLogStatus} pending := ll.SelectByStatus(pendingQualifiedStat) d.pendingQualifiedLl = &pending d.policy = plc d.pendingLogsPolicy = pendingLogsPolicy{} d.logClients = make(map[string]client.AddLogClient) d.logRoots = make(loglist3.LogRoots) d.rootPool = x509util.NewPEMCertPool() // Build clients for each of the Logs. Also build log-to-id map. if err := d.buildLogClients(lcBuilder, d.usableLl); err != nil { return nil, err } if err := d.buildLogClients(lcBuilder, d.pendingQualifiedLl); err != nil { return nil, err } if mf == nil { mf = monitoring.InertMetricFactory{} } distOnce.Do(func() { distInitMetrics(mf) }) for _, option := range distributorOptions { if err := option.Apply(&d); err != nil { return nil, fmt.Errorf("unable to apply distributor option %v: %w", option, err) } } return &d, nil } // buildLogClients builds clients for every Log provided and adds them into // Distributor internals. func (d *Distributor) buildLogClients(lcBuilder LogClientBuilder, ll *loglist3.LogList) error { for _, op := range ll.Operators { for _, log := range op.Logs { lc, err := lcBuilder(log) if err != nil { return fmt.Errorf("failed to create log client for %s: %v", log.URL, err) } d.logClients[log.URL] = lc } } return nil } // DistributorOption allows the setting of internal behavior on the distributor. type DistributorOption interface { // Apply applies a change to the distributor. Apply(d *Distributor) error } // DisableRootCompatibilityCheckingDistributorOption disables the root compatibility // checking that the distributor does before submitting a certificate to CT logs. type DisableRootCompatibilityCheckingDistributorOption struct{} func (DisableRootCompatibilityCheckingDistributorOption) Apply(d *Distributor) error { d.rootCompatibilityCheckDisabled = true return nil } google-certificate-transparency-go-2308f62/submission/distributor_test.go000066400000000000000000000421641462611535200270030ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "encoding/json" "errors" "fmt" "regexp" "testing" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/ctpolicy" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/schedule" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/trillian/monitoring" "k8s.io/klog/v2" ) func newLocalStubLogClient(log *loglist3.Log) (client.AddLogClient, error) { return newRootedStubLogClient(log, RootsCerts) } func ExampleDistributor() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() d, err := NewDistributor(sampleValidLogList(), buildStubCTPolicy(1), newLocalStubLogClient, monitoring.InertMetricFactory{}) if err != nil { panic(err) } // Refresh roots periodically so they stay up-to-date. // Not necessary for this example, but appropriate for long-running systems. refresh := make(chan struct{}) go schedule.Every(ctx, time.Hour, func(ctx context.Context) { if errs := d.RefreshRoots(ctx); len(errs) > 0 { klog.Error(errs) } refresh <- struct{}{} }) select { case <-refresh: break case <-ctx.Done(): panic("Context expired") } scts, err := d.AddPreChain(ctx, pemFileToDERChain("../trillian/testdata/subleaf-pre.chain"), false /* loadPendingLogs */) if err != nil { panic(err) } for _, sct := range scts { fmt.Printf("%s\n", *sct) } // Output: // {https://ct.googleapis.com/rocketeer/ {Version:0 LogId:aHR0cHM6Ly9jdC5nb29nbGVhcGlzLmNvbS9yb2NrZXQ= Timestamp:1234 Extensions:'' Signature:{{SHA256 ECDSA} []}}} } var ( RootsCerts = map[string][]rootInfo{ "https://ct.googleapis.com/aviator/": { rootInfo{filename: "../trillian/testdata/fake-ca-1.cert"}, rootInfo{filename: "testdata/some.cert"}, }, "https://ct.googleapis.com/rocketeer/": { rootInfo{filename: "../trillian/testdata/fake-ca.cert"}, rootInfo{filename: "../trillian/testdata/fake-ca-1.cert"}, rootInfo{filename: "testdata/some.cert"}, rootInfo{filename: "testdata/another.cert"}, }, "https://ct.googleapis.com/icarus/": { rootInfo{raw: []byte("invalid000")}, rootInfo{filename: "testdata/another.cert"}, }, "uncollectable-roots/log/": { rootInfo{raw: []byte("invalid")}, }, } ) // newNoLogClient is LogClientBuilder that always fails. func newNoLogClient(_ *loglist3.Log) (client.AddLogClient, error) { return nil, errors.New("bad log-client builder") } func sampleLogList() *loglist3.LogList { var ll loglist3.LogList if err := json.Unmarshal([]byte(testdata.SampleLogList3), &ll); err != nil { panic(fmt.Errorf("unable to Unmarshal testdata.SampleLogList3: %v", err)) } return &ll } func sampleValidLogList() *loglist3.LogList { ll := sampleLogList() // Id of invalid Log description Racketeer inval := 2 ll.Operators[0].Logs = append(ll.Operators[0].Logs[:inval], ll.Operators[0].Logs[inval+1:]...) return ll } func sampleUncollectableLogList() *loglist3.LogList { ll := sampleValidLogList() // Append loglist that is unable to provide roots on request. ll.Operators[0].Logs = append(ll.Operators[0].Logs, &loglist3.Log{ Description: "Does not return roots", Key: []byte("VW5jb2xsZWN0YWJsZUxvZ0xpc3Q="), URL: "uncollectable-roots/log/", DNS: "uncollectable.ct.googleapis.com", MMD: 123, State: &loglist3.LogStates{Usable: &loglist3.LogState{}}, }) return ll } func TestNewDistributorLogClients(t *testing.T) { testCases := []struct { name string ll *loglist3.LogList lcBuilder LogClientBuilder errRegexp *regexp.Regexp }{ { name: "ValidLogClients", ll: sampleValidLogList(), lcBuilder: newEmptyStubLogClient, }, { name: "NoLogClients", ll: sampleValidLogList(), lcBuilder: newNoLogClient, errRegexp: regexp.MustCompile("failed to create log client"), }, { name: "NoLogClientsEmptyLogList", ll: &loglist3.LogList{}, lcBuilder: newNoLogClient, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, err := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, tc.lcBuilder, monitoring.InertMetricFactory{}) if gotErr, wantErr := err != nil, tc.errRegexp != nil; gotErr != wantErr { var unwantedErr string if gotErr { unwantedErr = fmt.Sprintf(" (%q)", err) } t.Errorf("Got error = %v%s, expected error = %v", gotErr, unwantedErr, wantErr) } else if tc.errRegexp != nil && !tc.errRegexp.MatchString(err.Error()) { t.Errorf("Error %q did not match expected regexp %q", err, tc.errRegexp) } }) } } func TestNewDistributorRootPools(t *testing.T) { testCases := []struct { name string ll *loglist3.LogList rootNum map[string]int wantErrs int distributorOptions []DistributorOption }{ { name: "InactiveZeroRoots", ll: sampleValidLogList(), // aviator is not active; 1 of 2 icarus roots is not x509 struct rootNum: map[string]int{"https://ct.googleapis.com/aviator/": 0, "https://ct.googleapis.com/rocketeer/": 4, "https://ct.googleapis.com/icarus/": 1}, wantErrs: 1, }, { name: "InactiveZeroRoots-NoRootChecking", ll: sampleValidLogList(), // aviator is not active; 1 of 2 icarus roots is not x509 struct rootNum: map[string]int{"https://ct.googleapis.com/aviator/": 0, "https://ct.googleapis.com/rocketeer/": 0, "https://ct.googleapis.com/icarus/": 0}, wantErrs: 0, distributorOptions: []DistributorOption{DisableRootCompatibilityCheckingDistributorOption{}}, }, { name: "CouldNotCollect", ll: sampleUncollectableLogList(), // aviator is not active; uncollectable client cannot provide roots rootNum: map[string]int{"https://ct.googleapis.com/aviator/": 0, "https://ct.googleapis.com/rocketeer/": 4, "https://ct.googleapis.com/icarus/": 1, "uncollectable-roots/log/": 0}, wantErrs: 2, }, { name: "CouldNotCollect-NoRootChecking", ll: sampleUncollectableLogList(), // aviator is not active; uncollectable client cannot provide roots rootNum: map[string]int{"https://ct.googleapis.com/aviator/": 0, "https://ct.googleapis.com/rocketeer/": 0, "https://ct.googleapis.com/icarus/": 0, "uncollectable-roots/log/": 0}, wantErrs: 0, distributorOptions: []DistributorOption{DisableRootCompatibilityCheckingDistributorOption{}}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() dist, _ := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, newLocalStubLogClient, monitoring.InertMetricFactory{}, tc.distributorOptions...) if errs := dist.RefreshRoots(ctx); len(errs) != tc.wantErrs { t.Errorf("dist.RefreshRoots() = %v, want %d errors", errs, tc.wantErrs) } for logURL, wantNum := range tc.rootNum { gotNum := 0 if roots, ok := dist.logRoots[logURL]; ok { gotNum = len(roots.RawCertificates()) } if wantNum != gotNum { t.Errorf("Expected %d root(s) for Log %s, got %d", wantNum, logURL, gotNum) } } }) } } func pemFileToDERChain(filename string) [][]byte { if len(filename) == 0 { return nil } rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { panic(err) } return rawChain } // Stub CT policy to run tests. type stubCTPolicy struct { baseNum int } // Builds simplistic policy requiring n SCTs from any Logs for each cert. func buildStubCTPolicy(n int) stubCTPolicy { return stubCTPolicy{baseNum: n} } func (stubP stubCTPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist3.LogList) (ctpolicy.LogPolicyData, error) { baseGroup, err := ctpolicy.BaseGroupFor(approved, stubP.baseNum) groups := ctpolicy.LogPolicyData{baseGroup.Name: baseGroup} return groups, err } func (stubP stubCTPolicy) Name() string { return "stub" } func TestDistributorAddChain(t *testing.T) { testCases := []struct { name string ll *loglist3.LogList plc ctpolicy.CTPolicy pemChainFile string getRoots bool scts []*AssignedSCT wantErr error }{ { name: "MalformedChainRequest with log roots available", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf.misordered.chain", getRoots: true, scts: nil, wantErr: ctfe.ErrNoRFCCompliantPathFound, }, { name: "MalformedChainRequest without log roots available", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf.misordered.chain", getRoots: false, scts: nil, wantErr: ErrDistributorNotEnoughCompatibleLogs, }, { name: "CallBeforeInit", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "", scts: nil, wantErr: ErrDistributorUnableToProcessEmptyChain, }, { name: "InsufficientSCTsForPolicy", ll: sampleValidLogList(), plc: ctpolicy.AppleCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf.chain", // subleaf chain is fake-ca-1-rooted getRoots: true, scts: []*AssignedSCT{}, wantErr: ErrDistributorNotEnoughCompatibleLogs, }, { name: "FullChain1Policy", ll: sampleValidLogList(), plc: buildStubCTPolicy(1), pemChainFile: "../trillian/testdata/subleaf.chain", getRoots: true, scts: []*AssignedSCT{ { LogURL: "https://ct.googleapis.com/rocketeer/", SCT: testSCT("https://ct.googleapis.com/rocketeer/"), }, }, }, // TODO(merkulova): Add tests to cover more cases where log roots aren't available } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dist, _ := NewDistributor(tc.ll, tc.plc, newLocalStubLogClient, monitoring.InertMetricFactory{}) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if tc.getRoots { if errs := dist.RefreshRoots(ctx); len(errs) != 1 || errs["https://ct.googleapis.com/icarus/"] == nil { // 1 error is expected, because the Icarus log has an invalid root (see RootCerts). t.Fatalf("dist.RefreshRoots() = %v, want 1 error for 'https://ct.googleapis.com/icarus/'", errs) } } scts, err := dist.AddChain(context.Background(), pemFileToDERChain(tc.pemChainFile), false /* loadPendingLogs */) if !errors.Is(err, tc.wantErr) { t.Fatalf("dist.AddPreChain(from %q) = (_, error: %v), want err? %t", tc.pemChainFile, err, tc.wantErr) } if err != nil { return } if got, want := len(scts), len(tc.scts); got != want { t.Errorf("dist.AddChain(from %q) = %d SCTs, want %d SCTs", tc.pemChainFile, got, want) } if diff := cmp.Diff(scts, tc.scts, cmpopts.SortSlices(func(x, y *AssignedSCT) bool { return x.LogURL < y.LogURL })); diff != "" { t.Errorf("dist.AddChain(from %q): diff -want +got\n%s", tc.pemChainFile, diff) } }) } } // TestDistributorAddChain copy but for pre-chain calls. func TestDistributorAddPreChain(t *testing.T) { testCases := []struct { name string ll *loglist3.LogList plc ctpolicy.CTPolicy pemChainFile string getRoots bool scts []*AssignedSCT wantErr error }{ { name: "MalformedChainRequest with log roots available", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf-pre.misordered.chain", getRoots: true, scts: nil, wantErr: ctfe.ErrNoRFCCompliantPathFound, }, { name: "MalformedChainRequest without log roots available", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf-pre.misordered.chain", getRoots: false, scts: nil, wantErr: ErrDistributorNotEnoughCompatibleLogs, }, { name: "CallBeforeInit", ll: sampleValidLogList(), plc: ctpolicy.ChromeCTPolicy{}, pemChainFile: "", scts: nil, wantErr: ErrDistributorUnableToProcessEmptyChain, }, { name: "InsufficientSCTsForPolicy", ll: sampleValidLogList(), plc: ctpolicy.AppleCTPolicy{}, pemChainFile: "../trillian/testdata/subleaf-pre.chain", // subleaf chain is fake-ca-1-rooted getRoots: true, scts: []*AssignedSCT{}, wantErr: ErrDistributorNotEnoughCompatibleLogs, }, { name: "FullChain1Policy", ll: sampleValidLogList(), plc: buildStubCTPolicy(1), pemChainFile: "../trillian/testdata/subleaf-pre.chain", getRoots: true, scts: []*AssignedSCT{ { LogURL: "https://ct.googleapis.com/rocketeer/", SCT: testSCT("https://ct.googleapis.com/rocketeer/"), }, }, }, // TODO(merkulova): Add tests to cover more cases where log roots aren't available } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dist, _ := NewDistributor(tc.ll, tc.plc, newLocalStubLogClient, monitoring.InertMetricFactory{}) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if tc.getRoots { if errs := dist.RefreshRoots(ctx); len(errs) != 1 || errs["https://ct.googleapis.com/icarus/"] == nil { // 1 error is expected, because the Icarus log has an invalid root (see RootCerts). t.Fatalf("dist.RefreshRoots() = %v, want 1 error for 'https://ct.googleapis.com/icarus/'", errs) } } scts, err := dist.AddPreChain(context.Background(), pemFileToDERChain(tc.pemChainFile), true /* loadPendingLogs */) if !errors.Is(err, tc.wantErr) { t.Fatalf("dist.AddPreChain(from %q) = (_, error: %v), want err? %t", tc.pemChainFile, err, tc.wantErr) } if err != nil { return } if got, want := len(scts), len(tc.scts); got != want { t.Errorf("dist.AddPreChain(from %q) = %d SCTs, want %d SCTs", tc.pemChainFile, got, want) } if diff := cmp.Diff(scts, tc.scts, cmpopts.SortSlices(func(x, y *AssignedSCT) bool { return x.LogURL < y.LogURL })); diff != "" { t.Errorf("dist.AddPreChain(from %q): diff -want +got\n%s", tc.pemChainFile, diff) } }) } } func TestDistributorAddTypeMismatch(t *testing.T) { testCases := []struct { name string asPreChain bool pemChainFile string scts []*AssignedSCT wantErr bool }{ { name: "FullChain1PolicyCertToPreAdd", asPreChain: true, pemChainFile: "../trillian/testdata/subleaf.chain", scts: nil, wantErr: true, // Sending valid cert via AddPreChain call }, { name: "FullChain1PolicyPreCertToAdd", asPreChain: false, pemChainFile: "../trillian/testdata/subleaf-pre.chain", scts: nil, wantErr: true, // Sending pre-cert via AddChain call }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dist, _ := NewDistributor(sampleValidLogList(), buildStubCTPolicy(1), newLocalStubLogClient, monitoring.InertMetricFactory{}) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if errs := dist.RefreshRoots(ctx); len(errs) != 1 || errs["https://ct.googleapis.com/icarus/"] == nil { // 1 error is expected, because the Icarus log has an invalid root (see RootCerts). t.Fatalf("dist.RefreshRoots() = %v, want 1 error for 'https://ct.googleapis.com/icarus/'", errs) } var scts []*AssignedSCT var err error if tc.asPreChain { scts, err = dist.AddPreChain(context.Background(), pemFileToDERChain(tc.pemChainFile), false /* loadPendingLogs */) } else { scts, err = dist.AddChain(context.Background(), pemFileToDERChain(tc.pemChainFile), false /* loadPendingLogs */) } pre := "" if tc.asPreChain { pre = "Pre" } if gotErr := err != nil; gotErr != tc.wantErr { t.Fatalf("dist.Add%sChain(from %q) = (_, error: %v), want err? %t", pre, tc.pemChainFile, err, tc.wantErr) } else if gotErr { return } if got, want := len(scts), len(tc.scts); got != want { t.Errorf("dist.Add%sChain(from %q) = %d SCTs, want %d SCTs", pre, tc.pemChainFile, got, want) } if diff := cmp.Diff(scts, tc.scts, cmpopts.SortSlices(func(x, y *AssignedSCT) bool { return x.LogURL < y.LogURL })); diff != "" { t.Errorf("dist.Add%sChain(from %q): diff -want +got\n%s", pre, tc.pemChainFile, diff) } }) } } google-certificate-transparency-go-2308f62/submission/hammer/000077500000000000000000000000001462611535200243055ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/hammer/main.go000066400000000000000000000073121462611535200255630ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Hammer tool sends multiple add-pre-chain requests to Submission proxy at the same time. package main import ( "bytes" "context" "encoding/json" "flag" "fmt" "io" "log" "net/http" "os" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/schedule" "github.com/google/certificate-transparency-go/submission" ) // Default number of submissions is intentionally low. // After several runs, likely to hit Log rate-limits. var ( proxyEndpoint = flag.String("proxy_endpoint", "http://localhost:5951/", "Endpoint for HTTP (host:port). Final slash is expected") timeout = flag.Duration("duration", 0*time.Minute, "Time to run continuous flow of submissions. "+ "When this and --count both have non-zero values, submission ends upon reaching earliest restriction") count = flag.Int("count", 10, "Total number of submissions to execute. "+ "When this and --duration both have non-zero values, submission ends upon reaching earliest restriction") qps = flag.Int("qps", 5, "Number of requests per second") ) func main() { flag.Parse() certData, err := os.ReadFile("submission/hammer/testdata/precert.der") if err != nil { log.Fatal(err) } interimData, err := os.ReadFile("submission/hammer/testdata/intermediate.der") if err != nil { log.Fatal(err) } var req ct.AddChainRequest req.Chain = append(req.Chain, certData) req.Chain = append(req.Chain, interimData) postBody, err := json.Marshal(req) if err != nil { log.Fatalf("%v\n", err) } // Submission runs until timeout or count is reached. If any of those flags // is set to 0, only other flag value is used as restriction. ctx, cancel := context.WithCancel(context.Background()) if *timeout > 0 { ctx, cancel = context.WithTimeout(context.Background(), *timeout) } else if *count <= 0 { log.Print("Both timeout and count flags set to 0. No submissions to be sent.") return } leftToSend := *count // If count flag is not set, send *qps requests each second until *timeout. if *count <= 0 { leftToSend = int(*timeout/time.Second) * *qps } var batchSize int schedule.Every(ctx, time.Second, func(ctx context.Context) { batchSize = *qps if leftToSend < *qps { batchSize = leftToSend } leftToSend -= batchSize if batchSize == 0 { cancel() } var wg sync.WaitGroup wg.Add(batchSize) for i := 0; i < batchSize; i++ { go func() { url := *proxyEndpoint + "ct/v1/proxy/add-pre-chain/" resp, err := http.Post(url, "application/json", bytes.NewBuffer(postBody)) if err != nil { log.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", url, err) } defer func() { if err := resp.Body.Close(); err != nil { log.Fatalf("Unable to close response body: %v", err) } }() var scts submission.SCTBatch err = json.NewDecoder(resp.Body).Decode(&scts) if err != nil { responseData, err := io.ReadAll(resp.Body) if err != nil { log.Fatalf("Unable to parse response: %v", err) } log.Fatalf("Unable to decode response: %v\n%v", err, responseData) } fmt.Printf("%v\n", scts) wg.Done() }() } wg.Wait() }) } google-certificate-transparency-go-2308f62/submission/hammer/testdata/000077500000000000000000000000001462611535200261165ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/hammer/testdata/README000066400000000000000000000002101462611535200267670ustar00rootroot00000000000000precert.der contains DER-encoded pre-certificate signed by testing CA intermediate.der contains DER-encoded intermediate testing CA certgoogle-certificate-transparency-go-2308f62/submission/hammer/testdata/README.md000066400000000000000000000002031462611535200273700ustar00rootroot00000000000000precert.der contains DER-encoded pre-certificate signed by testing CA interim.der contains DER-encoded intermediate testing CA certgoogle-certificate-transparency-go-2308f62/submission/hammer/testdata/intermediate.der000066400000000000000000000027141462611535200312700ustar00rootroot00000000000000000  *H 0}1 0 UGB10 U London10U Google UK Ltd.1!0U Certificate Transparency1!0U Merge Delay Monitor Root0 140717122630Z 190716122630Z01 0 UGB10 U London10U Google UK Ltd.1!0U Certificate Transparency1#0!U Merge Delay Intermediate 10"0  *H 0 tcE8, dC;WlLWǒ  ŗ7FsޥK9m6bUו>O?NEcN ì^=iȅA@:L=5ݔWCTt[ O%ϐďDiCE$+1o1'\% L$aL[U4nJS/  [](f-X>8pz%ExLI^Rsa1&^,cOaX.Ec.&LVRBz%q1-$.n0IZH+ U29:J:˗ir)!=JE@OYfm:|^H徆nj'6ɌY(8Ü$l( x9,Lp7F˔9:P_ya9EO4XxS/>9|9WGcP?pㆋP0N0U</„-&pϪ0U#0_{uIxA9j |kL\=XA0 U00  *H XE. jß=U6QCcs?G%د56G%T?Y =nu1A^Y޻?&rm;zFQc,~#|=uIU?AGjQ,\ge |kmy"EKLG6cț h9 YKVYud1hχ}X_vց*~[ԙ;} 8Cpܿx>#Pf IwB jDM6fSL-0 ȑ@#K뮹(oXHt;{y[%QfE3`1*24dd˵8]|jeZÍf4ޣ*#2q`B6PP9to&7ZgQã^x x,DS bP a#@:a;ĭ)xhӴ m>6H1R[p 4AZ1LRrgoogle-certificate-transparency-go-2308f62/submission/hammer/testdata/precert.der000066400000000000000000000024101462611535200302530ustar00rootroot0000000000000000pҘK0  *H  01 0 UGB10 U London10U Google UK Ltd.1!0U Certificate Transparency1#0!U Merge Delay Intermediate 10 180712194453Z 181208231805Z0c1 0 UGB10 U London1(0&U Google Certificate Transparency10U15314246936789230"0  *H 0 fn\n'iW*RDi- ;o¦_p$#٦션5U&fȷeJCpW yj2G=/dr#li4VJBn5߿'c4f_c(pj%<*6V@{g̺#&H>@36w'MS*pT6= >̘ cX5x6m= :*aa-Eϧ2\00U% 0 +0#U0flowers-to-the-world.com0 U00U#0</„-&pϪ0U:S Du `g0 +y0  *H  /h(E :XՈ%ڹ+;)f67*J#WCgwII_yiB/l+P?ll]ٿ14܊u(;I`?kMB0?ѣKv]XcuzW'F)j>I~google-certificate-transparency-go-2308f62/submission/loglist_manager.go000066400000000000000000000074141462611535200265400ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "fmt" "sync" "time" "github.com/google/certificate-transparency-go/schedule" "github.com/google/trillian/monitoring" ) var ( logRefOnce sync.Once logListLastRefresh monitoring.Gauge // Unix time ) // logRefInitMetrics initializes all the exported metrics. func logRefInitMetrics(ctx context.Context, mf monitoring.MetricFactory) { logListLastRefresh = mf.NewGauge("log_list_last_refresh", "Unix timestamp for last successful Log-list refresh") } // LogListManager runs loglist updates and keeps two latest versions of Log // list. type LogListManager struct { Errors chan error LLUpdates chan LogListData llRefreshInterval time.Duration llr LogListRefresher latestLL *LogListData previousLL *LogListData mu sync.Mutex // guards latestLL and previousLL mtf monitoring.MetricFactory } // NewLogListManager creates and inits a LogListManager instance. func NewLogListManager(llr LogListRefresher, mf monitoring.MetricFactory) *LogListManager { if mf == nil { mf = monitoring.InertMetricFactory{} } return &LogListManager{ Errors: make(chan error, 1), LLUpdates: make(chan LogListData, 1), llr: llr, mtf: mf, } } // Run starts regular LogList checks and associated versions archiving. // Emits errors and Loglist-updates into its corresponding channels, expected // to have readers listening. func (llm *LogListManager) Run(ctx context.Context, llRefresh time.Duration) { llm.llRefreshInterval = llRefresh logRefOnce.Do(func() { logRefInitMetrics(ctx, llm.mtf) }) go schedule.Every(ctx, llm.llRefreshInterval, llm.refreshLogListAndNotify) } // refreshLogListAndNotify runs single Log-list refresh and propagates data and // errors to corresponding channels func (llm *LogListManager) refreshLogListAndNotify(ctx context.Context) { if lld, err := llm.RefreshLogList(ctx); err != nil { llm.Errors <- err } else if lld != nil { llm.LLUpdates <- llm.ProduceClientLogList() } } // GetTwoLatestLogLists returns last version of Log list and a previous one. func (llm *LogListManager) GetTwoLatestLogLists() (*LogListData, *LogListData) { llm.mu.Lock() defer llm.mu.Unlock() return llm.latestLL, llm.previousLL } // RefreshLogList reads Log List one time and runs updates if necessary. func (llm *LogListManager) RefreshLogList(ctx context.Context) (*LogListData, error) { if llm.llr == nil { return nil, fmt.Errorf("the LogListManager has no LogListRefresher") } ll, err := llm.llr.Refresh() if err != nil { return nil, err } logListLastRefresh.Set(float64(time.Now().Unix())) if ll == nil { // No updates return nil, nil } llm.mu.Lock() defer llm.mu.Unlock() llm.previousLL = llm.latestLL llm.latestLL = ll return llm.latestLL, nil } // ProduceClientLogList applies client filtration on Log list. func (llm *LogListManager) ProduceClientLogList() LogListData { // TODO(Mercurrent): Add filtration clientLL := *(llm.latestLL) return clientLL } // Source exposes internal Log list path. func (llm *LogListManager) Source() string { return llm.llr.Source() } // LastJSON returns last version of Log list in JSON. func (llm *LogListManager) LastJSON() []byte { return llm.llr.LastJSON() } google-certificate-transparency-go-2308f62/submission/loglist_manager_test.go000066400000000000000000000072351462611535200276000ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "os" "strings" "testing" "time" "github.com/google/certificate-transparency-go/testdata" "github.com/google/trillian/monitoring" ) func TestNoLLRefresher(t *testing.T) { llm := NewLogListManager(nil, nil) _, err := llm.RefreshLogList(context.Background()) if err == nil { t.Errorf("llm.RefreshLogList() on nil LogListRefresher expected to get error, got none") } } func TestNoLLRefresherAfterRun(t *testing.T) { llm := NewLogListManager(nil, nil) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() llm.Run(ctx, 3*time.Second) select { case <-llm.Errors: return case <-ctx.Done(): t.Errorf("llm.Run() on nil LogListRefresher expected to emit error, got none") } } func TestFirstRefresh(t *testing.T) { f, err := createTempFile(testdata.SampleLogList3) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", testdata.SampleLogList3, err) } defer func() { if err := os.Remove(f); err != nil { t.Fatalf("Operation to remove temp file failed: %v", err) } }() llr := NewLogListRefresher(f) llm := NewLogListManager(llr, nil) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() llm.Run(ctx, 3*time.Second) select { case <-llm.LLUpdates: return case err := <-llm.Errors: t.Errorf("llm.Run() emitted error %q while expected none", err) case <-ctx.Done(): t.Errorf("llm.Run() on stub LogListRefresher expected to emit update, got none") } } func TestSecondRefresh(t *testing.T) { f, err := createTempFile(testdata.SampleLogList3) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", testdata.SampleLogList3, err) } defer func() { if err := os.Remove(f); err != nil { t.Fatalf("Operation to remove temp file failed: %v", err) } }() llr := NewLogListRefresher(f) llm := NewLogListManager(llr, monitoring.InertMetricFactory{}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() halfCtx, halfCancel := context.WithTimeout(context.Background(), time.Second) defer halfCancel() llm.Run(ctx, 300*time.Millisecond) readFirst := false First: for { select { case <-llm.LLUpdates: if !readFirst { readFirst = true } else { t.Errorf("llm.Run() emitted Log-list update when no updates happened") } case err := <-llm.Errors: t.Errorf("llm.Run() remitted error %q while expected none", err) case <-halfCtx.Done(): if !readFirst { t.Errorf("llm.Run() didn't emit any Log-list updates on init. Expected one") } break First } } sampleLogListUpdate := strings.Replace(testdata.SampleLogList3, "ct.googleapis.com/racketeer/", "ct.googleapis.com/racketeer/v2/", 1) if err := os.WriteFile(f, []byte(sampleLogListUpdate), 0644); err != nil { t.Fatalf("unable to update Log-list data file: %q", err) } select { case <-llm.LLUpdates: return case err := <-llm.Errors: t.Errorf("llm.Run() emitted error %q while expected none", err) case <-ctx.Done(): t.Errorf("llm.Run() on stub LogListRefresher expected to emit update, got none") } } google-certificate-transparency-go-2308f62/submission/loglist_refresher.go000066400000000000000000000061101462611535200271030ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "bytes" "fmt" "net/http" "sync" "time" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/x509util" ) const ( // HttpClientTimeout timeout for Log list reader http client. httpClientTimeout = 10 * time.Second ) // LogListData wraps info on external LogList, keeping its JSON source and time // of download. type LogListData struct { JSON []byte List *loglist3.LogList DownloadTime time.Time } // LogListRefresher is interface for Log List updates watcher. type LogListRefresher interface { Refresh() (*LogListData, error) LastJSON() []byte Source() string } // logListRefresherImpl regularly reads Log-list and emits notifications when // updates/errors observed. Implements LogListRefresher interface. type logListRefresherImpl struct { // updateMu limits LogListRefresherImpl to a single Refresh() at a time. updateMu sync.RWMutex lastJSON []byte path string client *http.Client } // NewCustomLogListRefresher creates and inits a LogListRefresherImpl instance. func NewCustomLogListRefresher(client *http.Client, llPath string) LogListRefresher { return &logListRefresherImpl{ path: llPath, client: client, } } // NewLogListRefresher creates and inits a LogListRefresherImpl instance using // default http.Client func NewLogListRefresher(llPath string) LogListRefresher { return NewCustomLogListRefresher(&http.Client{Timeout: httpClientTimeout}, llPath) } // Refresh fetches the log list and returns its source, formed LogList and // timestamp if source has changed compared to previous Refresh. func (llr *logListRefresherImpl) Refresh() (*LogListData, error) { llr.updateMu.Lock() defer llr.updateMu.Unlock() t := time.Now() json, err := x509util.ReadFileOrURL(llr.path, llr.client) if err != nil { return nil, fmt.Errorf("failed to read %q: %v", llr.path, err) } if bytes.Equal(json, llr.lastJSON) { return nil, nil } ll, err := loglist3.NewFromJSON(json) if err != nil { return nil, fmt.Errorf("failed to parse %q: %v", llr.path, err) } llr.lastJSON = json return &LogListData{JSON: json, List: ll, DownloadTime: t}, nil } // LastJSON returns last version of Log list in JSON. func (llr *logListRefresherImpl) LastJSON() []byte { llr.updateMu.Lock() defer llr.updateMu.Unlock() if llr.lastJSON == nil { return []byte{} } return llr.lastJSON } // Source exposes internal Log list path. func (llr *logListRefresherImpl) Source() string { return llr.path } google-certificate-transparency-go-2308f62/submission/loglist_refresher_test.go000066400000000000000000000167631462611535200301610ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "fmt" "log" "net/http" "os" "regexp" "strings" "testing" "time" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/schedule" "github.com/google/go-cmp/cmp" ) // createTempFile creates a file in the system's temp directory and writes data to it. // It returns the name of the file. func createTempFile(data string) (string, error) { f, err := os.CreateTemp("", "") if err != nil { return "", err } defer func() { if err := f.Close(); err != nil { log.Fatalf("Operation to close file failed: %v", err) } }() if _, err := f.WriteString(data); err != nil { return "", err } return f.Name(), nil } func ExampleLogListRefresher() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() f, err := createTempFile(`{"operators": [{"name":"Google"}]}`) if err != nil { panic(err) } defer func() { if err := os.Remove(f); err != nil { log.Fatalf("Operation to remove temp file failed: %v", err) } }() llr := NewLogListRefresher(f) // Refresh log list periodically so it stays up-to-date. // Not necessary for this example, but appropriate for long-running systems. llChan := make(chan *LogListData) errChan := make(chan error) go schedule.Every(ctx, time.Hour, func(ctx context.Context) { if ll, err := llr.Refresh(); err != nil { errChan <- err } else { llChan <- ll } }) select { case ll := <-llChan: fmt.Printf("# Log Operators: %d\n", len(ll.List.Operators)) case err := <-errChan: panic(err) case <-ctx.Done(): panic("Context expired") } // Output: // # Log Operators: 1 } func TestNewLogListRefresherNoFile(t *testing.T) { const wantErrSubstr = "failed to read" llr := NewLogListRefresher("nofile.json") if _, err := llr.Refresh(); !strings.Contains(err.Error(), wantErrSubstr) { t.Errorf("llr.Refresh() = (_, %v), want err containing %q", err, wantErrSubstr) } } type fakeTransport struct { called bool } func (ft *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) { ft.called = true return nil, fmt.Errorf("fakeTransport got called") } func TestNewCustomLogListRefresher(t *testing.T) { transport := fakeTransport{} client := &http.Client{Transport: &transport, Timeout: time.Second} llr := NewCustomLogListRefresher(client, "https://loglist.net/") if _, err := llr.Refresh(); err == nil { t.Errorf("Expected llr.Refresh() to return error using fakeTransport, got none") } if transport.called != true { t.Errorf("NewCustomLogListRefresher initialized with fakeTransport didn't call it on Refresh()") } } func TestNewLogListRefresher(t *testing.T) { testCases := []struct { name string ll string wantLl *loglist3.LogList errRegexp *regexp.Regexp }{ { name: "SuccessfulRead", ll: `{"operators": [{"id":0,"name":"Google"}]}`, wantLl: &loglist3.LogList{Operators: []*loglist3.Operator{{Name: "Google"}}}, }, { name: "CannotParseInput", ll: `invalid`, errRegexp: regexp.MustCompile("failed to parse"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { f, err := createTempFile(tc.ll) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", tc.ll, err) } defer func() { if err := os.Remove(f); err != nil { t.Fatalf("Operation to remove temp file failed: %v", err) } }() beforeRefresh := time.Now() llr := NewLogListRefresher(f) ll, err := llr.Refresh() afterRefresh := time.Now() if gotErr, wantErr := err != nil, tc.errRegexp != nil; gotErr != wantErr { t.Fatalf("llr.Refresh() = (_, %v), want err? %t", err, wantErr) } else if gotErr && !tc.errRegexp.MatchString(err.Error()) { t.Fatalf("llr.Refresh() = (_, %q), want err to match regexp %q", err, tc.errRegexp) } if (ll == nil) != (tc.wantLl == nil) { t.Fatalf("llr.Refresh() = (%v, _), expected value? %t", ll, tc.wantLl != nil) } if ll == nil { return } if diff := cmp.Diff(ll.List, tc.wantLl); diff != "" { t.Errorf("llr.Refresh() LogList: diff -want +got\n%s", diff) } if diff := cmp.Diff(ll.JSON, []byte(tc.ll)); diff != "" { t.Errorf("llr.Refresh() JSON: diff -want +got\n%s", diff) } if !beforeRefresh.Before(ll.DownloadTime) || !afterRefresh.After(ll.DownloadTime) { t.Errorf("llr.Refresh() DownloadTime %s: outside of (%s, %s) interval", ll.DownloadTime, beforeRefresh, afterRefresh) } }) } } func TestNewLogListRefresherUpdate(t *testing.T) { testCases := []struct { name string ll string llNext string wantLl *loglist3.LogList errRegexp *regexp.Regexp }{ { name: "NoUpdate", ll: `{"operators": [{"id":0,"name":"Google"}]}`, llNext: `{"operators": [{"id":0,"name":"Google"}]}`, wantLl: nil, errRegexp: nil, }, { name: "LogListUpdated", ll: `{"operators": [{"id":0,"name":"Google"}]}`, llNext: `{"operators": [{"id":0,"name":"GoogleOps"}]}`, wantLl: &loglist3.LogList{Operators: []*loglist3.Operator{{Name: "GoogleOps"}}}, errRegexp: nil, }, { name: "CannotParseInput", ll: `{"operators": [{"id":0,"name":"Google"}]}`, llNext: `invalid`, errRegexp: regexp.MustCompile("failed to parse"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { f, err := createTempFile(tc.ll) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", tc.ll, err) } defer func() { if err := os.Remove(f); err != nil { t.Fatalf("Operation to remove temp file failed: %v", err) } }() llr := NewLogListRefresher(f) if _, err := llr.Refresh(); err != nil { t.Fatalf("llr.Refresh() = (_, %v), want (_, nil)", err) } // Simulate Log list update. if err := os.WriteFile(f, []byte(tc.llNext), 0755); err != nil { t.Fatalf("os.WriteFile(%q, %q) = %q, want nil", f, tc.llNext, err) } beforeRefresh := time.Now() ll, err := llr.Refresh() afterRefresh := time.Now() if gotErr, wantErr := err != nil, tc.errRegexp != nil; gotErr != wantErr { t.Fatalf("llr.Refresh() = (_, %v), want err? %t", err, wantErr) } else if gotErr && !tc.errRegexp.MatchString(err.Error()) { t.Fatalf("llr.Refresh() = (_, %q), want err to match regexp %q", err, tc.errRegexp) } if llNil, wantNil := ll == nil, tc.wantLl == nil; llNil != wantNil { t.Fatalf("llr.Refresh() = (%v, _), expected nil? %t", ll, wantNil) } if ll == nil { return } if diff := cmp.Diff(tc.wantLl, ll.List); diff != "" { t.Errorf("llr.Refresh(): diff -want +got\n%s", diff) } if diff := cmp.Diff(ll.JSON, []byte(tc.llNext)); diff != "" { t.Errorf("llr.Refresh() JSON: diff -want +got\n%s", diff) } if !beforeRefresh.Before(ll.DownloadTime) || !afterRefresh.After(ll.DownloadTime) { t.Errorf("llr.Refresh() DownloadTime %s: outside of (%s, %s) interval", ll.DownloadTime, beforeRefresh, afterRefresh) } }) } } google-certificate-transparency-go-2308f62/submission/proxy.go000066400000000000000000000146241462611535200245530ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package submission contains code and structs for certificates submission proxy. package submission import ( "context" "fmt" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/ctpolicy" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/schedule" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian/monitoring" "k8s.io/klog/v2" ) // CTPolicyType indicates CT-policy used for certificate submission. type CTPolicyType int // Policy type values: const ( ChromeCTPolicy CTPolicyType = iota AppleCTPolicy ) var ( proxyOnce sync.Once logListUpdates monitoring.Counter rspLatency monitoring.Histogram // ep => value ) // proxyInitMetrics initializes all the exported metrics. func proxyInitMetrics(mf monitoring.MetricFactory) { logListUpdates = mf.NewCounter("log_list_updates", "Number of Log-list updates") rspLatency = mf.NewHistogram("http_latency", "Latency of policy-multiplexed add-responses in seconds", "ep") } // DistributorBuilder builds distributor instance for a given Log list. type DistributorBuilder func(*loglist3.LogList) (*Distributor, error) // GetDistributorBuilder given CT-policy type and Log-client builder produces // Distributor c-tor. func GetDistributorBuilder(plc CTPolicyType, lcBuilder LogClientBuilder, mf monitoring.MetricFactory) DistributorBuilder { if plc == AppleCTPolicy { return func(ll *loglist3.LogList) (*Distributor, error) { return NewDistributor(ll, ctpolicy.AppleCTPolicy{}, lcBuilder, mf) } } return func(ll *loglist3.LogList) (*Distributor, error) { return NewDistributor(ll, ctpolicy.ChromeCTPolicy{}, lcBuilder, mf) } } // ASN1MarshalSCTs serializes list of AssignedSCTs according to RFC6962 3.3 func ASN1MarshalSCTs(scts []*AssignedSCT) ([]byte, error) { if len(scts) == 0 { return nil, fmt.Errorf("ASN1MarshalSCTs requires positive number of SCTs, 0 provided") } unassignedSCTs := make([]*ct.SignedCertificateTimestamp, 0, len(scts)) for _, sct := range scts { unassignedSCTs = append(unassignedSCTs, sct.SCT) } sctList, err := x509util.MarshalSCTsIntoSCTList(unassignedSCTs) if err != nil { return nil, err } encdSCTList, err := tls.Marshal(*sctList) if err != nil { return nil, err } encoded, err := asn1.Marshal(encdSCTList) if err != nil { return nil, err } return encoded, nil } // Proxy wraps Log List updates watcher and Distributor running on fresh Log List. type Proxy struct { Init chan bool llRefreshInterval time.Duration rootsRefreshInterval time.Duration llWatcher *LogListManager distributorBuilder DistributorBuilder distMu sync.RWMutex // guards the distributor dist *Distributor distCancel context.CancelFunc // used to cancel distributor updates } // NewProxy creates an inactive Proxy instance. Call Run() to activate. func NewProxy(llm *LogListManager, db DistributorBuilder, mf monitoring.MetricFactory) *Proxy { var p Proxy p.llWatcher = llm p.distributorBuilder = db p.Init = make(chan bool, 1) p.rootsRefreshInterval = 24 * time.Hour if mf == nil { mf = monitoring.InertMetricFactory{} } proxyOnce.Do(func() { proxyInitMetrics(mf) }) return &p } // Run starts regular LogList checks and associated Distributor initialization. // Sends true via Init channel when init is complete. // Terminates upon context cancellation. func (p *Proxy) Run(ctx context.Context, llRefresh time.Duration, rootsRefresh time.Duration) { init := false p.llRefreshInterval = llRefresh p.rootsRefreshInterval = rootsRefresh p.llWatcher.Run(ctx, llRefresh) go func() { for { select { case <-ctx.Done(): if !init { close(p.Init) } return case llData := <-p.llWatcher.LLUpdates: logListUpdates.Inc() if err := p.restartDistributor(ctx, llData.List); err != nil { klog.Errorf("Unable to use Log-list:\n %v\n %v", err, llData.JSON) } else if !init { init = true p.Init <- true close(p.Init) } case err := <-p.llWatcher.Errors: klog.Error(err) } } }() } // restartDistributor activates new Distributor instance with Log List provided // and sets it as active. func (p *Proxy) restartDistributor(ctx context.Context, ll *loglist3.LogList) error { d, err := p.distributorBuilder(ll) if err != nil { // losing ll info. No good. return err } // Start refreshing roots periodically so they stay up-to-date. refreshCtx, refreshCancel := context.WithCancel(ctx) go schedule.Every(refreshCtx, p.rootsRefreshInterval, func(ectx context.Context) { if errs := d.RefreshRoots(ectx); len(errs) > 0 { for _, err := range errs { klog.Warning(err) } } }) p.distMu.Lock() defer p.distMu.Unlock() if p.distCancel != nil { p.distCancel() } p.dist = d p.distCancel = refreshCancel return nil } // AddPreChain passes call to underlying Distributor instance. func (p *Proxy) AddPreChain(ctx context.Context, rawChain [][]byte, loadPendingLogs bool) ([]*AssignedSCT, error) { if p.dist == nil { return []*AssignedSCT{}, fmt.Errorf("proxy distributor is not initialized. call Run()") } defer func(start time.Time) { rspLatency.Observe(time.Since(start).Seconds(), "add-pre-chain") }(time.Now()) return p.dist.AddPreChain(ctx, rawChain, loadPendingLogs) } // AddChain passes call to underlying Distributor instance. func (p *Proxy) AddChain(ctx context.Context, rawChain [][]byte, loadPendingLogs bool) ([]*AssignedSCT, error) { if p.dist == nil { return []*AssignedSCT{}, fmt.Errorf("proxy distributor is not initialized. call Run()") } defer func(start time.Time) { rspLatency.Observe(time.Since(start).Seconds(), "add-chain") }(time.Now()) return p.dist.AddChain(ctx, rawChain, loadPendingLogs) } google-certificate-transparency-go-2308f62/submission/proxy_server.go000066400000000000000000000110311462611535200261260ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "encoding/json" "fmt" "html/template" "net/http" "os" "strings" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/trillian/monitoring" ) // ProxyServer wraps Proxy and handles HTTP-requests for it. type ProxyServer struct { p *Proxy addTimeout time.Duration loadPendingLogs bool } // NewProxyServer creates ProxyServer instance. Call Run() to init. func NewProxyServer(logListPath string, dBuilder DistributorBuilder, reqTimeout time.Duration, mf monitoring.MetricFactory) *ProxyServer { s := &ProxyServer{addTimeout: reqTimeout} s.p = NewProxy(NewLogListManager(NewLogListRefresher(logListPath), mf), dBuilder, mf) return s } // Run starts regular Log list updates in the background, running until the // context is canceled. Blocks until initialization happens. func (s *ProxyServer) Run(ctx context.Context, logListRefreshInterval time.Duration, rootsRefreshInterval time.Duration, loadPendingLogs bool) { s.loadPendingLogs = loadPendingLogs s.p.Run(ctx, logListRefreshInterval, rootsRefreshInterval) <-s.p.Init } // SCTBatch represents JSON response to add-pre-chain method of proxy. type SCTBatch struct { SCTs []ct.SignedCertificateTimestamp `json:"scts"` } func marshalSCTs(scts []*AssignedSCT) ([]byte, error) { var jsonSCTsObj SCTBatch jsonSCTsObj.SCTs = make([]ct.SignedCertificateTimestamp, 0, len(scts)) for _, sct := range scts { jsonSCTsObj.SCTs = append(jsonSCTsObj.SCTs, *sct.SCT) } return json.Marshal(jsonSCTsObj) } // handleAddSomeChain is helper func choosing between AddChain and AddPreChain // based on asPreChain value func (s *ProxyServer) handleAddSomeChain(w http.ResponseWriter, r *http.Request, asPreChain bool) { if r.Method != http.MethodPost { http.NotFound(w, r) return } addChainReq, err := ctfe.ParseBodyAsJSONChain(r) if err != nil { rc := http.StatusBadRequest pre := "" if asPreChain { pre = "pre-" } http.Error(w, fmt.Sprintf("proxy: failed to parse add-%schain body: %s", pre, err), rc) return } ctx, cancel := context.WithTimeout(r.Context(), s.addTimeout) defer cancel() var scts []*AssignedSCT if asPreChain { scts, err = s.p.AddPreChain(ctx, addChainReq.Chain, s.loadPendingLogs) } else { scts, err = s.p.AddChain(ctx, addChainReq.Chain, s.loadPendingLogs) } if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } data, err := marshalSCTs(scts) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) fmt.Fprint(w, string(data)) } // HandleAddPreChain handles multiplexed add-pre-chain HTTP request. func (s *ProxyServer) HandleAddPreChain(w http.ResponseWriter, r *http.Request) { s.handleAddSomeChain(w, r, true /* asPreChain*/) } // HandleAddChain handles multiplexed add-chain HTTP request. func (s *ProxyServer) HandleAddChain(w http.ResponseWriter, r *http.Request) { s.handleAddSomeChain(w, r, false /* asPreChain*/) } func stringToHTML(s string) template.HTML { return template.HTML(strings.Replace(template.HTMLEscapeString(string(s)), "\n", "
", -1)) } // InfoData wraps data field required for info-page. type InfoData struct { PolicyName string LogListPath template.HTML LogListJSON template.HTML } // HandleInfo handles info-page request. func (s *ProxyServer) HandleInfo(w http.ResponseWriter, r *http.Request) { data := InfoData{ s.p.dist.policy.Name(), stringToHTML(s.p.llWatcher.Source()), stringToHTML(string(s.p.llWatcher.LastJSON())), } wd, err := os.Getwd() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } t, err := template.ParseFiles(wd + "/submission/view/info.html") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := t.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } google-certificate-transparency-go-2308f62/submission/proxy_test.go000066400000000000000000000162351462611535200256120ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "bytes" "context" "encoding/hex" "fmt" "os" "strings" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian/monitoring" ) // stubLogListRefresher produces error on each Refresh call. type stubLogListRefresher struct { } func (llr stubLogListRefresher) Refresh() (*LogListData, error) { return nil, fmt.Errorf("stub Log List Refresher produces no Log List") } func (llr stubLogListRefresher) LastJSON() []byte { return nil } func (llr stubLogListRefresher) Source() string { return "stub" } func stubLogListManager() *LogListManager { return NewLogListManager(stubLogListRefresher{}, nil) } var imf monitoring.InertMetricFactory func TestProxyRefreshLLErr(t *testing.T) { p := NewProxy(stubLogListManager(), GetDistributorBuilder(ChromeCTPolicy, NewStubLogClient, imf), imf) _, err := p.llWatcher.RefreshLogList(context.Background()) if err == nil { t.Errorf("p.RefreshLogList() on stubLogListRefresher expected to get error, got none") } } func TestProxyBrokenDistributor(t *testing.T) { p := NewProxy(stubLogListManager(), GetDistributorBuilder(ChromeCTPolicy, newNoLogClient, imf), imf) _, err := p.llWatcher.RefreshLogList(context.Background()) if err == nil { t.Errorf("p.RefreshLogList() on brokenDistributorFactory expected to get error, got none") } } // Stub for AddLogCLient interface type stubNoRootsLogClient struct { logURL string } func (m stubNoRootsLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubNoRootsLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubNoRootsLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) { return nil, fmt.Errorf("stubNoRootsLogClient cannot provide roots") } func buildStubNoRootsLogClient(log *loglist3.Log) (client.AddLogClient, error) { return stubNoRootsLogClient{logURL: log.URL}, nil } func TestProxyInitState(t *testing.T) { f, err := createTempFile(testdata.SampleLogList3) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", testdata.SampleLogList3, err) } defer func() { if err := os.Remove(f); err != nil { t.Fatalf("Operation to remove temp file failed: %v", err) } }() llr := NewLogListRefresher(f) p := NewProxy(NewLogListManager(llr, nil), GetDistributorBuilder(ChromeCTPolicy, buildStubNoRootsLogClient, imf), imf) p.Run(context.Background(), 100*time.Millisecond, time.Hour) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() Init: for { select { case <-ctx.Done(): t.Fatalf("p.Run() expected to send init signal, got none") case b, ok := <-p.Init: if !ok { t.Fatalf("p.Run() expected to send 'true' init signal via Init channel, but channel is closed") } if b != true { t.Fatalf("p.Run() expected to send 'true' init signal, got false") } break Init } } sampleLogListUpdate := strings.Replace(testdata.SampleLogList3, "ct.googleapis.com/racketeer/", "ct.googleapis.com/racketeer/v2/", 1) if err := os.WriteFile(f, []byte(sampleLogListUpdate), 0644); err != nil { t.Fatalf("unable to update Log-list data file: %q", err) } ctx, cancel = context.WithTimeout(context.Background(), time.Second) defer cancel() for { select { case <-ctx.Done(): return case _, ok := <-p.Init: if ok { t.Fatalf("p.Refresh() after initial p.Run() sent signal into Init-channel, expected none") } } } } // Helper func building slice of N AssignedSCTs. func buildAssignedSCTs(t *testing.T, n int) []*AssignedSCT { rawSCT := testdata.TestCertProof var sct ct.SignedCertificateTimestamp _, err := tls.Unmarshal(rawSCT, &sct) if err != nil { t.Fatalf("failed to tls-unmarshal test certificate proof: %s", err) } sampleLogURL := "ct.googleapis.com/racketeer/" assignedSCT := AssignedSCT{LogURL: sampleLogURL, SCT: &sct} assignedSCTs := make([]*AssignedSCT, n) for i := 0; i < n; i++ { assignedSCTs[i] = &assignedSCT } return assignedSCTs } // Helper func building slice of 1 AssignedSCTs containing one nil. func buildNilAssignedSCT() []*AssignedSCT { sampleLogURL := "ct.googleapis.com/racketeer/" assignedNilSCT := AssignedSCT{LogURL: sampleLogURL, SCT: nil} assignedSCTs := []*AssignedSCT{&assignedNilSCT} return assignedSCTs } func decodeValidHex(t *testing.T, s string) []byte { b, err := hex.DecodeString(s) if err != nil { t.Fatalf("Unable to hex-Decode %s", s) return nil } return b } func TestASN1MarshalSCT(t *testing.T) { tests := []struct { name string scts []*AssignedSCT want []byte wantErr bool }{ { name: "OneSCT", scts: buildAssignedSCTs(t, 1), want: decodeValidHex(t, "047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5"), wantErr: false, }, { name: "NoSCT", scts: buildAssignedSCTs(t, 0), want: nil, wantErr: true, }, { name: "TwoSCTs", scts: buildAssignedSCTs(t, 2), want: decodeValidHex(t, "0481f200f0007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5"), wantErr: false, }, { name: "NilSCT", scts: buildNilAssignedSCT(), want: nil, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := ASN1MarshalSCTs(test.scts) if (err != nil) && !test.wantErr { t.Fatalf("ASN1MarshalSCT() returned error %v", err) } if (err == nil) && test.wantErr { t.Fatalf("ASN1MarshalSCT() expected to return error, got none") } if !(bytes.Equal(got, test.want)) { t.Fatalf("ASN1MarshalSCT() returned unexpected output: \n got: %x\nwant: %x", got, test.want) } }) } } google-certificate-transparency-go-2308f62/submission/races.go000066400000000000000000000224171462611535200244660ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "fmt" "strings" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/ctpolicy" ) const ( // PostBatchInterval is duration between parallel batch call and subsequent // requests to Logs within group. // TODO(Mercurrent): optimize to avoid excessive requests. PostBatchInterval = time.Second ) // Submitter is interface wrapping Log-request-response cycle and any processing. type Submitter interface { SubmitToLog(ctx context.Context, logURL string, chain []ct.ASN1Cert, asPreChain bool) (*ct.SignedCertificateTimestamp, error) } // submissionResult holds outcome of a single-log submission. type submissionResult struct { sct *ct.SignedCertificateTimestamp err error } type groupState struct { Name string Success bool } // safeSubmissionState is a submission state-machine for set of Log-groups. // When some group is complete cancels all requests that are not needed by any // group. type safeSubmissionState struct { mu sync.Mutex logToGroups map[string]ctpolicy.GroupSet groupNeeds map[string]int results map[string]*submissionResult cancels map[string]context.CancelFunc } func newSafeSubmissionState(groups ctpolicy.LogPolicyData) *safeSubmissionState { var s safeSubmissionState s.logToGroups = ctpolicy.GroupByLogs(groups) s.groupNeeds = make(map[string]int) for _, g := range groups { s.groupNeeds[g.Name] = g.MinInclusions } s.results = make(map[string]*submissionResult) s.cancels = make(map[string]context.CancelFunc) return &s } // request includes empty submissionResult in the set, returns whether // the entry is requested for the first time. func (sub *safeSubmissionState) request(logURL string, cancel context.CancelFunc) bool { sub.mu.Lock() defer sub.mu.Unlock() if sub.results[logURL] != nil { // Already requested. return false } sub.results[logURL] = &submissionResult{} isAwaited := false for g := range sub.logToGroups[logURL] { if sub.groupNeeds[g] > 0 { isAwaited = true break } } if !isAwaited { // No groups expecting result from this Log. return false } sub.cancels[logURL] = cancel return true } // setResult processes SCT-result. Writes it down if it is error or awaited-SCT. // Re-calculates group-completion and cancels any running but // not-awaited-anymore Log-requests. func (sub *safeSubmissionState) setResult(logURL string, sct *ct.SignedCertificateTimestamp, err error) { sub.mu.Lock() defer sub.mu.Unlock() if sct == nil { sub.results[logURL] = &submissionResult{sct: sct, err: err} return } // If at least one group needs that SCT, result is set. Otherwise dumped. for groupName := range sub.logToGroups[logURL] { // Ignore the base group (All-logs) here to check separately. if groupName == ctpolicy.BaseName { continue } if sub.groupNeeds[groupName] > 0 { sub.results[logURL] = &submissionResult{sct: sct, err: err} } sub.groupNeeds[groupName]-- } // Check the base group (All-logs) only if sub.logToGroups[logURL][ctpolicy.BaseName] { if sub.results[logURL].sct != nil { // It is already processed in a non-base group, so we can reduce the groupNeeds for the base group as well. sub.groupNeeds[ctpolicy.BaseName]-- } else if sub.groupNeeds[ctpolicy.BaseName] > 0 { minInclusionsForOtherGroup := 0 for g, cnt := range sub.groupNeeds { if g != ctpolicy.BaseName && cnt > 0 { minInclusionsForOtherGroup += cnt } } // Set the result only if the base group still needs SCTs more than total counts // of minimum inclusions for other groups. if sub.groupNeeds[ctpolicy.BaseName] > minInclusionsForOtherGroup { sub.results[logURL] = &submissionResult{sct: sct, err: err} sub.groupNeeds[ctpolicy.BaseName]-- } } } // Cancel any pending Log-requests for which there are no more awaiting // Log-groups. for logURL, groupSet := range sub.logToGroups { isAwaited := false for g := range groupSet { if sub.groupNeeds[g] > 0 { isAwaited = true break } } if !isAwaited && sub.cancels[logURL] != nil { sub.cancels[logURL]() sub.cancels[logURL] = nil } } } // groupComplete returns true iff the specified group has all the SCTs it needs. func (sub *safeSubmissionState) groupComplete(groupName string) bool { sub.mu.Lock() defer sub.mu.Unlock() needs, ok := sub.groupNeeds[groupName] if !ok { return true } return needs <= 0 } func (sub *safeSubmissionState) collectSCTs() []*AssignedSCT { sub.mu.Lock() defer sub.mu.Unlock() scts := []*AssignedSCT{} for logURL, r := range sub.results { if r != nil && r.sct != nil { scts = append(scts, &AssignedSCT{LogURL: logURL, SCT: r.sct}) } } return scts } // postInterval calculates duration for consequent call. // For first parallelStart calls duration is 0, while every next one gets // additional dur interval. func postInterval(idx int, parallelStart int, dur time.Duration) time.Duration { if idx < parallelStart { return time.Duration(0) } return time.Duration(idx+1-parallelStart) * dur } // groupRace shuffles logs within the group, submits avoiding // duplicate-requests and collects responses. func groupRace(ctx context.Context, chain []ct.ASN1Cert, asPreChain bool, group *ctpolicy.LogGroupInfo, parallelStart int, state *safeSubmissionState, submitter Submitter) groupState { // Randomize the order in which we send requests to the logs in a group // so we maximize the distribution of logs we get SCTs from. session := group.GetSubmissionSession() type count struct{} counter := make(chan count, len(session)) countCall := func() { counter <- count{} } for i, logURL := range session { subCtx, cancel := context.WithCancel(ctx) go func(i int, logURL string) { defer countCall() timeoutchan := time.After(postInterval(i, parallelStart, PostBatchInterval)) select { case <-subCtx.Done(): return case <-timeoutchan: } if state.groupComplete(group.Name) { cancel() return } if firstRequested := state.request(logURL, cancel); !firstRequested { return } sct, err := submitter.SubmitToLog(subCtx, logURL, chain, asPreChain) // TODO(Mercurrent): verify SCT state.setResult(logURL, sct, err) }(i, logURL) } // Wait until either all logs within session are processed or context is // cancelled. for range session { select { case <-ctx.Done(): return groupState{Name: group.Name, Success: state.groupComplete(group.Name)} case <-counter: if state.groupComplete(group.Name) { return groupState{Name: group.Name, Success: true} } } } return groupState{Name: group.Name, Success: state.groupComplete(group.Name)} } func parallelNums(groups ctpolicy.LogPolicyData) map[string]int { nums := make(map[string]int) var subsetSum int for _, g := range groups { nums[g.Name] = g.MinInclusions if !g.IsBase { subsetSum += g.MinInclusions } } if _, hasBase := nums[ctpolicy.BaseName]; hasBase { if nums[ctpolicy.BaseName] >= subsetSum { nums[ctpolicy.BaseName] -= subsetSum } else { nums[ctpolicy.BaseName] = 0 } } return nums } // AssignedSCT represents SCT with logURL of log-producer. type AssignedSCT struct { LogURL string SCT *ct.SignedCertificateTimestamp } func completenessError(groupComplete map[string]bool) error { failedGroups := []string{} for name, success := range groupComplete { if !success { failedGroups = append(failedGroups, name) } } if len(failedGroups) > 0 { return fmt.Errorf("log-group(s) %s didn't receive enough SCTs", strings.Join(failedGroups, ", ")) } return nil } // GetSCTs picks required number of Logs according to policy-group logic and // collects SCTs from them. // Emits all collected SCTs even when any error produced. func GetSCTs(ctx context.Context, submitter Submitter, chain []ct.ASN1Cert, asPreChain bool, groups ctpolicy.LogPolicyData) ([]*AssignedSCT, error) { groupComplete := make(map[string]bool) for _, g := range groups { groupComplete[g.Name] = false } parallelNums := parallelNums(groups) // channel listening to group-completion (failure) events from each single group-race. groupEvents := make(chan groupState, len(groups)) submissions := newSafeSubmissionState(groups) for _, g := range groups { go func(g *ctpolicy.LogGroupInfo) { groupEvents <- groupRace(ctx, chain, asPreChain, g, parallelNums[g.Name], submissions, submitter) }(g) } // Terminates upon either all logs available are requested or required // number of SCTs is collected or upon context timeout. for i := 0; i < len(groups); i++ { select { case <-ctx.Done(): return submissions.collectSCTs(), completenessError(groupComplete) case g := <-groupEvents: groupComplete[g.Name] = g.Success } } return submissions.collectSCTs(), completenessError(groupComplete) } google-certificate-transparency-go-2308f62/submission/races_test.go000066400000000000000000000134211462611535200255200ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "regexp" "sync" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/ctpolicy" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" ) func testdataSCT() *ct.SignedCertificateTimestamp { var sct ct.SignedCertificateTimestamp if _, err := tls.Unmarshal(testdata.TestPreCertProof, &sct); err != nil { panic(err) } return &sct } type mockSubmitter struct { fixedDelay map[byte]time.Duration firstLetterURLReqNumber map[byte]int mu sync.Mutex } // Each request within same Log-group gets additional sleep period. func (ms *mockSubmitter) SubmitToLog(_ context.Context, logURL string, _ []ct.ASN1Cert, _ bool) (*ct.SignedCertificateTimestamp, error) { ms.mu.Lock() reqNum := ms.firstLetterURLReqNumber[logURL[0]] ms.firstLetterURLReqNumber[logURL[0]]++ ms.mu.Unlock() sct := testdataSCT() time.Sleep(time.Duration(500*reqNum)*time.Millisecond + ms.fixedDelay[logURL[0]]) return sct, nil } func evaluateSCTs(t *testing.T, got []*AssignedSCT, trail map[string]int) { t.Helper() for _, sct := range got { if _, ok := trail[ctpolicy.BaseName]; ok { trail[ctpolicy.BaseName]-- if trail[sct.LogURL[0:1]] > 0 { trail[sct.LogURL[0:1]]-- } } else { trail[sct.LogURL[0:1]]-- } } for groupName, count := range trail { // It's possible to get more SCTs for Log-group than minimally-required. // If group completion happened in-between Log-request and response. Or in case of group-intersection. if count > 0 { for _, s := range got { t.Errorf("%v\n", s.LogURL) } t.Errorf("Got %v. Received %d less SCTs from group %q than expected", got, count, groupName) } else if count < 0 { for _, s := range got { t.Errorf("%v\n", s.LogURL) } t.Errorf("Got %v. Received %d more SCTs from group %q than expected", got, -count, groupName) } } } func TestGetSCTs(t *testing.T) { testCases := []struct { name string sbMock Submitter groups ctpolicy.LogPolicyData resultTrail map[string]int errRegexp *regexp.Regexp }{ { name: "singleGroupOneSCT", sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)}, groups: ctpolicy.LogPolicyData{ "a": { Name: "a", LogURLs: map[string]bool{"a1.com": true, "a2.com": true}, MinInclusions: 1, IsBase: false, LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0}, }, }, resultTrail: map[string]int{"a": 1}, }, { name: "singleGroupMultiSCT", sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)}, groups: ctpolicy.LogPolicyData{ "a": { Name: "a", LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "a5.com": true}, MinInclusions: 3, IsBase: false, LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "a5.com": 1.0}, }, }, resultTrail: map[string]int{"a": 3}, }, { name: "chromeLike", sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0, 'b': 2 * time.Second}, firstLetterURLReqNumber: make(map[byte]int)}, groups: ctpolicy.LogPolicyData{ "a": { Name: "a", LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true}, MinInclusions: 1, IsBase: false, LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0}, }, "b": { Name: "b", LogURLs: map[string]bool{"b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true}, MinInclusions: 1, IsBase: false, LogWeights: map[string]float32{"b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0}, }, ctpolicy.BaseName: { Name: ctpolicy.BaseName, LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true}, MinInclusions: 5, IsBase: true, LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0}, }, }, resultTrail: map[string]int{"a": 1, "b": 1, ctpolicy.BaseName: 5}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() res, err := GetSCTs(ctx, tc.sbMock, []ct.ASN1Cert{{Data: []byte{0}}}, true, tc.groups) if tc.resultTrail != nil { evaluateSCTs(t, res, tc.resultTrail) } if tc.errRegexp == nil { if err != nil { t.Fatalf("GetSCTs() got err=%q want nil", err) } return } // If we reach here then we expected an error. if err == nil { t.Fatalf("GetSCTs() got err=nil want err matching: %q", tc.errRegexp) } if !tc.errRegexp.MatchString(err.Error()) { t.Errorf("GetSCTs() got err=%q want err matching: %q", err, tc.errRegexp) } }) } } google-certificate-transparency-go-2308f62/submission/server/000077500000000000000000000000001462611535200243425ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/server/main.go000066400000000000000000000056131462611535200256220ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // The submission_server runs (pre-)certs multi-Log submission complying with // CT-policy provided. package main import ( "context" "flag" "log" "net/http" "time" "github.com/google/certificate-transparency-go/submission" "github.com/google/trillian/monitoring/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "k8s.io/klog/v2" ) // Flags. var ( httpEndpoint = flag.String("http_endpoint", "localhost:5951", "Endpoint for HTTP (host:port)") logListPath = flag.String("loglist_path", "https://www.gstatic.com/ct/log_list/v3/log_list.json", "Path for list of CT Logs in JSON format") logListRefreshInterval = flag.Duration("loglist_refresh_interval", 24*time.Hour, "Interval between consecutive reads of Log-list") rootsRefreshInterval = flag.Duration("roots_refresh_interval", 24*time.Hour, "Interval between consecutive get-roots calls") policyType = flag.String("policy_type", "chrome", "CT-policy ") dryRun = flag.Bool("dry_run", false, "No real submissions done") addPreChainTimeout = flag.Duration("add_prechain_timeout", 10*time.Second, "Timeout for each add-prechain call") loadPendingQualifiedLogs = flag.Bool("load_pending_qualified_logs", true, "Whether to submit cert to one of Pending+Qualified Logs along main submission") ) func parsePolicyType() submission.CTPolicyType { if *policyType == "chrome" { return submission.ChromeCTPolicy } else if *policyType == "apple" { return submission.AppleCTPolicy } klog.Fatalf("flag policyType does not support value %q", *policyType) return submission.ChromeCTPolicy } func main() { klog.InitFlags(nil) flag.Parse() plc := parsePolicyType() lcb := submission.BuildLogClient if *dryRun { lcb = submission.NewStubLogClient } mf := prometheus.MetricFactory{} s := submission.NewProxyServer(*logListPath, submission.GetDistributorBuilder(plc, lcb, mf), *addPreChainTimeout, mf) s.Run(context.Background(), *logListRefreshInterval, *rootsRefreshInterval, *loadPendingQualifiedLogs) http.HandleFunc("/ct/v1/proxy/add-pre-chain/", s.HandleAddPreChain) http.HandleFunc("/ct/v1/proxy/add-chain/", s.HandleAddChain) http.Handle("/metrics", promhttp.Handler()) http.HandleFunc("/", s.HandleInfo) log.Fatal(http.ListenAndServe(*httpEndpoint, nil)) } google-certificate-transparency-go-2308f62/submission/server/prometheus.yml000066400000000000000000000003671462611535200272660ustar00rootroot00000000000000global: scrape_interval: 15s external_labels: monitor: 'proxy-monitor' scrape_configs: - job_name: 'submission-proxy' scrape_interval: 5s static_configs: - targets: ['localhost:5951'] # mathes default for proxy google-certificate-transparency-go-2308f62/submission/stub.go000066400000000000000000000064011462611535200243410ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "context" "crypto/sha256" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509util" ) type rootInfo struct { raw []byte filename string } // Stub for AddLogCLient interface type stubLogClient struct { logURL string rootsCerts map[string][]rootInfo } func (m stubLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { if _, ok := m.rootsCerts[m.logURL]; ok { return testSCT(m.logURL), nil } return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { if _, ok := m.rootsCerts[m.logURL]; ok { return testSCT(m.logURL), nil } return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) { roots := []ct.ASN1Cert{} certInfos, ok := m.rootsCerts[m.logURL] if !ok { return roots, nil } for _, certInfo := range certInfos { if len(certInfo.raw) > 0 { roots = append(roots, ct.ASN1Cert{Data: certInfo.raw}) continue } roots = append(roots, ct.ASN1Cert{Data: readCertFile(certInfo.filename)}) } return roots, nil } // readCertFile returns the first certificate it finds in file provided. func readCertFile(filename string) []byte { data, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { return nil } return data[0] } // TestSCT builds a mock SCT for given logURL. func testSCT(logURL string) *ct.SignedCertificateTimestamp { var keyID [sha256.Size]byte copy(keyID[:], logURL) return &ct.SignedCertificateTimestamp{ SCTVersion: ct.V1, LogID: ct.LogID{KeyID: keyID}, Timestamp: 1234, Extensions: []byte{}, Signature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.ECDSA, }, }, } } func newRootedStubLogClient(log *loglist3.Log, rCerts map[string][]rootInfo) (client.AddLogClient, error) { return stubLogClient{logURL: log.URL, rootsCerts: rCerts}, nil } func newEmptyStubLogClient(log *loglist3.Log) (client.AddLogClient, error) { return newRootedStubLogClient(log, map[string][]rootInfo{}) } // NewStubLogClient is builder for log-client stubs. Used for dry-runs and // testing. func NewStubLogClient(log *loglist3.Log) (client.AddLogClient, error) { return stubLogClient{logURL: log.URL, rootsCerts: map[string][]rootInfo{log.URL: {}}}, nil } google-certificate-transparency-go-2308f62/submission/testdata/000077500000000000000000000000001462611535200246455ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/testdata/another.cert000066400000000000000000000022371462611535200271700ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/submission/testdata/some.cert000066400000000000000000000034751462611535200265000ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFLjCCAxagAwIBAgIQNgEiBHAkH6lLUWKp42Ob1DANBgkqhkiG9w0BAQ0FADAWMRQwEgYDVQQDEwt lc2lnbml0Lm9yZzAeFw0xNDA2MjAxODM3NTRaFw0zMDA2MjAxODQ3NDZaMBYxFDASBgNVBAMTC2VzaW duaXQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtylZx/zTLxRDsok14XO0Z3PvW MIY4HWro0YLgCF8dYv3tUaNkmN3ghlQvY8UcByH2LMOBGiQAcMHxgEJ53cnWRyc2DjoGhkDkiPdS2Jt tNEB0B/XTaGvaHwJh2CSgIBbpZpWTaqGywbe7AgJQ81L8h7tZ4E6W8ZM0vt4mnzqkPBT+BmyjTXG/Mc GhYTQAsmdsYZDBAdB2Y4X1/RAyL0e9MHdSboRofhg+8d5MeC0VEIgHXU/R4f4wz/pSw0FI9xxWJR3UU K/qOWqNsVYZfmCu6+ksDQtezxSTAuymoL094Dwn+hnXb8RS6dEbIQ+b0bIHxxpypcxH7rBMIpQcbZ8J SqNVDZPI9QahKNPQMQiuBE66KlqbnLOj7lGBxsbpU2Dx8QL8W96op6dTGtniFyXqhuYN2UxDMNI+fb1 j9G7ENpoqvTVfjxa4RUU6uZ9ZygOiiOZD4P54vEQFteiu4OM+mWOm5Vll9yPXqHPc5oiCfyvCNVzfap qPoGbaCM6oQtcHdAca9VpE2eDTo36zfdFo31YYBOEjWNsfXwp8frNduS/L6gmWYrd91HeEoOVX2ZQKq BLp5ydW72xDSeCIr5kugqdY6whW80ugjLlc9mDd8/LEGQQKnrxzeeWdjiQG/WwcOse9GRktOzH2gvmk J+vY82z1jhrZP4REoA6T+aYGR8CAwEAAaN4MHYwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w HQYDVR0OBBYEFPOGsFKraD+/FoPAUXSf77qYfZHRMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAG CNxUCBBYEFEq/BT//OC3eNeJ4wEfNqJXdZRNpMA0GCSqGSIb3DQEBDQUAA4ICAQBEvh2kzI+1uoUx/e mM654QvpM6WtgQSJMubKwKeBY5UNgwwNpwmtswiEKzdZwBiGb1xEehPrAKz0d7aiIIEOonYEohIV6s zl0+F56nN16813n1lPsCjdLSA8fjgf28jvlTKcrLRqeyCn4APadh6g7/FRiGcmIxEFPf/VNTUBZ7l4e 2zzb06PxCq8oDaOsbAVYXQz8A0KX50KURZrdC2knUg1HX0J/orVpdaQ9UZYVNp2WAbe9vYTCCF5Fdtz NU+nJDojpDxF5guMe9bifL3YTvd87YQwsH7+o+UbtHX4lG8VsSfmvvJulNBY6RtzZEpZvyRWIvQahM9 qTrzFpsxl4wyPSBDPLDZ6YvVWsXvU4PqLOWTbPdq4BB24P9kFxeYjEe/rDQ8bd1/V/OFZTEM0rxdZDD N9vWnybzl8xL5VmNLDGl1u6JrOVvCzVAWP++L9l5UTusQI/BPSMebz6msd8vhTluD4jQIba1/6zOwfB raFgCIktCT3GEIiyt59x3rdSirLyjzmeQA9NkwoG/GqlFlSdWmQCK/sCL+z050rqjL0kEwIl/D6ncCX fBvhCpCmcrIlZFruyeOlsISZ410T1w/pLK8OXhbCr13Gb7A5jhv1nn811cQaR7XUXhcn6Wq/VV/oQZL unBYvoYOs3dc8wpBabPrrRhkdNmN6Rib6TvMg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/submission/view/000077500000000000000000000000001462611535200240065ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/submission/view/info.html000066400000000000000000000003601462611535200256260ustar00rootroot00000000000000

Submission Proxy

Policy applied: {{.PolicyName}}
Log list extracted from address: {{.LogListPath}}
Log list:
{{.LogListJSON}}
google-certificate-transparency-go-2308f62/testdata/000077500000000000000000000000001462611535200224525ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/testdata/argon.cfg000066400000000000000000000040161462611535200242420ustar00rootroot00000000000000shard { uri: "https://ct.googleapis.com/logs/argon2017" public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004Tm|\211\335\352\235\360\272_\364m`z7O\002%\277\034\366o\205\256\257\025\337in\355\333\251\232)\227\362\231v\036\3463F\036'\364\276p\335Y\327\272\317\376\320r\216\260W\017\2357\211b\243" not_after_start: not_after_limit: } shard { uri: "https://ct.googleapis.com/logs/argon2018" public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\322\000U\005\255\325G\264\031\273\315\225\373)\327X=x$\315\316F\235\3732\324qN`\002%^Y>\327\324\003\270mChh~\350\240e\013>nqY\2227\276\251\350\361\243+\344\331\rUh" not_after_start: not_after_limit: } shard { uri: "https://ct.googleapis.com/logs/argon2019" public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004#s\020\233\341\363^\366\230ki\225\226\020x\316I\333\264\004\374q,Z\222`h%\300J\032\241\260a-\033\207\024\251\272\360\0013Y\035\0050\351B\025\347U\327*\370\264\242\272E\311F\221\207V" not_after_start: not_after_limit: } shard { uri: "https://ct.googleapis.com/logs/argon2020" public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\351LdX\373vcQ2\030c\325\262\273\355\352\377^;$n/5R\213\2645\232\255\234\025\250i \352P\030\314" not_after_start: not_after_limit: } shard { uri: "https://ct.googleapis.com/logs/argon2021" public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004M\340fd\352\363d\2528\305\211-\307\330\010\331\310Dq\355\334\303\373[\257\234d\241\tf\204\035|h\247\354\304?\214\234\202\340\030\331t\024\351\264y\201\242\224Ub\363\234\013D\203\241+\311q+" not_after_start: not_after_limit: } google-certificate-transparency-go-2308f62/testdata/certs.go000066400000000000000000000523041462611535200241250ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testdata import ( "encoding/hex" ) const ( // CACertPEM is a CA cert: // Certificate: // Data: // Version: 3 (0x2) // Serial Number: 0 (0x0) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:d5:8a:68:53:62:10:a2:71:19:93:6e:77:83:21: // 18:1c:2a:40:13:c6:d0:7b:8c:76:eb:91:57:d3:d0: // fb:4b:3b:51:6e:ce:cb:d1:c9:8d:91:c5:2f:74:3f: // ab:63:5d:55:09:9c:d1:3a:ba:f3:1a:e5:41:44:24: // 51:a7:4c:78:16:f2:24:3c:f8:48:cf:28:31:cc:e6: // 7b:a0:4a:5a:23:81:9f:3c:ba:37:e6:24:d9:c3:bd: // b2:99:b8:39:dd:fe:26:31:d2:cb:3a:84:fc:7b:b2: // b5:c5:2f:cf:c1:4f:ff:40:6f:5c:d4:46:69:cb:b2: // f7:cf:df:86:fb:6a:b9:d1:b1 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:TRUE // Signature Algorithm: sha1WithRSAEncryption // 06:08:cc:4a:6d:64:f2:20:5e:14:6c:04:b2:76:f9:2b:0e:fa: // 94:a5:da:f2:3a:fc:38:06:60:6d:39:90:d0:a1:ea:23:3d:40: // 29:57:69:46:3b:04:66:61:e7:fa:1d:17:99:15:20:9a:ea:2e: // 0a:77:51:76:41:12:27:d7:c0:03:07:c7:47:0e:61:58:4f:d7: // 33:42:24:72:7f:51:d6:90:bc:47:a9:df:35:4d:b0:f6:eb:25: // 95:5d:e1:89:3c:4d:d5:20:2b:24:a2:f3:e4:40:d2:74:b5:4e: // 1b:d3:76:26:9c:a9:62:89:b7:6e:ca:a4:10:90:e1:4f:3b:0a: // 94:2e CACertPEM = "-----BEGIN CERTIFICATE-----\n" + "MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" + "YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" + "MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu\n" + "c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf\n" + "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7\n" + "jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP\n" + "KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL\n" + "svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk\n" + "tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG\n" + "A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO\n" + "MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB\n" + "/zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt\n" + "OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy\n" + "f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP\n" + "OwqULg==\n" + "-----END CERTIFICATE-----" // TestCertPEM is a leaf certificate signed by CACertPEM. // Certificate: // Data: // Version: 3 (0x2) // Serial Number: 6 (0x6) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4: // 19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04: // 16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34: // 1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56: // 46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc: // a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02: // ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d: // d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65: // b1:a3:7a:8c:67:62:53:dc:c1 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // Signature Algorithm: sha1WithRSAEncryption // 17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2: // 70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae: // fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f: // 71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5: // 06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58: // 2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71: // 90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da: // c0:e6 TestCertPEM = "-----BEGIN CERTIFICATE-----\n" + "MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" + "YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" + "MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" + "c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" + "CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ\n" + "4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx\n" + "/KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj\n" + "eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY\n" + "4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE\n" + "BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG\n" + "A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq\n" + "hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6\n" + "n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah\n" + "ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g==\n" + "-----END CERTIFICATE-----\n" // TestPreCertPEM is a pre-certificate signed by CACertPEM. // Certificate: // Data: // Version: 3 (0x2) // Serial Number: 7 (0x7) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c: // 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1: // ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b: // 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4: // dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10: // cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87: // 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d: // c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a: // 05:b0:14:09:ff:5d:d8:7e:b5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // 1.3.6.1.4.1.11129.2.4.3: critical // .. // Signature Algorithm: sha1WithRSAEncryption // 02:a1:c3:9e:01:5a:f5:4d:ff:02:3c:33:60:87:5f:ff:34:37: // 55:2f:1f:09:01:bd:c2:54:31:5f:33:72:b7:23:fb:15:fb:ce: // cc:4d:f4:71:a0:ce:4d:8c:54:65:5d:84:87:97:fb:28:1e:3d: // fa:bb:46:2d:2c:68:4b:05:6f:ea:7b:63:b4:70:ff:16:6e:32: // d4:46:06:35:b3:d2:bc:6d:a8:24:9b:26:30:e7:1f:c3:4f:08: // f2:3d:d4:ee:22:8f:8f:74:f6:3d:78:63:11:dd:0a:58:11:40: // 5f:90:6c:ca:2c:2d:3e:eb:fc:81:99:64:eb:d8:cf:7c:08:86: // 3f:be TestPreCertPEM = "-----BEGIN CERTIFICATE-----\n" + "MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" + "YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" + "MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" + "c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" + "CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" + "BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" + "EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" + "FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3\n" + "tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE\n" + "BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG\n" + "A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor\n" + "BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg\n" + "h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL\n" + "BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf\n" + "kGzKLC0+6/yBmWTr2M98CIY/vg==\n" + "-----END CERTIFICATE-----\n" // TestEmbeddedCertPEM is a certificate containing an embedded SCT that // corresponds to TestPreCertProof. // Certificate: // Data: // Version: 3 (0x2) // Serial Number: 7 (0x7) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C = GB, O = Certificate Transparency CA, ST = Wales, L = Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C = GB, O = Certificate Transparency, ST = Wales, L = Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c: // 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1: // ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b: // 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4: // dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10: // cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87: // 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d: // c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a: // 05:b0:14:09:ff:5d:d8:7e:b5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // CT Precertificate SCTs: // Signed Certificate Timestamp: // Version : v1 (0x0) // Log ID : DF:1C:2E:C1:15:00:94:52:47:A9:61:68:32:5D:DC:5C: // 79:59:E8:F7:C6:D3:88:FC:00:2E:0B:BD:3F:74:D7:64 // Timestamp : Apr 5 17:04:16.275 2013 GMT // Extensions: none // Signature : ecdsa-with-SHA256 // 30:45:02:20:48:2F:67:51:AF:35:DB:A6:54:36:BE:1F: // D6:64:0F:3D:BF:9A:41:42:94:95:92:45:30:28:8F:A3: // E5:E2:3E:06:02:21:00:E4:ED:C0:DB:3A:C5:72:B1:E2: // F5:E8:AB:6A:68:06:53:98:7D:CF:41:02:7D:FE:FF:A1: // 05:51:9D:89:ED:BF:08 // Signature Algorithm: sha1WithRSAEncryption // 8a:0c:4b:ef:09:9d:47:92:79:af:a0:a2:8e:68:9f:91:e1:c4: // 42:1b:e2:d2:69:a2:ea:6c:a4:e8:21:5d:de:dd:ca:15:04:a1: // 1e:7c:87:c4:b7:7e:80:f0:e9:79:03:52:68:f2:7c:a2:0e:16: // 68:04:ae:55:6f:31:69:81:f9:6a:39:4a:b7:ab:fd:3e:25:5a: // c0:04:45:13:fe:76:57:0c:67:95:ab:e4:70:31:33:d3:03:f8: // 9f:3a:fa:6b:bc:fc:51:73:19:df:d9:5b:93:42:41:21:1f:63: // 40:35:c3:d0:78:30:7a:68:c6:07:5a:2e:20:c8:9f:36:b8:91: // 0c:a0 TestEmbeddedCertPEM = "-----BEGIN CERTIFICATE-----\n" + "MIIDWTCCAsKgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" + "YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" + "MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" + "c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" + "CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" + "BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" + "EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" + "FAn/Xdh+tQIDAQABo4IBOjCCATYwHQYDVR0OBBYEFCAxVBryXAX/2GWLaEN5T16Q\n" + "Nve0MH0GA1UdIwR2MHSAFF+diA3Ic+ZU1PgN2OawwSS0R8NVoVmkVzBVMQswCQYD\n" + "VQQGEwJHQjEkMCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4w\n" + "DAYDVQQIEwVXYWxlczEQMA4GA1UEBxMHRXJ3IFdlboIBADAJBgNVHRMEAjAAMIGK\n" + "BgorBgEEAdZ5AgQCBHwEegB4AHYA3xwuwRUAlFJHqWFoMl3cXHlZ6PfG04j8AC4L\n" + "vT9012QAAAE92yffkwAABAMARzBFAiBIL2dRrzXbplQ2vh/WZA89v5pBQpSVkkUw\n" + "KI+j5eI+BgIhAOTtwNs6xXKx4vXoq2poBlOYfc9BAn3+/6EFUZ2J7b8IMA0GCSqG\n" + "SIb3DQEBBQUAA4GBAIoMS+8JnUeSea+goo5on5HhxEIb4tJpoupspOghXd7dyhUE\n" + "oR58h8S3foDw6XkDUmjyfKIOFmgErlVvMWmB+Wo5Srer/T4lWsAERRP+dlcMZ5Wr\n" + "5HAxM9MD+J86+mu8/FFzGd/ZW5NCQSEfY0A1w9B4MHpoxgdaLiDInza4kQyg\n" + "-----END CERTIFICATE-----\n" // TestInvalidEmbeddedCertPEM is a certificate that contains an SCT that is // not for the corresponding pre-certificate. The SCT embedded corresponds // to TestInvalidProof. // Certificate: // Data: // Version: 3 (0x2) // Serial Number: 7 (0x7) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C = GB, O = Certificate Transparency CA, ST = Wales, L = Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C = GB, O = Certificate Transparency, ST = Wales, L = Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c: // 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1: // ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b: // 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4: // dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10: // cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87: // 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d: // c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a: // 05:b0:14:09:ff:5d:d8:7e:b5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // CT Precertificate SCTs: // Signed Certificate Timestamp: // Version : v1 (0x0) // Log ID : DF:1C:2E:C1:15:00:94:52:47:A9:61:68:32:5D:DC:5C: // 79:59:E8:F7:C6:D3:88:FC:00:2E:0B:BD:3F:74:D7:64 // Timestamp : Apr 5 17:04:17.060 2013 GMT // Extensions: none // Signature : ecdsa-with-SHA256 // 30:45:02:21:00:A6:D3:45:17:F3:39:2D:9E:C5:D2:57: // AD:F1:C5:97:DC:45:BD:4C:D3:B7:38:56:C6:16:A9:FB: // 99:E5:AE:75:A8:02:20:5E:26:C8:D1:C7:E2:22:FE:8C: // DA:29:BA:EB:04:A8:34:EE:97:D3:4F:D8:17:18:F1:AA: // E0:CD:66:F4:B8:A9:3F // Signature Algorithm: sha1WithRSAEncryption // af:28:89:06:38:b0:12:6f:dd:64:5d:d0:62:80:f8:10:6c:ec: // 49:4c:f8:22:86:0a:29:d4:f1:7e:6a:a5:7c:5a:58:b2:96:cc: // 90:c6:db:f1:22:10:4b:7f:4a:76:d6:fd:df:f2:1a:41:3a:9e: // e7:88:7e:32:a3:c7:a2:07:3c:e6:af:ae:01:b4:1a:a2:3d:ce: // 98:f3:ab:5e:c7:5c:e7:59:fa:7c:cc:ab:4f:fa:7a:a7:3e:7d: // 98:38:77:c6:d0:f1:de:cd:dd:37:49:00:59:b7:91:90:b2:7f: // 85:94:2b:7c:c8:b2:3c:bf:90:30:68:5d:21:43:c4:95:a5:39: // 6d:9f TestInvalidEmbeddedCertPEM = "-----BEGIN CERTIFICATE-----\n" + "MIIDWTCCAsKgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" + "YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" + "MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" + "c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" + "CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" + "BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" + "EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" + "FAn/Xdh+tQIDAQABo4IBOjCCATYwHQYDVR0OBBYEFCAxVBryXAX/2GWLaEN5T16Q\n" + "Nve0MH0GA1UdIwR2MHSAFF+diA3Ic+ZU1PgN2OawwSS0R8NVoVmkVzBVMQswCQYD\n" + "VQQGEwJHQjEkMCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4w\n" + "DAYDVQQIEwVXYWxlczEQMA4GA1UEBxMHRXJ3IFdlboIBADAJBgNVHRMEAjAAMIGK\n" + "BgorBgEEAdZ5AgQCBHwEegB4AHYA3xwuwRUAlFJHqWFoMl3cXHlZ6PfG04j8AC4L\n" + "vT9012QAAAE92yfipAAABAMARzBFAiEAptNFF/M5LZ7F0let8cWX3EW9TNO3OFbG\n" + "Fqn7meWudagCIF4myNHH4iL+jNopuusEqDTul9NP2BcY8argzWb0uKk/MA0GCSqG\n" + "SIb3DQEBBQUAA4GBAK8oiQY4sBJv3WRd0GKA+BBs7ElM+CKGCinU8X5qpXxaWLKW\n" + "zJDG2/EiEEt/SnbW/d/yGkE6nueIfjKjx6IHPOavrgG0GqI9zpjzq17HXOdZ+nzM\n" + "q0/6eqc+fZg4d8bQ8d7N3TdJAFm3kZCyf4WUK3zIsjy/kDBoXSFDxJWlOW2f\n" + "-----END CERTIFICATE-----\n" ) var ( // TestCertProof is a TLS-encoded ct.SignedCertificateTimestamp corresponding // to TestCertPEM. TestCertProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" + "640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc49" + "37f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06b" + "db38a069469aa86fe12e18bb7cc45689a2c0187ef5a5") // TestPreCertProof is a TLS-encoded ct.SignedCertificateTimestamp // corresponding to TestPreCertPEM TestPreCertProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" + "640000013ddb27df9300000403004730450220482f6751af35dba65436be1fd6" + "640f3dbf9a41429495924530288fa3e5e23e06022100e4edc0db3ac572b1e2f5" + "e8ab6a680653987dcf41027dfeffa105519d89edbf08") // TestInvalidProof is a TLS-encoded ct.SignedCertificateTimestamp // corresponding to the invalid SCT embedded in TestInvalidEmbeddedCertPEM. TestInvalidProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" + "640000013ddb27e2a40000040300473045022100a6d34517f3392d9ec5d257ad" + "f1c597dc45bd4cd3b73856c616a9fb99e5ae75a802205e26c8d1c7e222fe8cda" + "29baeb04a834ee97d34fd81718f1aae0cd66f4b8a93f") // TestCertB64LeafHash is the base64-encoded leaf hash of TestCertPEM with // TestCertProof as the corresponding SCT. TestCertB64LeafHash = "BKZLZGMbAnDW0gQWjNJNCyS2IgweWn76YW3tFlu3AuY=" // TestPreCertB64LeafHash is the base64-encoded leaf hash of TestPreCertPEM // with TestPreCertProof as the corresponding SCT. TestPreCertB64LeafHash = "mbk+d5FDW3oNZJCbOi828rqC3hf+qhigTWVGcXEZq1U=" ) func dh(h string) []byte { r, err := hex.DecodeString(h) if err != nil { panic(err) } return r } google-certificate-transparency-go-2308f62/testdata/gossip-root.cert000066400000000000000000000015131462611535200256160ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICQTCCAeegAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBpMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMRkwFwYDVQQDExBUZXN0R29zc2lwZXJSb290MB4XDTE4 MDIyNTA4MTA1M1oXDTI4MDIyMzA4MTA1M1owaTELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxDDAK BgNVBAsTA0VuZzEZMBcGA1UEAxMQVGVzdEdvc3NpcGVyUm9vdDBZMBMGByqGSM49 AgEGCCqGSM49AwEHA0IABOqzZufPSU6hMJOIbljkjklDvQKBGYW9VenI6i7HSiyH ccPUuh3F3fbbe2MrLtuRCjH7nqvcELPqBJsL3IVgQJijfTB7MB0GA1UdDgQWBBRq 6hoXslGgHhrCVJMu4jrYlksyZjAfBgNVHSMEGDAWgBRq6hoXslGgHhrCVJMu4jrY lksyZjASBgNVHRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwICBDAVBgNVHSUE DjAMBgorBgEEAdZ5AgQGMAoGCCqGSM49BAMCA0gAMEUCIQCQCnWTIOlC6LqkcdH0 fWZeNo5E3AaZBb9Tkv76ET2fJAIgOeGJvfiiOIlDV41/bIOg5eTHb/fxg80TCQBe 6ia6ZS8= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/testdata/gossiper.privkey.pem000066400000000000000000000004461462611535200265040ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,559BE893ECD7A88C UOwSw+WlSv5LLiBZSCnR12FX13Hk1a3vavdpUde4W4qawQgJSMqLa3it8Lfadtnm GfGVqN+gF5KFiNWxgMs2qRcbdQ03ZlMmoH8Z8jPQHXvKseJvME8tZQWPvJ15rbXh G9Lcx7NYlm0miHPy3ras8ci58HSDqz9Z7yOdgHzPpiU= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/testdata/keys.go000066400000000000000000000304511462611535200237570ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testdata holds data and utility functions needed for various Go tests. package testdata import "encoding/hex" // Hashes of text string 'abcd' with various algorithms; check with either "sum input" or "openssl dgst - input" const ( // AbcdMD5 is 'abcd' hashed with MD5 AbcdMD5 = "e2fc714c4727ee9395f324cd2e7f331f" // AbcdSHA1 is 'abcd' hashed with SHA1 AbcdSHA1 = "81fe8bfe87576c3ecb22426f8e57847382917acf" // AbcdSHA224 is 'abcd' hashed with SHA224 AbcdSHA224 = "a76654d8e3550e9a2d67a0eeb6c67b220e5885eddd3fde135806e601" // AbcdSHA256 is 'abcd' hashed with SHA256 AbcdSHA256 = "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589" // AbcdSHA384 is 'abcd' hashed with SHA384 AbcdSHA384 = "1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b" // AbcdSHA512 is 'abcd' hashed with SHA512 AbcdSHA512 = "d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f" ) const ( // LogPublicKeyB64 is an ECDSA key copied from test/testdata/ct-server-key-public.pem LogPublicKeyB64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3UyEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==" // LogPublicKeyPEM is an ECDSA key copied from test/testdata/ct-server-key-public.pem LogPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U\n" + "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==\n" + "-----END PUBLIC KEY-----\n" // RsaPrivateKeyPEM was generated with: // openssl genpkey -algorithm RSA -out rsa.privkey.pem -pkeyopt rsa_keygen_bits:2048 RsaPrivateKeyPEM = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP8SJM5SBIbFpF\n" + "dRAUEERo35E4iFXfT5SVAzBIqATkCpf3LfdFZgssPeliBdTtgdvT/CWC2m6aVXYZ\n" + "eScAJOauTpGDyJ7OXj7Yhx1q6u28NuQleU3UQK5QYb7VOjeIse7oTh+THf5IK5T5\n" + "esyh+ioybEYBXXMQetHc4YbXAq06crNT2BP+AxQ4z27xu4X5CKLwwVMa2xwMCFDC\n" + "Y7mxQzEhFv8+HbOtM0IQMSSyh6+o0w4Yzt6i/TUOytZb1D7WHOEMpVTd1CMbXjN0\n" + "tFZvoO0V+n0OV3oT1E5AvwujVguUJblQiapbL890/nZbBtP3xMoFLLhc7sX7N2vR\n" + "1wLWsjOVAgMBAAECggEBAL+frUZDV86l20JqsFhs7T3f2MnKCahyg7AWciZif69O\n" + "e+BTQa14bg9lNm8YhLIim1vs3vyJIqei3eR3mxMs7k/vI3XYKVBv1WZgjSF8QXzS\n" + "8Mf/01MoD/sPOHby4T5dCpaVd89xMmV7lBubqHwUN1KkKJcVcPXc2Qy94C6/zrcu\n" + "Vb7Q91ugTvvhyalWEN02M3tzHEn4cXrnMLWPmTgxb7YtTMRIbJFx2um7/W9G5D+1\n" + "ZtKMjwt+P/yXynx3Sa555MjjJ1/LUyMSbQZoLE0SGOigz1VQnP8nFlpvXUHzktiA\n" + "IBPBBQSsEoS8H3z6WThwY72O795/i2l5mf7EUUAa2/kCgYEA+W7tJPg69KWHKn6E\n" + "gGZKIDTxvp3vRxjLedp7D7cu6bVPCBVIHrPQdmp/mneY6x7Air0a9gCVbh32iCrm\n" + "UrSBCio4i6jtqtna2T1Gc0z63K3UrVQPR0Z6Swq1XDOI6/I23ibxPr12/XXWWYlA\n" + "wkPwvewJVrjtMoP1dkAKVbyzImcCgYEA1WqS0xg7BL4iNGFPrrSme2blZOgga83x\n" + "CtY9dnWAs0IvdxsuItCDaFVJm3oU18ohirEMzqZvbRlwMlUawpd75hCNWyVF4cRo\n" + "+l1RvzOlC/7QXX/E+Kv7sTEUYH7hJyS46QhOtC1/ndAyObk9c2VNJmERfMwIHNm9\n" + "BfSN8yrq1KMCgYEA94fCZPbGIvSFj4EgYv+fvhhscvrucsLDYniTuUPTlXAtLttX\n" + "x8gwLuN/ID5hjarl7oi90bVAlZe8iOLx0M96YykFFmuc9/jcOsuZN2EEbq0/KocJ\n" + "5nSldgT5d7dYwLWNB6bjr5x8Egm3nwEbN+4OYZt0pRA9q+zSUfg5iV4K8y8CgYA7\n" + "S9YposTbJ3zXcuYx022iQc+gvsIrUdgUO7xuCm3M4KnRfRLPh4HLXk8KTNw3rKiv\n" + "IUw+qo2xEW1T/sNlp7M8FANCfNOyy+CjF4ScDFxiPdVk9RgkQ5y1+b4ApaAnQRPD\n" + "Y5SCiVW44lziHu7M/it2a2fxdbsXUQQtAGrkUltW4wKBgAPZyEGi4V2wryOswmkC\n" + "I29TbC6ChnUOvqJfSfz536eIi41D1Ua2Y/VlAK1ud7K8ilr/M4VoRjHqbPivR1Uk\n" + "RU7nO3cWRBuDBjgZWeAq5WFIMPbm9gRlaTECI6EFtAOI1GcHOsOBecd3PBiQNXvj\n" + "YyGD1wSrHQwDcnxtP1rqEQDV\n" + "-----END PRIVATE KEY-----\n" // RsaPublicKeyPEM was generated from above with: // openssl rsa -pubout -in rsa.privkey.pem -out rsa.pubkey.pem RsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/EiTOUgSGxaRXUQFBBE\n" + "aN+ROIhV30+UlQMwSKgE5AqX9y33RWYLLD3pYgXU7YHb0/wlgtpumlV2GXknACTm\n" + "rk6Rg8iezl4+2IcdaurtvDbkJXlN1ECuUGG+1To3iLHu6E4fkx3+SCuU+XrMofoq\n" + "MmxGAV1zEHrR3OGG1wKtOnKzU9gT/gMUOM9u8buF+Qii8MFTGtscDAhQwmO5sUMx\n" + "IRb/Ph2zrTNCEDEksoevqNMOGM7eov01DsrWW9Q+1hzhDKVU3dQjG14zdLRWb6Dt\n" + "Ffp9Dld6E9ROQL8Lo1YLlCW5UImqWy/PdP52WwbT98TKBSy4XO7F+zdr0dcC1rIz\n" + "lQIDAQAB\n" + "-----END PUBLIC KEY-----\n" // RsaSignedAbcdHex was generated with: // echo -n "abcd" > sign.input // openssl sha256 -sign rsa.privkey.pem -out sign.rsa sign.input // Verify with: // openssl sha256 -verify rsa.pubkey.pem -signature sign.rsa sign.input RsaSignedAbcdHex = "4692f17adad62324a38a786d9b05eb7facf95c277729bc23e8d4f4fe25c0ac0e" + "9115f14ffd4dc6c00e6b2717677e96ed4da9e2226745db5c2d14ec8b4f96959e" + "a43ca73e3ab801d4e76b0c0599c741b9701dd0c22b20cc7a294fccce97d2eda2" + "72448830a19b0b1075f962d3bcf65922f81f4747dde1a2883ffce12941927af6" + "25549ea7b4f745a95f5558822af434cc7471ea5a21cce656a7667d29bff3e6f2" + "fb3d48bbee1a60116f0dd429d5691b008482e9ec42538bcf2692e1a685e836c8" + "49316fb3c49b58bd5a0c33f0256b09d59af08a018fe64cd31051cb9e9a1d3fde" + "e186bd80bfe304abbbec2ac18d3bee09465cb69f2638728aba4b9886a252768a" // DsaPrivateKeyPEM was generated with: // openssl dsaparam -out dsa.param.pem 2048; openssl gendsa dsa.param.pem -out dsa.privkey.pem DsaPrivateKeyPEM = "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIDVwIBAAKCAQEA3Q72qQc0ZM2eUjvZ/XOROQTbhmjzuZdzY7kd+82n54NPeqVz\n" + "+FWmk26INd7pUNbn/TjniN5+pC2pK+p9alFLE19bOvsY4x7Ugwrpd9sQIea3W4Q7\n" + "H9PaEW+BooFzT/feC9TmnU0ynwoNhZB33Cr3k3PFt/69lmyrkd91wTpXrstrQurU\n" + "h4baL5l6t2/JT3TxpKx6xe19hWqqFsBLu/j+kE2o2Aw1TVLHg4/74TJLfqAkWnCI\n" + "u9QL0uGQnW80kRfJnT+tUJXrXMpg1HxK7CsDEW5xsAWF370Is0UPaP7jRru6FiSN\n" + "ObcVhSqCIs+QJIWie9cRtwniwhoi4z6AfGeEYQIhAOkSzAsWja1fwB486/LCW+zv\n" + "DJKiCGLTalimfawzHQHFAoIBAQDJvCujEZ/WeQFLlP/0zS+2DjqdaxnKj1xuXfoL\n" + "LOocvMw06dnrQNmDu/nBQiYSfzf7zjwpnx+AVjBQssK0jXs0l7gw52FyCIztDejT\n" + "/ybMVudwKUGudxAEjOclOSQ7XVpPd4p3UMy+E6pkJ8VRiuNFDibYTD+O3XhFTj2l\n" + "6LqV/KcB2fWV1fUqfxax2vpcwe4clTJbJXUgJC3mYmUXv1U6z0hliDz2WMhbOyea\n" + "1fs31zYvIMyk2wJFJl7+EbXb8BgYcQt1FE4c14fuFnincAW0So+xBZ5DtiTja4lm\n" + "DE0OAI2hhLslSQ7rGwWt2zIKSFLtsWf78CP1wigJvWcUVhazAoIBAFAhxgtOaV8G\n" + "FJxs6A3TtXEmmrq3i5/w3/8l7skWamcHvcwrvw37g5KQY+N+85JZszHPjmUzEq/o\n" + "NOLjF6zq7+oRtWbOR3Mvc2hHsHg86tE/HAkBIu2G73c9UuskzGSMec4F6TyWKHvC\n" + "26F2cvu9aA42v/8q1d/QQs3+LrtARGhyFCqnBVNSZmZj5ngR0kLGUfs/LQklpGFe\n" + "VciXK57RxHSPo15lnKiHW9kCte7tGEg9Eber8paMz67dQDuj1yRkS5H7JXqRZjIP\n" + "UrICWGGk1vluQsuoyI/Nu8/tL7Im24xZH6XHBm4F+aijl9XlAJiKXfkCLIaxYSdc\n" + "4PXquTwtRLICIQCijrpD+UhidGFLPZCCLzBC239PrP7PabSVurWXHh5iYw==\n" + "-----END DSA PRIVATE KEY-----\n" // DsaPrivateKeyPKCS8PEM is a copy of the private key above, encoded in PKCS#8 format. // Generated from above with: // openssl pkcs8 -topk8 -nocrypt -in dsa.privkey.pem -out dsa.privkey.pkcs8.pem DsaPrivateKeyPKCS8PEM = "-----BEGIN PRIVATE KEY-----\n" + "MIICZgIBADCCAjoGByqGSM44BAEwggItAoIBAQDdDvapBzRkzZ5SO9n9c5E5BNuG\n" + "aPO5l3NjuR37zafng096pXP4VaaTbog13ulQ1uf9OOeI3n6kLakr6n1qUUsTX1s6\n" + "+xjjHtSDCul32xAh5rdbhDsf09oRb4GigXNP994L1OadTTKfCg2FkHfcKveTc8W3\n" + "/r2WbKuR33XBOleuy2tC6tSHhtovmXq3b8lPdPGkrHrF7X2FaqoWwEu7+P6QTajY\n" + "DDVNUseDj/vhMkt+oCRacIi71AvS4ZCdbzSRF8mdP61QletcymDUfErsKwMRbnGw\n" + "BYXfvQizRQ9o/uNGu7oWJI05txWFKoIiz5AkhaJ71xG3CeLCGiLjPoB8Z4RhAiEA\n" + "6RLMCxaNrV/AHjzr8sJb7O8MkqIIYtNqWKZ9rDMdAcUCggEBAMm8K6MRn9Z5AUuU\n" + "//TNL7YOOp1rGcqPXG5d+gss6hy8zDTp2etA2YO7+cFCJhJ/N/vOPCmfH4BWMFCy\n" + "wrSNezSXuDDnYXIIjO0N6NP/JsxW53ApQa53EASM5yU5JDtdWk93indQzL4TqmQn\n" + "xVGK40UOJthMP47deEVOPaXoupX8pwHZ9ZXV9Sp/FrHa+lzB7hyVMlsldSAkLeZi\n" + "ZRe/VTrPSGWIPPZYyFs7J5rV+zfXNi8gzKTbAkUmXv4RtdvwGBhxC3UUThzXh+4W\n" + "eKdwBbRKj7EFnkO2JONriWYMTQ4AjaGEuyVJDusbBa3bMgpIUu2xZ/vwI/XCKAm9\n" + "ZxRWFrMEIwIhAKKOukP5SGJ0YUs9kIIvMELbf0+s/s9ptJW6tZceHmJj\n" + "-----END PRIVATE KEY-----\n" // DsaPublicKeyPEM was generated from above with: // openssl dsa -in dsa.privkey.pem -pubout -out dsa.pubkey.pem DsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MIIDRzCCAjoGByqGSM44BAEwggItAoIBAQDdDvapBzRkzZ5SO9n9c5E5BNuGaPO5\n" + "l3NjuR37zafng096pXP4VaaTbog13ulQ1uf9OOeI3n6kLakr6n1qUUsTX1s6+xjj\n" + "HtSDCul32xAh5rdbhDsf09oRb4GigXNP994L1OadTTKfCg2FkHfcKveTc8W3/r2W\n" + "bKuR33XBOleuy2tC6tSHhtovmXq3b8lPdPGkrHrF7X2FaqoWwEu7+P6QTajYDDVN\n" + "UseDj/vhMkt+oCRacIi71AvS4ZCdbzSRF8mdP61QletcymDUfErsKwMRbnGwBYXf\n" + "vQizRQ9o/uNGu7oWJI05txWFKoIiz5AkhaJ71xG3CeLCGiLjPoB8Z4RhAiEA6RLM\n" + "CxaNrV/AHjzr8sJb7O8MkqIIYtNqWKZ9rDMdAcUCggEBAMm8K6MRn9Z5AUuU//TN\n" + "L7YOOp1rGcqPXG5d+gss6hy8zDTp2etA2YO7+cFCJhJ/N/vOPCmfH4BWMFCywrSN\n" + "ezSXuDDnYXIIjO0N6NP/JsxW53ApQa53EASM5yU5JDtdWk93indQzL4TqmQnxVGK\n" + "40UOJthMP47deEVOPaXoupX8pwHZ9ZXV9Sp/FrHa+lzB7hyVMlsldSAkLeZiZRe/\n" + "VTrPSGWIPPZYyFs7J5rV+zfXNi8gzKTbAkUmXv4RtdvwGBhxC3UUThzXh+4WeKdw\n" + "BbRKj7EFnkO2JONriWYMTQ4AjaGEuyVJDusbBa3bMgpIUu2xZ/vwI/XCKAm9ZxRW\n" + "FrMDggEFAAKCAQBQIcYLTmlfBhScbOgN07VxJpq6t4uf8N//Je7JFmpnB73MK78N\n" + "+4OSkGPjfvOSWbMxz45lMxKv6DTi4xes6u/qEbVmzkdzL3NoR7B4POrRPxwJASLt\n" + "hu93PVLrJMxkjHnOBek8lih7wtuhdnL7vWgONr//KtXf0ELN/i67QERochQqpwVT\n" + "UmZmY+Z4EdJCxlH7Py0JJaRhXlXIlyue0cR0j6NeZZyoh1vZArXu7RhIPRG3q/KW\n" + "jM+u3UA7o9ckZEuR+yV6kWYyD1KyAlhhpNb5bkLLqMiPzbvP7S+yJtuMWR+lxwZu\n" + "Bfmoo5fV5QCYil35AiyGsWEnXOD16rk8LUSy\n" + "-----END PUBLIC KEY-----\n" // DsaSignedAbcdHex was generated with: // echo -n "abcd" > sign.input // openssl dgst -dss1 -sign dsa.privkey.pem -out sign.dsa sign.input // Note that this includes randomization so will give different results each time. // Verify with: // openssl dgst -dss1 -verify dsa.pubkey.pem -signature sign.dsa sign.input DsaSignedAbcdHex = "3045022100c287211dec54eab597ed5264f6fdf57faf651909914c533d42f0e3" + "2809e9141402205f34cc4b8ca12ebdf025bf36bbe1ff7d807d4cfe951ac1329a" + "35a39f5e971f10" // EcdsaPrivateKeyPEM was generated with: // openssl ecparam -genkey -name prime256v1 -noout -out ecdsa.privkey.pem EcdsaPrivateKeyPEM = "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIHg0hg3vLqLVI7wv2y0cCk+kmwuwoKsMZqzqbjqP1AtjoAoGCCqGSM49\n" + "AwEHoUQDQgAEjHs6Rw7KFt8Wd2ZcioZi7eZY5nodUXMnCWUhZzsGVsPaexqUyPSr\n" + "9cQgrCe7MPRLJ524AO6rREqfs7FKt85++A==\n" + "-----END EC PRIVATE KEY-----\n" // EcdsaPrivateKeyPKCS8PEM is a copy of the private key above, encoded in PKCS#8 format. // Generated from above with: // openssl pkcs8 -topk8 -nocrypt -in ecdsa.privkey.pem -out ecdsa.privkey.pkcs8.pem EcdsaPrivateKeyPKCS8PEM = "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeDSGDe8uotUjvC/b\n" + "LRwKT6SbC7CgqwxmrOpuOo/UC2OhRANCAASMezpHDsoW3xZ3ZlyKhmLt5ljmeh1R\n" + "cycJZSFnOwZWw9p7GpTI9Kv1xCCsJ7sw9EsnnbgA7qtESp+zsUq3zn74\n" + "-----END PRIVATE KEY-----\n" // EcdsaPublicKeyPEM was generated from above with: // openssl ec -in ecdsa.privkey.pem -pubout -out ecdsa.pubkey.pem EcdsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjHs6Rw7KFt8Wd2ZcioZi7eZY5nod\n" + "UXMnCWUhZzsGVsPaexqUyPSr9cQgrCe7MPRLJ524AO6rREqfs7FKt85++A==\n" + "-----END PUBLIC KEY-----\n" // EcdsaSignedAbcdHex was generated with: // echo -n "abcd" > sign.input // openssl dgst -sha256 -sign ecdsa.privkey.pem -out sign.ecdsa sign.input // Note that this includes randomization so will give different results each time. // Verify with: // openssl dgst -sha256 -verify ecdsa.pubkey.pem -signature sign.ecdsa sign.input EcdsaSignedAbcdHex = "304502202e0204dadf2baa35e426b66ab8cd32a9ba33421187645a9110512e3e" + "d1b8007b022100ae83f5f993ab3af6f46bb09b4f15d07cc582b03e1353879ada" + "ce68796fe537e5" ) // FromHex decodes a hex string to a byte array, and panics on error; only suitable for // use in test code. func FromHex(hexdata string) []byte { data, err := hex.DecodeString(hexdata) if err != nil { panic("non hex data: " + err.Error()) } return data } google-certificate-transparency-go-2308f62/testdata/loglist3.go000066400000000000000000000012711462611535200245420ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testdata import _ "embed" //go:embed loglist3.json var SampleLogList3 string google-certificate-transparency-go-2308f62/testdata/loglist3.json000066400000000000000000000067571462611535200251240ustar00rootroot00000000000000{ "version": "9.4", "log_list_timestamp": "2022-05-06T12:55:11Z", "operators": [ { "name": "Google", "email": [ "google-ct-logs@googlegroups.com" ], "logs": [ { "description": "Google 'Aviator' log", "log_id": "aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q=", "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q==", "url": "https://ct.googleapis.com/aviator/", "mmd": 86400, "state":{ "readonly": { "timestamp": "2016-11-30T13:24:18.33Z", "final_tree_head": { "sha256_root_hash": "LcGcZRsm+LGYmrlyC5LXhV1T6OD8iH5dNlb0sEJl9bA=", "tree_size": 46466472 } } } }, { "description": "Google 'Icarus' log", "log_id": "KTxRllTIOWW6qlD8WAfUt2+/WHopctykwwz05UVH9Hg=", "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA==", "url": "https://ct.googleapis.com/icarus/", "mmd": 86400, "state": { "usable": { "timestamp": "2018-02-27T00:00:00Z" } } }, { "description": "Google 'Racketeer' log", "log_id": "7kEv4llINIlh4vPgjGgugT7A/3cLbXUXF2OvMBT/l2g=", "key": "Hy2TPTZ2yq9ASMmMZiB9SZEUx5WNH5G0Ft5Tm9vKMcPXA+ic/Ap3gg6fXzBJR8zLkt5lQjvKMdbHYMGv7yrsZg==", "url": "https://ct.googleapis.com/racketeer/", "mmd": 86400 }, { "description": "Google 'Rocketeer' log", "log_id": "7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=", "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==", "url": "https://ct.googleapis.com/rocketeer/", "mmd": 86400, "state": { "usable": { "timestamp": "2018-02-27T00:00:00Z" } } }, { "description": "Google 'Argon2020' log", "log_id": "sh4FzIuizYogTodm+Su5iiUgZ2va+nDnsklTLe+LkF4=", "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Tx2p1yKY4015NyIYvdrk36es0uAc1zA4PQ+TGRY+3ZjUTIYY9Wyu+3q/147JG4vNVKLtDWarZwVqGkg6lAYzA==", "url": "https://ct.googleapis.com/logs/argon2020/", "mmd": 86400, "state": { "qualified": { "timestamp": "2018-02-27T00:00:00Z" } }, "temporal_interval": { "start_inclusive": "2018-02-27T00:00:00Z", "end_exclusive": "2020-01-01T00:00:00Z" } } ] }, { "name": "Bob's CT Log Shop", "email": [ "bob@example.com" ], "logs": [ { "description": "Bob's Dubious Log", "log_id": "zbUXm3/BwEb+6jETaj+PAC5hgvr4iW/syLL1tatgSQA=", "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2MNvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA==", "url": "https://log.bob.io", "mmd": 86400, "state": { "retired": { "timestamp": "2016-04-15T00:00:00Z" } }, "temporal_interval": { "start_inclusive": "2014-11-07T12:00:00Z", "end_exclusive": "2015-03-07T12:00:00Z" } } ] } ] } google-certificate-transparency-go-2308f62/testdata/test-cert.pem000066400000000000000000000060701462611535200250720ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 6 (0x6) Signature Algorithm: sha1WithRSAEncryption Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen Validity Not Before: Jun 1 00:00:00 2012 GMT Not After : Jun 1 00:00:00 2022 GMT Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: 00:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4: 19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04: 16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34: 1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56: 46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc: a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02: ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d: d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65: b1:a3:7a:8c:67:62:53:dc:c1 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2 X509v3 Authority Key Identifier: keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen serial:00 X509v3 Basic Constraints: CA:FALSE Signature Algorithm: sha1WithRSAEncryption 17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2: 70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae: fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f: 71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5: 06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58: 2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71: 90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da: c0:e6 -----BEGIN CERTIFICATE----- MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ 4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx /KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY 4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6 n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/testdata/test-cert.proof000066400000000000000000000001661462611535200254360ustar00rootroot00000000000000.RGah2]\yYӈ. ?td='G0E `n\-Z II7N߿Sl!rmak8iFo.|V~google-certificate-transparency-go-2308f62/third_party/000077500000000000000000000000001462611535200231725ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/third_party/prometheus/000077500000000000000000000000001462611535200253655ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/third_party/prometheus/LICENSE000066400000000000000000000261351462611535200264010ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. google-certificate-transparency-go-2308f62/third_party/prometheus/console_libs/000077500000000000000000000000001462611535200300405ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/third_party/prometheus/console_libs/menu.lib000066400000000000000000000027531462611535200315030ustar00rootroot00000000000000{{/* Navbar, should be passed . */}} {{ define "navbar" }} {{ end }} {{/* LHS menu, should be passed . */}} {{ define "menu" }}
    {{ template "_menuItem" (args . "trillian.html" "Trillian Overview") }} {{ template "_menuItem" (args . "writes.html" "Write Path") }} {{ template "_menuItem" (args . "reads.html" "Read Path") }} {{ template "_menuItem" (args . "signer.html" "Signer") }}
{{ end }} {{/* Helper, pass (args . path name) */}} {{ define "_menuItem" }}
  • {{ .arg2 }}
  • {{ end }} {{/* Bottom of all pages. */}} {{ define "tail" }} {{ end }} google-certificate-transparency-go-2308f62/third_party/prometheus/console_libs/prom.lib000066400000000000000000000130361462611535200315100ustar00rootroot00000000000000{{/* vim: set ft=html: */}} {{/* Load Prometheus console library JS/CSS. Should go in */}} {{ define "prom_console_head" }} {{ end }} {{/* Top of all pages. */}} {{ define "head" }} {{ template "prom_console_head" }} {{ template "navbar" . }} {{ template "menu" . }} {{ end }} {{ define "__prom_query_drilldown_noop" }}{{ . }}{{ end }} {{ define "humanize" }}{{ humanize . }}{{ end }} {{ define "humanizeNoSmallPrefix" }}{{ if and (lt . 1.0) (gt . -1.0) }}{{ printf "%.3g" . }}{{ else }}{{ humanize . }}{{ end }}{{ end }} {{ define "humanize1024" }}{{ humanize1024 . }}{{ end }} {{ define "humanizeDuration" }}{{ humanizeDuration . }}{{ end }} {{ define "humanizeTimestamp" }}{{ humanizeTimestamp . }}{{ end }} {{ define "printf.1f" }}{{ printf "%.1f" . }}{{ end }} {{ define "printf.3g" }}{{ printf "%.3g" . }}{{ end }} {{/* prom_query_drilldown (args expr suffix? renderTemplate?) Displays the result of the expression, with a link to /graph for it. renderTemplate is the name of the template to use to render the value. */}} {{ define "prom_query_drilldown" }} {{ $expr := .arg0 }}{{ $suffix := (or .arg1 "") }}{{ $renderTemplate := (or .arg2 "__prom_query_drilldown_noop") }} {{ with query $expr }}{{tmpl $renderTemplate ( . | first | value )}}{{ $suffix }}{{ else }}-{{ end }} {{ end }} {{ define "prom_path" }}/consoles/{{ .Path }}?{{ range $param, $value := .Params }}{{ $param }}={{ $value }}&{{ end }}{{ end }}" {{ define "prom_right_table_head" }}
    {{ end }} {{ define "prom_right_table_tail" }}
    {{ end }} {{/* RHS table head, pass job name. Should be used after prom_right_table_head. */}} {{ define "prom_right_table_job_head" }} {{ . }} {{ template "prom_query_drilldown" (args (printf "sum(up{job='%s'})" .)) }} / {{ template "prom_query_drilldown" (args (printf "count(up{job='%s'})" .)) }} CPU {{ template "prom_query_drilldown" (args (printf "avg by(job)(irate(process_cpu_seconds_total{job='%s'}[5m]))" .) "s/s" "humanizeNoSmallPrefix") }} Memory {{ template "prom_query_drilldown" (args (printf "avg by(job)(process_resident_memory_bytes{job='%s'})" .) "B" "humanize1024") }} {{ end }} {{ define "prom_content_head" }}
    {{ template "prom_graph_timecontrol" . }} {{ end }} {{ define "prom_content_tail" }}
    {{ end }} {{ define "prom_graph_timecontrol" }}
    {{ end }} {{/* Bottom of all pages. */}} {{ define "tail" }} {{ end }} google-certificate-transparency-go-2308f62/tls/000077500000000000000000000000001462611535200214435ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/tls/hash_test.go000066400000000000000000000046561462611535200237670ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "encoding/hex" "strings" "testing" "github.com/google/certificate-transparency-go/testdata" ) func TestGenerateHash(t *testing.T) { var tests = []struct { in string // hex encoded algo HashAlgorithm want string // hex encoded errstr string }{ // Empty hash values {"", MD5, "d41d8cd98f00b204e9800998ecf8427e", ""}, {"", SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", ""}, {"", SHA224, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""}, {"", SHA256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, {"", SHA384, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", ""}, {"", SHA512, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", ""}, {"", 999, "", "unsupported"}, // Hashes of "abcd". {"61626364", MD5, testdata.AbcdMD5, ""}, {"61626364", SHA1, testdata.AbcdSHA1, ""}, {"61626364", SHA224, testdata.AbcdSHA224, ""}, {"61626364", SHA256, testdata.AbcdSHA256, ""}, {"61626364", SHA384, testdata.AbcdSHA384, ""}, {"61626364", SHA512, testdata.AbcdSHA512, ""}, } for _, test := range tests { got, _, err := generateHash(test.algo, testdata.FromHex(test.in)) if test.errstr != "" { if err == nil { t.Errorf("generateHash(%s)=%s,nil; want error %q", test.in, hex.EncodeToString(got), test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("generateHash(%s)=nil,%q; want error %q", test.in, test.errstr, err.Error()) } continue } if err != nil { t.Errorf("generateHash(%s)=nil,%q; want %s", test.in, err, test.want) } else if hex.EncodeToString(got) != test.want { t.Errorf("generateHash(%s)=%s,nil; want %s", test.in, hex.EncodeToString(got), test.want) } } } google-certificate-transparency-go-2308f62/tls/signature.go000066400000000000000000000110221462611535200237670ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "crypto" "crypto/dsa" //nolint:staticcheck "crypto/ecdsa" _ "crypto/md5" // For registration side-effect "crypto/rand" "crypto/rsa" _ "crypto/sha1" // For registration side-effect _ "crypto/sha256" // For registration side-effect _ "crypto/sha512" // For registration side-effect "errors" "fmt" "log" "math/big" "github.com/google/certificate-transparency-go/asn1" ) type dsaSig struct { R, S *big.Int } func generateHash(algo HashAlgorithm, data []byte) ([]byte, crypto.Hash, error) { var hashType crypto.Hash switch algo { case MD5: hashType = crypto.MD5 case SHA1: hashType = crypto.SHA1 case SHA224: hashType = crypto.SHA224 case SHA256: hashType = crypto.SHA256 case SHA384: hashType = crypto.SHA384 case SHA512: hashType = crypto.SHA512 default: return nil, hashType, fmt.Errorf("unsupported Algorithm.Hash in signature: %v", algo) } hasher := hashType.New() if _, err := hasher.Write(data); err != nil { return nil, hashType, fmt.Errorf("failed to write to hasher: %v", err) } return hasher.Sum([]byte{}), hashType, nil } // VerifySignature verifies that the passed in signature over data was created by the given PublicKey. func VerifySignature(pubKey crypto.PublicKey, data []byte, sig DigitallySigned) error { hash, hashType, err := generateHash(sig.Algorithm.Hash, data) if err != nil { return err } switch sig.Algorithm.Signature { case RSA: rsaKey, ok := pubKey.(*rsa.PublicKey) if !ok { return fmt.Errorf("cannot verify RSA signature with %T key", pubKey) } if err := rsa.VerifyPKCS1v15(rsaKey, hashType, hash, sig.Signature); err != nil { return fmt.Errorf("failed to verify rsa signature: %v", err) } case DSA: dsaKey, ok := pubKey.(*dsa.PublicKey) if !ok { return fmt.Errorf("cannot verify DSA signature with %T key", pubKey) } var dsaSig dsaSig rest, err := asn1.Unmarshal(sig.Signature, &dsaSig) if err != nil { return fmt.Errorf("failed to unmarshal DSA signature: %v", err) } if len(rest) != 0 { log.Printf("Garbage following signature %q", rest) } if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 { return errors.New("DSA signature contained zero or negative values") } if !dsa.Verify(dsaKey, hash, dsaSig.R, dsaSig.S) { return errors.New("failed to verify DSA signature") } case ECDSA: ecdsaKey, ok := pubKey.(*ecdsa.PublicKey) if !ok { return fmt.Errorf("cannot verify ECDSA signature with %T key", pubKey) } var ecdsaSig dsaSig rest, err := asn1.Unmarshal(sig.Signature, &ecdsaSig) if err != nil { return fmt.Errorf("failed to unmarshal ECDSA signature: %v", err) } if len(rest) != 0 { log.Printf("Garbage following signature %q", rest) } if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 { return errors.New("ECDSA signature contained zero or negative values") } if !ecdsa.Verify(ecdsaKey, hash, ecdsaSig.R, ecdsaSig.S) { return errors.New("failed to verify ECDSA signature") } default: return fmt.Errorf("unsupported Algorithm.Signature in signature: %v", sig.Algorithm.Hash) } return nil } // CreateSignature builds a signature over the given data using the specified hash algorithm and private key. func CreateSignature(privKey crypto.PrivateKey, hashAlgo HashAlgorithm, data []byte) (DigitallySigned, error) { var sig DigitallySigned sig.Algorithm.Hash = hashAlgo hash, hashType, err := generateHash(sig.Algorithm.Hash, data) if err != nil { return sig, err } switch privKey := privKey.(type) { case rsa.PrivateKey: sig.Algorithm.Signature = RSA sig.Signature, err = rsa.SignPKCS1v15(rand.Reader, &privKey, hashType, hash) return sig, err case ecdsa.PrivateKey: sig.Algorithm.Signature = ECDSA var ecdsaSig dsaSig ecdsaSig.R, ecdsaSig.S, err = ecdsa.Sign(rand.Reader, &privKey, hash) if err != nil { return sig, err } sig.Signature, err = asn1.Marshal(ecdsaSig) return sig, err default: return sig, fmt.Errorf("unsupported private key type %T", privKey) } } google-certificate-transparency-go-2308f62/tls/signature_test.go000066400000000000000000000143641462611535200250420ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls_test import ( "crypto" "encoding/pem" mathrand "math/rand" "reflect" "strings" "testing" "time" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) func TestVerifySignature(t *testing.T) { var tests = []struct { pubKey crypto.PublicKey in string // hex encoded hashAlgo tls.HashAlgorithm sigAlgo tls.SignatureAlgorithm errstr string sig string // hex encoded }{ {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", 99, tls.ECDSA, "unsupported Algorithm.Hash", "1234"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, 99, "unsupported Algorithm.Signature", "1234"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.DSA, "cannot verify DSA", "1234"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "cannot verify ECDSA", "1234"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "verification error", "1234"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "cannot verify ECDSA", "1234"}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.RSA, "cannot verify RSA", "1234"}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.ECDSA, "cannot verify ECDSA", "1234"}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "failed to unmarshal DSA signature", "1234"}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "failed to verify DSA signature", "3006020101020101eeff"}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "zero or negative values", "3006020100020181"}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "cannot verify RSA", "1234"}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.DSA, "cannot verify DSA", "1234"}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "failed to unmarshal ECDSA signature", "1234"}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "failed to verify ECDSA signature", "3006020101020101eeff"}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "zero or negative values", "3006020100020181"}, {PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "", testdata.RsaSignedAbcdHex}, {PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "", testdata.DsaSignedAbcdHex}, {PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "", testdata.EcdsaSignedAbcdHex}, } for _, test := range tests { algo := tls.SignatureAndHashAlgorithm{Hash: test.hashAlgo, Signature: test.sigAlgo} signed := tls.DigitallySigned{Algorithm: algo, Signature: testdata.FromHex(test.sig)} err := tls.VerifySignature(test.pubKey, testdata.FromHex(test.in), signed) if test.errstr != "" { if err == nil { t.Errorf("VerifySignature(%s)=nil; want %q", test.in, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("VerifySignature(%s)=%q; want %q", test.in, err.Error(), test.errstr) } continue } if err != nil { t.Errorf("VerifySignature(%s)=%q; want nil", test.in, err) } } } func TestCreateSignatureVerifySignatureRoundTrip(t *testing.T) { var tests = []struct { privKey crypto.PrivateKey pubKey crypto.PublicKey hashAlgo tls.HashAlgorithm }{ {PEM2PrivKey(testdata.RsaPrivateKeyPEM), PEM2PK(testdata.RsaPublicKeyPEM), tls.SHA256}, {PEM2PrivKey(testdata.EcdsaPrivateKeyPKCS8PEM), PEM2PK(testdata.EcdsaPublicKeyPEM), tls.SHA256}, } seed := time.Now().UnixNano() r := mathrand.New(mathrand.NewSource(seed)) for _, test := range tests { for j := 0; j < 1; j++ { dataLen := 10 + r.Intn(100) data := make([]byte, dataLen) _, _ = r.Read(data) sig, err := tls.CreateSignature(test.privKey, test.hashAlgo, data) if err != nil { t.Errorf("CreateSignature(%T, %v) failed with: %q", test.privKey, test.hashAlgo, err.Error()) continue } if err := tls.VerifySignature(test.pubKey, data, sig); err != nil { t.Errorf("VerifySignature(%T, %v) failed with: %q", test.pubKey, test.hashAlgo, err) } } } } func TestCreateSignatureFailures(t *testing.T) { var tests = []struct { privKey crypto.PrivateKey hashAlgo tls.HashAlgorithm in string // hex encoded errstr string }{ {PEM2PrivKey(testdata.EcdsaPrivateKeyPKCS8PEM), 99, "abcd", "unsupported Algorithm.Hash"}, {nil, tls.SHA256, "abcd", "unsupported private key type"}, } for _, test := range tests { if sig, err := tls.CreateSignature(test.privKey, test.hashAlgo, testdata.FromHex(test.in)); err == nil { t.Errorf("CreateSignature(%T, %v)=%v,nil; want error %q", test.privKey, test.hashAlgo, sig, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("CreateSignature(%T, %v)=nil,%q; want error %q", test.privKey, test.hashAlgo, err.Error(), test.errstr) } } } func PEM2PK(s string) crypto.PublicKey { p, _ := pem.Decode([]byte(s)) if p == nil { panic("no PEM block found in " + s) } pubKey, _ := x509.ParsePKIXPublicKey(p.Bytes) if pubKey == nil { panic("public key not parsed from " + s) } return pubKey } func PEM2PrivKey(s string) crypto.PrivateKey { p, _ := pem.Decode([]byte(s)) if p == nil { panic("no PEM block found in " + s) } // Try various different private key formats one after another. if rsaPrivKey, err := x509.ParsePKCS1PrivateKey(p.Bytes); err == nil { return *rsaPrivKey } if pkcs8Key, err := x509.ParsePKCS8PrivateKey(p.Bytes); err == nil { if reflect.TypeOf(pkcs8Key).Kind() == reflect.Ptr { pkcs8Key = reflect.ValueOf(pkcs8Key).Elem().Interface() } return pkcs8Key } return nil } google-certificate-transparency-go-2308f62/tls/tls.go000066400000000000000000000532101462611535200225750ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package tls implements functionality for dealing with TLS-encoded data, // as defined in RFC 5246. This includes parsing and generation of TLS-encoded // data, together with utility functions for dealing with the DigitallySigned // TLS type. package tls import ( "bytes" "encoding/binary" "fmt" "reflect" "strconv" "strings" ) // This file holds utility functions for TLS encoding/decoding data // as per RFC 5246 section 4. // A structuralError suggests that the TLS data is valid, but the Go type // which is receiving it doesn't match. type structuralError struct { field string msg string } func (e structuralError) Error() string { var prefix string if e.field != "" { prefix = e.field + ": " } return "tls: structure error: " + prefix + e.msg } // A syntaxError suggests that the TLS data is invalid. type syntaxError struct { field string msg string } func (e syntaxError) Error() string { var prefix string if e.field != "" { prefix = e.field + ": " } return "tls: syntax error: " + prefix + e.msg } // Uint24 is an unsigned 3-byte integer. type Uint24 uint32 // Enum is an unsigned integer. type Enum uint64 var ( uint8Type = reflect.TypeOf(uint8(0)) uint16Type = reflect.TypeOf(uint16(0)) uint24Type = reflect.TypeOf(Uint24(0)) uint32Type = reflect.TypeOf(uint32(0)) uint64Type = reflect.TypeOf(uint64(0)) enumType = reflect.TypeOf(Enum(0)) ) // Unmarshal parses the TLS-encoded data in b and uses the reflect package to // fill in an arbitrary value pointed at by val. Because Unmarshal uses the // reflect package, the structs being written to must use exported fields // (upper case names). // // The mappings between TLS types and Go types is as follows; some fields // must have tags (to indicate their encoded size). // // TLS Go Required Tags // opaque byte / uint8 // uint8 byte / uint8 // uint16 uint16 // uint24 tls.Uint24 // uint32 uint32 // uint64 uint64 // enum tls.Enum size:S or maxval:N // Type []Type minlen:N,maxlen:M // opaque[N] [N]byte / [N]uint8 // uint8[N] [N]byte / [N]uint8 // struct { } struct { } // select(T) { // case e1: Type *T selector:Field,val:e1 // } // // TLS variants (RFC 5246 s4.6.1) are only supported when the value of the // associated enumeration type is available earlier in the same enclosing // struct, and each possible variant is marked with a selector tag (to // indicate which field selects the variants) and a val tag (to indicate // what value of the selector picks this particular field). // // For example, a TLS structure: // // enum { e1(1), e2(2) } EnumType; // struct { // EnumType sel; // select(sel) { // case e1: uint16 // case e2: uint32 // } data; // } VariantItem; // // would have a corresponding Go type: // // type VariantItem struct { // Sel tls.Enum `tls:"maxval:2"` // Data16 *uint16 `tls:"selector:Sel,val:1"` // Data32 *uint32 `tls:"selector:Sel,val:2"` // } // // TLS fixed-length vectors of types other than opaque or uint8 are not supported. // // For TLS variable-length vectors that are themselves used in other vectors, // create a single-field structure to represent the inner type. For example, for: // // opaque InnerType<1..65535>; // struct { // InnerType inners<1,65535>; // } Something; // // convert to: // // type InnerType struct { // Val []byte `tls:"minlen:1,maxlen:65535"` // } // type Something struct { // Inners []InnerType `tls:"minlen:1,maxlen:65535"` // } // // If the encoded value does not fit in the Go type, Unmarshal returns a parse error. func Unmarshal(b []byte, val interface{}) ([]byte, error) { return UnmarshalWithParams(b, val, "") } // UnmarshalWithParams allows field parameters to be specified for the // top-level element. The form of the params is the same as the field tags. func UnmarshalWithParams(b []byte, val interface{}, params string) ([]byte, error) { info, err := fieldTagToFieldInfo(params, "") if err != nil { return nil, err } // The passed in interface{} is a pointer (to allow the value to be written // to); extract the pointed-to object as a reflect.Value, so parseField // can do various introspection things. v := reflect.ValueOf(val).Elem() offset, err := parseField(v, b, 0, info) if err != nil { return nil, err } return b[offset:], nil } // Return the number of bytes needed to encode values up to (and including) x. func byteCount(x uint64) uint { switch { case x < 0x100: return 1 case x < 0x10000: return 2 case x < 0x1000000: return 3 case x < 0x100000000: return 4 case x < 0x10000000000: return 5 case x < 0x1000000000000: return 6 case x < 0x100000000000000: return 7 default: return 8 } } type fieldInfo struct { count uint // Number of bytes countSet bool minlen uint64 // Only relevant for slices maxlen uint64 // Only relevant for slices selector string // Only relevant for select sub-values val uint64 // Only relevant for select sub-values name string // Used for better error messages } func (i *fieldInfo) fieldName() string { if i == nil { return "" } return i.name } // Given a tag string, return a fieldInfo describing the field. func fieldTagToFieldInfo(str string, name string) (*fieldInfo, error) { var info *fieldInfo // Iterate over clauses in the tag, ignoring any that don't parse properly. for _, part := range strings.Split(str, ",") { switch { case strings.HasPrefix(part, "maxval:"): if v, err := strconv.ParseUint(part[7:], 10, 64); err == nil { info = &fieldInfo{count: byteCount(v), countSet: true} } case strings.HasPrefix(part, "size:"): if sz, err := strconv.ParseUint(part[5:], 10, 32); err == nil { info = &fieldInfo{count: uint(sz), countSet: true} } case strings.HasPrefix(part, "maxlen:"): v, err := strconv.ParseUint(part[7:], 10, 64) if err != nil { continue } if info == nil { info = &fieldInfo{} } info.count = byteCount(v) info.countSet = true info.maxlen = v case strings.HasPrefix(part, "minlen:"): v, err := strconv.ParseUint(part[7:], 10, 64) if err != nil { continue } if info == nil { info = &fieldInfo{} } info.minlen = v case strings.HasPrefix(part, "selector:"): if info == nil { info = &fieldInfo{} } info.selector = part[9:] case strings.HasPrefix(part, "val:"): v, err := strconv.ParseUint(part[4:], 10, 64) if err != nil { continue } if info == nil { info = &fieldInfo{} } info.val = v } } if info != nil { info.name = name if info.selector == "" { if info.count < 1 { return nil, structuralError{name, "field of unknown size in " + str} } else if info.count > 8 { return nil, structuralError{name, "specified size too large in " + str} } else if info.minlen > info.maxlen { return nil, structuralError{name, "specified length range inverted in " + str} } else if info.val > 0 { return nil, structuralError{name, "specified selector value but not field in " + str} } } } else if name != "" { info = &fieldInfo{name: name} } return info, nil } // Check that a value fits into a field described by a fieldInfo structure. func (i fieldInfo) check(val uint64, fldName string) error { if val >= (1 << (8 * i.count)) { return structuralError{fldName, fmt.Sprintf("value %d too large for size", val)} } if i.maxlen != 0 { if val < i.minlen { return structuralError{fldName, fmt.Sprintf("value %d too small for minimum %d", val, i.minlen)} } if val > i.maxlen { return structuralError{fldName, fmt.Sprintf("value %d too large for maximum %d", val, i.maxlen)} } } return nil } // readVarUint reads an big-endian unsigned integer of the given size in // bytes. func readVarUint(data []byte, info *fieldInfo) (uint64, error) { if info == nil || !info.countSet { return 0, structuralError{info.fieldName(), "no field size information available"} } if len(data) < int(info.count) { return 0, syntaxError{info.fieldName(), "truncated variable-length integer"} } var result uint64 for i := uint(0); i < info.count; i++ { result = (result << 8) | uint64(data[i]) } if err := info.check(result, info.name); err != nil { return 0, err } return result, nil } // parseField is the main parsing function. Given a byte slice and an offset // (in bytes) into the data, it will try to parse a suitable ASN.1 value out // and store it in the given Value. func parseField(v reflect.Value, data []byte, initOffset int, info *fieldInfo) (int, error) { offset := initOffset rest := data[offset:] fieldType := v.Type() // First look for known fixed types. switch fieldType { case uint8Type: if len(rest) < 1 { return offset, syntaxError{info.fieldName(), "truncated uint8"} } v.SetUint(uint64(rest[0])) offset++ return offset, nil case uint16Type: if len(rest) < 2 { return offset, syntaxError{info.fieldName(), "truncated uint16"} } v.SetUint(uint64(binary.BigEndian.Uint16(rest))) offset += 2 return offset, nil case uint24Type: if len(rest) < 3 { return offset, syntaxError{info.fieldName(), "truncated uint24"} } v.SetUint(uint64(data[0])<<16 | uint64(data[1])<<8 | uint64(data[2])) offset += 3 return offset, nil case uint32Type: if len(rest) < 4 { return offset, syntaxError{info.fieldName(), "truncated uint32"} } v.SetUint(uint64(binary.BigEndian.Uint32(rest))) offset += 4 return offset, nil case uint64Type: if len(rest) < 8 { return offset, syntaxError{info.fieldName(), "truncated uint64"} } v.SetUint(uint64(binary.BigEndian.Uint64(rest))) offset += 8 return offset, nil } // Now deal with user-defined types. switch v.Kind() { case enumType.Kind(): // Assume that anything of the same kind as Enum is an Enum, so that // users can alias types of their own to Enum. val, err := readVarUint(rest, info) if err != nil { return offset, err } v.SetUint(val) offset += int(info.count) return offset, nil case reflect.Struct: structType := fieldType // TLS includes a select(Enum) {..} construct, where the value of an enum // indicates which variant field is present (like a C union). We require // that the enum value be an earlier field in the same structure (the selector), // and that each of the possible variant destination fields be pointers. // So the Go mapping looks like: // type variantType struct { // Which tls.Enum `tls:"size:1"` // this is the selector // Val1 *type1 `tls:"selector:Which,val:1"` // this is a destination // Val2 *type2 `tls:"selector:Which,val:1"` // this is a destination // } // To deal with this, we track any enum-like fields and their values... enums := make(map[string]uint64) // .. and we track which selector names we've seen (in the destination field tags), // and whether a destination for that selector has been chosen. selectorSeen := make(map[string]bool) for i := 0; i < structType.NumField(); i++ { // Find information about this field. tag := structType.Field(i).Tag.Get("tls") fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name) if err != nil { return offset, err } destination := v.Field(i) if fieldInfo.selector != "" { // This is a possible select(Enum) destination, so first check that the referenced // selector field has already been seen earlier in the struct. choice, ok := enums[fieldInfo.selector] if !ok { return offset, structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector} } if structType.Field(i).Type.Kind() != reflect.Ptr { return offset, structuralError{fieldInfo.name, "choice field not a pointer type"} } // Is this the first mention of the selector field name? If so, remember it. seen, ok := selectorSeen[fieldInfo.selector] if !ok { selectorSeen[fieldInfo.selector] = false } if choice != fieldInfo.val { // This destination field was not the chosen one, so make it nil (we checked // it was a pointer above). v.Field(i).Set(reflect.Zero(structType.Field(i).Type)) continue } if seen { // We already saw a different destination field receive the value for this // selector value, which indicates a badly annotated structure. return offset, structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector} } selectorSeen[fieldInfo.selector] = true // Make an object of the pointed-to type and parse into that. v.Field(i).Set(reflect.New(structType.Field(i).Type.Elem())) destination = v.Field(i).Elem() } offset, err = parseField(destination, data, offset, fieldInfo) if err != nil { return offset, err } // Remember any possible tls.Enum values encountered in case they are selectors. if structType.Field(i).Type.Kind() == enumType.Kind() { enums[structType.Field(i).Name] = v.Field(i).Uint() } } // Now we have seen all fields in the structure, check that all select(Enum) {..} selector // fields found a destination to put their data in. for selector, seen := range selectorSeen { if !seen { return offset, syntaxError{info.fieldName(), selector + ": unhandled value for selector"} } } return offset, nil case reflect.Array: datalen := v.Len() if datalen > len(rest) { return offset, syntaxError{info.fieldName(), "truncated array"} } inner := rest[:datalen] offset += datalen if fieldType.Elem().Kind() != reflect.Uint8 { // Only byte/uint8 arrays are supported return offset, structuralError{info.fieldName(), "unsupported array type: " + v.Type().String()} } reflect.Copy(v, reflect.ValueOf(inner)) return offset, nil case reflect.Slice: sliceType := fieldType // Slices represent variable-length vectors, which are prefixed by a length field. // The fieldInfo indicates the size of that length field. varlen, err := readVarUint(rest, info) if err != nil { return offset, err } datalen := int(varlen) offset += int(info.count) rest = rest[info.count:] if datalen > len(rest) { return offset, syntaxError{info.fieldName(), "truncated slice"} } inner := rest[:datalen] offset += datalen if fieldType.Elem().Kind() == reflect.Uint8 { // Fast version for []byte v.Set(reflect.MakeSlice(sliceType, datalen, datalen)) reflect.Copy(v, reflect.ValueOf(inner)) return offset, nil } v.Set(reflect.MakeSlice(sliceType, 0, datalen)) single := reflect.New(sliceType.Elem()) for innerOffset := 0; innerOffset < len(inner); { var err error innerOffset, err = parseField(single.Elem(), inner, innerOffset, nil) if err != nil { return offset, err } v.Set(reflect.Append(v, single.Elem())) } return offset, nil default: return offset, structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())} } } // Marshal returns the TLS encoding of val. func Marshal(val interface{}) ([]byte, error) { return MarshalWithParams(val, "") } // MarshalWithParams returns the TLS encoding of val, and allows field // parameters to be specified for the top-level element. The form // of the params is the same as the field tags. func MarshalWithParams(val interface{}, params string) ([]byte, error) { info, err := fieldTagToFieldInfo(params, "") if err != nil { return nil, err } var out bytes.Buffer v := reflect.ValueOf(val) if err := marshalField(&out, v, info); err != nil { return nil, err } return out.Bytes(), err } func marshalField(out *bytes.Buffer, v reflect.Value, info *fieldInfo) error { var prefix string if info != nil && len(info.name) > 0 { prefix = info.name + ": " } fieldType := v.Type() // First look for known fixed types. switch fieldType { case uint8Type: out.WriteByte(byte(v.Uint())) return nil case uint16Type: scratch := make([]byte, 2) binary.BigEndian.PutUint16(scratch, uint16(v.Uint())) out.Write(scratch) return nil case uint24Type: i := v.Uint() if i > 0xffffff { return structuralError{info.fieldName(), fmt.Sprintf("uint24 overflow %d", i)} } scratch := make([]byte, 4) binary.BigEndian.PutUint32(scratch, uint32(i)) out.Write(scratch[1:]) return nil case uint32Type: scratch := make([]byte, 4) binary.BigEndian.PutUint32(scratch, uint32(v.Uint())) out.Write(scratch) return nil case uint64Type: scratch := make([]byte, 8) binary.BigEndian.PutUint64(scratch, uint64(v.Uint())) out.Write(scratch) return nil } // Now deal with user-defined types. switch v.Kind() { case enumType.Kind(): i := v.Uint() if info == nil { return structuralError{info.fieldName(), "enum field tag missing"} } if err := info.check(i, prefix); err != nil { return err } scratch := make([]byte, 8) binary.BigEndian.PutUint64(scratch, uint64(i)) out.Write(scratch[(8 - info.count):]) return nil case reflect.Struct: structType := fieldType enums := make(map[string]uint64) // Values of any Enum fields // The comment parseField() describes the mapping of the TLS select(Enum) {..} construct; // here we have selector and source (rather than destination) fields. // Track which selector names we've seen (in the source field tags), and whether a source // value for that selector has been processed. selectorSeen := make(map[string]bool) for i := 0; i < structType.NumField(); i++ { // Find information about this field. tag := structType.Field(i).Tag.Get("tls") fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name) if err != nil { return err } source := v.Field(i) if fieldInfo.selector != "" { // This field is a possible source for a select(Enum) {..}. First check // the selector field name has been seen. choice, ok := enums[fieldInfo.selector] if !ok { return structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector} } if structType.Field(i).Type.Kind() != reflect.Ptr { return structuralError{fieldInfo.name, "choice field not a pointer type"} } // Is this the first mention of the selector field name? If so, remember it. seen, ok := selectorSeen[fieldInfo.selector] if !ok { selectorSeen[fieldInfo.selector] = false } if choice != fieldInfo.val { // This source was not chosen; police that it should be nil. if v.Field(i).Pointer() != uintptr(0) { return structuralError{fieldInfo.name, "unchosen field is non-nil"} } continue } if seen { // We already saw a different source field generate the value for this // selector value, which indicates a badly annotated structure. return structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector} } selectorSeen[fieldInfo.selector] = true if v.Field(i).Pointer() == uintptr(0) { return structuralError{fieldInfo.name, "chosen field is nil"} } // Marshal from the pointed-to source object. source = v.Field(i).Elem() } var fieldData bytes.Buffer if err := marshalField(&fieldData, source, fieldInfo); err != nil { return err } out.Write(fieldData.Bytes()) // Remember any tls.Enum values encountered in case they are selectors. if structType.Field(i).Type.Kind() == enumType.Kind() { enums[structType.Field(i).Name] = v.Field(i).Uint() } } // Now we have seen all fields in the structure, check that all select(Enum) {..} selector // fields found a source field to get their data from. for selector, seen := range selectorSeen { if !seen { return syntaxError{info.fieldName(), selector + ": unhandled value for selector"} } } return nil case reflect.Array: datalen := v.Len() arrayType := fieldType if arrayType.Elem().Kind() != reflect.Uint8 { // Only byte/uint8 arrays are supported return structuralError{info.fieldName(), "unsupported array type"} } bytes := make([]byte, datalen) for i := 0; i < datalen; i++ { bytes[i] = uint8(v.Index(i).Uint()) } _, err := out.Write(bytes) return err case reflect.Slice: if info == nil { return structuralError{info.fieldName(), "slice field tag missing"} } sliceType := fieldType if sliceType.Elem().Kind() == reflect.Uint8 { // Fast version for []byte: first write the length as info.count bytes. datalen := v.Len() scratch := make([]byte, 8) binary.BigEndian.PutUint64(scratch, uint64(datalen)) out.Write(scratch[(8 - info.count):]) if err := info.check(uint64(datalen), prefix); err != nil { return err } // Then just write the data. bytes := make([]byte, datalen) for i := 0; i < datalen; i++ { bytes[i] = uint8(v.Index(i).Uint()) } _, err := out.Write(bytes) return err } // General version: use a separate Buffer to write the slice entries into. var innerBuf bytes.Buffer for i := 0; i < v.Len(); i++ { if err := marshalField(&innerBuf, v.Index(i), nil); err != nil { return err } } // Now insert (and check) the size. size := uint64(innerBuf.Len()) if err := info.check(size, prefix); err != nil { return err } scratch := make([]byte, 8) binary.BigEndian.PutUint64(scratch, size) out.Write(scratch[(8 - info.count):]) // Then copy the data. _, err := out.Write(innerBuf.Bytes()) return err default: return structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())} } } google-certificate-transparency-go-2308f62/tls/tls_test.go000066400000000000000000000342011462611535200236330ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "bytes" "encoding/hex" "reflect" "strings" "testing" ) type testStruct struct { Data []byte `tls:"minlen:2,maxlen:4"` IntVal uint16 Other [4]byte Enum Enum `tls:"size:2"` } type testVariant struct { Which Enum `tls:"size:1"` Val16 *uint16 `tls:"selector:Which,val:0"` Val32 *uint32 `tls:"selector:Which,val:1"` } type testTwoVariants struct { Which Enum `tls:"size:1"` Val16 *uint16 `tls:"selector:Which,val:0"` Val32 *uint32 `tls:"selector:Which,val:1"` Second Enum `tls:"size:1"` Second16 *uint16 `tls:"selector:Second,val:0"` Second32 *uint32 `tls:"selector:Second,val:1"` } // Check that library users can define their own Enum types. type aliasEnum Enum type testAliasEnum struct { Val aliasEnum `tls:"size:1"` Val16 *uint16 `tls:"selector:Val,val:1"` Val32 *uint32 `tls:"selector:Val,val:2"` } type testNonByteSlice struct { Vals []uint16 `tls:"minlen:2,maxlen:6"` } type testSliceOfStructs struct { Vals []testVariant `tls:"minlen:0,maxlen:100"` } type testInnerType struct { Val []byte `tls:"minlen:0,maxlen:65535"` } type testSliceOfSlices struct { Inners []testInnerType `tls:"minlen:0,maxlen:65535"` } func TestMarshalUnmarshalRoundTrip(t *testing.T) { thing := testStruct{Data: []byte{0x01, 0x02, 0x03}, IntVal: 42, Other: [4]byte{1, 2, 3, 4}, Enum: 17} data, err := Marshal(thing) if err != nil { t.Fatalf("Failed to Marshal(%+v): %s", thing, err.Error()) } var other testStruct rest, err := Unmarshal(data, &other) if err != nil { t.Fatalf("Failed to Unmarshal(%s)", hex.EncodeToString(data)) } if len(rest) > 0 { t.Errorf("Data left over after Unmarshal(%s): %s", hex.EncodeToString(data), hex.EncodeToString(rest)) } } func TestFieldTagToFieldInfo(t *testing.T) { var tests = []struct { tag string want *fieldInfo errstr string }{ {"", nil, ""}, {"bogus", nil, ""}, {"also,bogus", nil, ""}, {"also,bogus:99", nil, ""}, {"maxval:1xyz", nil, ""}, {"maxval:1", &fieldInfo{count: 1, countSet: true}, ""}, {"maxval:255", &fieldInfo{count: 1, countSet: true}, ""}, {"maxval:256", &fieldInfo{count: 2, countSet: true}, ""}, {"maxval:65535", &fieldInfo{count: 2, countSet: true}, ""}, {"maxval:65536", &fieldInfo{count: 3, countSet: true}, ""}, {"maxval:16777215", &fieldInfo{count: 3, countSet: true}, ""}, {"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:4294967295", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:4294967296", &fieldInfo{count: 5, countSet: true}, ""}, {"maxval:1099511627775", &fieldInfo{count: 5, countSet: true}, ""}, {"maxval:1099511627776", &fieldInfo{count: 6, countSet: true}, ""}, {"maxval:281474976710655", &fieldInfo{count: 6, countSet: true}, ""}, {"maxval:281474976710656", &fieldInfo{count: 7, countSet: true}, ""}, {"maxval:72057594037927935", &fieldInfo{count: 7, countSet: true}, ""}, {"maxval:72057594037927936", &fieldInfo{count: 8, countSet: true}, ""}, {"minlen:1x", nil, ""}, {"maxlen:1x", nil, ""}, {"maxlen:1", &fieldInfo{count: 1, countSet: true, maxlen: 1}, ""}, {"maxlen:255", &fieldInfo{count: 1, countSet: true, maxlen: 255}, ""}, {"maxlen:65535", &fieldInfo{count: 2, countSet: true, maxlen: 65535}, ""}, {"minlen:65530,maxlen:65535", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""}, {"maxlen:65535,minlen:65530", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""}, {"minlen:65536,maxlen:65535", nil, "inverted"}, {"maxlen:16777215", &fieldInfo{count: 3, countSet: true, maxlen: 16777215}, ""}, {"maxlen:281474976710655", &fieldInfo{count: 6, countSet: true, maxlen: 281474976710655}, ""}, {"maxlen:72057594037927936", &fieldInfo{count: 8, countSet: true, maxlen: 72057594037927936}, ""}, {"size:0", nil, "unknown size"}, {"size:1", &fieldInfo{count: 1, countSet: true}, ""}, {"size:2", &fieldInfo{count: 2, countSet: true}, ""}, {"size:3", &fieldInfo{count: 3, countSet: true}, ""}, {"size:4", &fieldInfo{count: 4, countSet: true}, ""}, {"size:5", &fieldInfo{count: 5, countSet: true}, ""}, {"size:6", &fieldInfo{count: 6, countSet: true}, ""}, {"size:7", &fieldInfo{count: 7, countSet: true}, ""}, {"size:8", &fieldInfo{count: 8, countSet: true}, ""}, {"size:9", nil, "too large"}, {"size:1x", nil, ""}, {"size:1,val:9", nil, "selector value"}, {"selector:Bob,val:x9", &fieldInfo{selector: "Bob"}, ""}, {"selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""}, {"val:9,selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""}, } for _, test := range tests { got, err := fieldTagToFieldInfo(test.tag, "") if test.errstr != "" { if err == nil { t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want error %q", test.tag, got, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want error %q", test.tag, err.Error(), test.errstr) } continue } if err != nil { t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want %+v", test.tag, err.Error(), test.want) } else if !reflect.DeepEqual(got, test.want) { t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want %+v", test.tag, got, test.want) } } } // Can't take the address of a numeric constant so use helper functions func newByte(n byte) *byte { return &n } func newUint8(n uint8) *uint8 { return &n } func newUint16(n uint16) *uint16 { return &n } func newUint24(n Uint24) *Uint24 { return &n } func newUint32(n uint32) *uint32 { return &n } func newUint64(n uint64) *uint64 { return &n } func newInt16(n int16) *int16 { return &n } func newEnum(n Enum) *Enum { return &n } func TestUnmarshalMarshalWithParamsRoundTrip(t *testing.T) { var tests = []struct { data string // hex encoded params string item interface{} }{ {"00", "", newUint8(0)}, {"03", "", newByte(3)}, {"0101", "", newUint16(0x0101)}, {"010203", "", newUint24(0x010203)}, {"000000", "", newUint24(0x00)}, {"00000009", "", newUint32(0x09)}, {"0000000901020304", "", newUint64(0x0901020304)}, {"030405", "", &[3]byte{3, 4, 5}}, {"03", "", &[1]byte{3}}, {"0001", "size:2", newEnum(1)}, {"0100000001", "size:5", newEnum(0x100000001)}, {"12", "maxval:18", newEnum(18)}, // Note that maxval is just used to give enum size; it's not policed {"20", "maxval:18", newEnum(32)}, {"020a0b", "minlen:1,maxlen:5", &[]byte{0xa, 0xb}}, {"020a0b0101010203040011", "", &testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}}, {"000102", "", &testVariant{Which: 0, Val16: newUint16(0x0102)}}, {"0101020304", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}}, {"0001020104030201", "", &testTwoVariants{Which: 0, Val16: newUint16(0x0102), Second: 1, Second32: newUint32(0x04030201)}}, {"06010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}}, {"00", "", &testSliceOfStructs{Vals: []testVariant{}}}, {"080001020101020304", "", &testSliceOfStructs{ Vals: []testVariant{ {Which: 0, Val16: newUint16(0x0102)}, {Which: 1, Val32: newUint32(0x01020304)}, }, }, }, {"000a00030102030003040506", "", &testSliceOfSlices{ Inners: []testInnerType{ {Val: []byte{1, 2, 3}}, {Val: []byte{4, 5, 6}}, }, }, }, {"011011", "", &testAliasEnum{Val: 1, Val16: newUint16(0x1011)}}, {"0403", "", &SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}}, {"04030003010203", "", &DigitallySigned{ Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}, Signature: []byte{1, 2, 3}, }, }, } for _, test := range tests { inVal := reflect.ValueOf(test.item).Elem() pv := reflect.New(reflect.TypeOf(test.item).Elem()) val := pv.Interface() inData, _ := hex.DecodeString(test.data) if _, err := UnmarshalWithParams(inData, val, test.params); err != nil { t.Errorf("Unmarshal(%s)=nil,%q; want %+v", test.data, err.Error(), inVal) } else if !reflect.DeepEqual(val, test.item) { t.Errorf("Unmarshal(%s)=%+v,nil; want %+v", test.data, reflect.ValueOf(val).Elem(), inVal) } if data, err := MarshalWithParams(inVal.Interface(), test.params); err != nil { t.Errorf("Marshal(%+v)=nil,%q; want %s", inVal, err.Error(), test.data) } else if !bytes.Equal(data, inData) { t.Errorf("Marshal(%+v)=%s,nil; want %s", inVal, hex.EncodeToString(data), test.data) } } } type testInvalidFieldTag struct { Data []byte `tls:"minlen:3,maxlen:2"` } type testDuplicateSelectorVal struct { Which Enum `tls:"size:1"` Val *uint16 `tls:"selector:Which,val:0"` DupVal *uint32 `tls:"selector:Which"` // implicit val:0 } type testMissingSelector struct { Val *uint16 `tls:"selector:Missing,val:0"` } type testChoiceNotPointer struct { Which Enum `tls:"size:1"` Val uint16 `tls:"selector:Which,val:0"` } type nonEnumAlias uint16 func newNonEnumAlias(n nonEnumAlias) *nonEnumAlias { return &n } func TestUnmarshalWithParamsFailures(t *testing.T) { var tests = []struct { data string // hex encoded params string item interface{} errstr string }{ {"", "", newUint8(0), "truncated"}, {"0x01", "", newUint16(0x0101), "truncated"}, {"0103", "", newUint24(0x010203), "truncated"}, {"00", "", newUint24(0x00), "truncated"}, {"000009", "", newUint32(0x09), "truncated"}, {"00000901020304", "", newUint64(0x0901020304), "truncated"}, {"0102", "", newInt16(0x0102), "unsupported type"}, // TLS encoding only supports unsigned integers {"0607", "", &[3]byte{6, 7, 8}, "truncated array"}, {"01010202", "", &[3]uint16{0x101, 0x202}, "unsupported array"}, {"01", "", newEnum(1), "no field size"}, {"00", "size:2", newEnum(0), "truncated"}, {"00", "size:9", newEnum(0), "too large"}, {"020a0b", "minlen:4,maxlen:8", &[]byte{0x0a, 0x0b}, "too small"}, {"040a0b0c0d", "minlen:1,maxlen:3", &[]byte{0x0a, 0x0b, 0x0c, 0x0d}, "too large"}, {"020a0b", "minlen:8,maxlen:6", &[]byte{0x0a, 0x0b}, "inverted"}, {"020a", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"}, {"02", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"}, {"0001", "minlen:0,maxlen:256", &[]byte{0x0a, 0x0b}, "truncated"}, {"020a", "minlen:0", &[]byte{0x0a, 0x0b}, "unknown size"}, {"020a", "", &[]byte{0x0a, 0x0b}, "no field size information"}, {"020a0b", "", &testInvalidFieldTag{}, "range inverted"}, {"020a0b01010102030400", "", &testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "truncated"}, {"010102", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}, "truncated"}, {"092122", "", &testVariant{Which: 0, Val16: newUint16(0x2122)}, "unhandled value for selector"}, {"0001020304", "", &testDuplicateSelectorVal{Which: 0, Val: newUint16(0x0102)}, "duplicate selector value"}, {"0102", "", &testMissingSelector{Val: newUint16(1)}, "selector not seen"}, {"000007", "", &testChoiceNotPointer{Which: 0, Val: 7}, "choice field not a pointer type"}, {"05010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}, "truncated"}, {"0101", "size:2", newNonEnumAlias(0x0102), "unsupported type"}, {"0403010203", "", &DigitallySigned{ Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}, Signature: []byte{1, 2, 3}}, "truncated"}, } for _, test := range tests { pv := reflect.New(reflect.TypeOf(test.item).Elem()) val := pv.Interface() in, _ := hex.DecodeString(test.data) if _, err := UnmarshalWithParams(in, val, test.params); err == nil { t.Errorf("Unmarshal(%s)=%+v,nil; want error %q", test.data, reflect.ValueOf(val).Elem(), test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("Unmarshal(%s)=nil,%q; want error %q", test.data, err.Error(), test.errstr) } } } func TestMarshalWithParamsFailures(t *testing.T) { var tests = []struct { item interface{} params string errstr string }{ {Uint24(0x1000000), "", "overflow"}, {int16(0x0102), "", "unsupported type"}, // All TLS ints are unsigned {Enum(1), "", "field tag missing"}, {Enum(256), "size:1", "too large"}, {Enum(256), "maxval:255", "too large"}, {Enum(2), "", "field tag missing"}, {Enum(256), "size:9", "too large"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:1,maxlen:3", "too large"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:13", "too small"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:3", "inverted"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6", "unknown size"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "", "field tag missing"}, {[3]uint16{0x101, 0x202}, "", "unsupported array"}, {testInvalidFieldTag{}, "", "inverted"}, {testStruct{Data: []byte{0xa}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "", "too small"}, {testVariant{Which: 0, Val32: newUint32(0x01020304)}, "", "chosen field is nil"}, {testVariant{Which: 0, Val16: newUint16(11), Val32: newUint32(0x01020304)}, "", "unchosen field is non-nil"}, {testVariant{Which: 3}, "", "unhandled value for selector"}, {testMissingSelector{Val: newUint16(1)}, "", "selector not seen"}, {testChoiceNotPointer{Which: 0, Val: 7}, "", "choice field not a pointer"}, {testDuplicateSelectorVal{Which: 0, Val: newUint16(1)}, "", "duplicate selector value"}, {testNonByteSlice{Vals: []uint16{1, 2, 3, 4}}, "", "too large"}, {testSliceOfStructs{[]testVariant{{Which: 3}}}, "", "unhandled value for selector"}, {nonEnumAlias(0x0102), "", "unsupported type"}, } for _, test := range tests { if data, err := MarshalWithParams(test.item, test.params); err == nil { t.Errorf("Marshal(%+v)=%x,nil; want error %q", test.item, data, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("Marshal(%+v)=nil,%q; want error %q", test.item, err.Error(), test.errstr) } } } google-certificate-transparency-go-2308f62/tls/types.go000066400000000000000000000056261462611535200231470ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "crypto" "crypto/dsa" //nolint:staticcheck "crypto/ecdsa" "crypto/rsa" "fmt" ) // DigitallySigned gives information about a signature, including the algorithm used // and the signature value. Defined in RFC 5246 s4.7. type DigitallySigned struct { Algorithm SignatureAndHashAlgorithm Signature []byte `tls:"minlen:0,maxlen:65535"` } func (d DigitallySigned) String() string { return fmt.Sprintf("Signature: HashAlgo=%v SignAlgo=%v Value=%x", d.Algorithm.Hash, d.Algorithm.Signature, d.Signature) } // SignatureAndHashAlgorithm gives information about the algorithms used for a // signature. Defined in RFC 5246 s7.4.1.4.1. type SignatureAndHashAlgorithm struct { Hash HashAlgorithm `tls:"maxval:255"` Signature SignatureAlgorithm `tls:"maxval:255"` } // HashAlgorithm enum from RFC 5246 s7.4.1.4.1. type HashAlgorithm Enum // HashAlgorithm constants from RFC 5246 s7.4.1.4.1. const ( None HashAlgorithm = 0 MD5 HashAlgorithm = 1 SHA1 HashAlgorithm = 2 SHA224 HashAlgorithm = 3 SHA256 HashAlgorithm = 4 SHA384 HashAlgorithm = 5 SHA512 HashAlgorithm = 6 ) func (h HashAlgorithm) String() string { switch h { case None: return "None" case MD5: return "MD5" case SHA1: return "SHA1" case SHA224: return "SHA224" case SHA256: return "SHA256" case SHA384: return "SHA384" case SHA512: return "SHA512" default: return fmt.Sprintf("UNKNOWN(%d)", h) } } // SignatureAlgorithm enum from RFC 5246 s7.4.1.4.1. type SignatureAlgorithm Enum // SignatureAlgorithm constants from RFC 5246 s7.4.1.4.1. const ( Anonymous SignatureAlgorithm = 0 RSA SignatureAlgorithm = 1 DSA SignatureAlgorithm = 2 ECDSA SignatureAlgorithm = 3 ) func (s SignatureAlgorithm) String() string { switch s { case Anonymous: return "Anonymous" case RSA: return "RSA" case DSA: return "DSA" case ECDSA: return "ECDSA" default: return fmt.Sprintf("UNKNOWN(%d)", s) } } // SignatureAlgorithmFromPubKey returns the algorithm used for this public key. // ECDSA, RSA, and DSA keys are supported. Other key types will return Anonymous. func SignatureAlgorithmFromPubKey(k crypto.PublicKey) SignatureAlgorithm { switch k.(type) { case *ecdsa.PublicKey: return ECDSA case *rsa.PublicKey: return RSA case *dsa.PublicKey: return DSA default: return Anonymous } } google-certificate-transparency-go-2308f62/tls/types_test.go000066400000000000000000000052431462611535200242010ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "crypto" "crypto/dsa" //nolint:staticcheck "crypto/ecdsa" "crypto/rsa" "testing" ) func TestHashAlgorithmString(t *testing.T) { var tests = []struct { algo HashAlgorithm want string }{ {None, "None"}, {MD5, "MD5"}, {SHA1, "SHA1"}, {SHA224, "SHA224"}, {SHA256, "SHA256"}, {SHA384, "SHA384"}, {SHA512, "SHA512"}, {99, "UNKNOWN(99)"}, } for _, test := range tests { if got := test.algo.String(); got != test.want { t.Errorf("%v.String()=%q; want %q", test.algo, got, test.want) } } } func TestSignatureAlgorithmString(t *testing.T) { var tests = []struct { algo SignatureAlgorithm want string }{ {Anonymous, "Anonymous"}, {RSA, "RSA"}, {DSA, "DSA"}, {ECDSA, "ECDSA"}, {99, "UNKNOWN(99)"}, } for _, test := range tests { if got := test.algo.String(); got != test.want { t.Errorf("%v.String()=%q; want %q", test.algo, got, test.want) } } } func TestDigitallySignedString(t *testing.T) { var tests = []struct { ds DigitallySigned want string }{ { ds: DigitallySigned{Algorithm: SignatureAndHashAlgorithm{Hash: SHA1, Signature: RSA}, Signature: []byte{0x01, 0x02}}, want: "Signature: HashAlgo=SHA1 SignAlgo=RSA Value=0102", }, { ds: DigitallySigned{Algorithm: SignatureAndHashAlgorithm{Hash: 99, Signature: 99}, Signature: []byte{0x03, 0x04}}, want: "Signature: HashAlgo=UNKNOWN(99) SignAlgo=UNKNOWN(99) Value=0304", }, } for _, test := range tests { if got := test.ds.String(); got != test.want { t.Errorf("%v.String()=%q; want %q", test.ds, got, test.want) } } } func TestSignatureAlgorithm(t *testing.T) { for _, test := range []struct { name string key crypto.PublicKey want SignatureAlgorithm }{ {name: "ECDSA", key: new(ecdsa.PublicKey), want: ECDSA}, {name: "RSA", key: new(rsa.PublicKey), want: RSA}, {name: "DSA", key: new(dsa.PublicKey), want: DSA}, {name: "Other", key: "foo", want: Anonymous}, } { if got := SignatureAlgorithmFromPubKey(test.key); got != test.want { t.Errorf("%v: SignatureAlgorithm() = %v, want %v", test.name, got, test.want) } } } google-certificate-transparency-go-2308f62/tools/000077500000000000000000000000001462611535200220015ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/tools/tools.go000066400000000000000000000021521462611535200234700ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build tools // +build tools // Package tools tracks dependencies on binaries not otherwise referenced in this codebase. // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module package tools import ( _ "github.com/fullstorydev/grpcurl/cmd/grpcurl" _ "github.com/golang/mock/mockgen" _ "github.com/google/trillian/cmd/createtree" _ "go.etcd.io/etcd/etcdctl/v3" _ "go.etcd.io/etcd/v3" _ "google.golang.org/protobuf/cmd/protoc-gen-go" _ "google.golang.org/protobuf/proto" ) google-certificate-transparency-go-2308f62/trillian/000077500000000000000000000000001462611535200224575ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/README.md000066400000000000000000000050071462611535200237400ustar00rootroot00000000000000# Trillian CT Personality This directory holds code and scripts for running a Certificate Transparency (CT) Log based on the [Trillian](https://github.com/google/trillian) general transparency Log. - [Codebase Structure](#codebase-structure) - [Deployment](#deployment) - [Operation](#operation) ## Codebase Structure The main code for the CT personality is held in `trillian/ctfe`; this code responds to HTTP requests on the [CT API paths](https://tools.ietf.org/html/rfc6962#section-4) and translates them to the equivalent gRPC API requests to the Trillian Log. This obviously relies on the gRPC API definitions at `github.com/google/trillian`; the code also uses common libraries from the Trillian project for various things including: - exposing monitoring and statistics via an `interface` and corresponding Prometheus implementation (`github.com/google/trillian/monitoring/...`) - dealing with cryptographic keys (`github.com/google/trillian/crypto/...`). The `trillian/integration/` directory holds scripts and tests for running the whole system locally. In particular: - `trillian/integration/ct_integration_test.sh` brings up local processes running a Trillian Log server, signer and a CT personality, and exercises the complete set of RFC 6962 API entrypoints. - `trillian/integration/ct_hammer_test.sh` brings up a complete system and runs a continuous randomized test of the CT entrypoints. These scripts require a local database instance to be configured as described in the [Trillian instructions](https://github.com/google/trillian#mysql-setup). ## Deployment Deploying a Trillian-based CT Log involves more than just the code contained in this directory. The [Manual Deployment document](docs/ManualDeployment.md) describes the components and process involved in manually setting up a CT Log instance on individual machines. The [Containerized Deployment document](docs/ContainerDeployment.md) describes the sample container scripts which make CT Log deployment easier and more automatic. However, if you're planning to operate a trusted CT Log (rather than simply experimenting/playing with the code) then you should expect to understand all of the information in the manual version – even if you use the containerized variant for deployment convenience. ## Operation Once all of the components for a Trillian-based CT Log have been deployed, log operators need to monitor and maintain the Log. The [Operation document](docs/Operation.md) describes key considerations and gotchas for this ongoing process. google-certificate-transparency-go-2308f62/trillian/ctfe/000077500000000000000000000000001462611535200234005ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/cache/000077500000000000000000000000001462611535200244435ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/cache/cache.go000066400000000000000000000043301462611535200260350ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cache defines the IssuanceChainCache type, which allows different cache implementation with Get and Set operations. package cache import ( "context" "errors" "time" "github.com/google/certificate-transparency-go/trillian/ctfe/cache/lru" "github.com/google/certificate-transparency-go/trillian/ctfe/cache/noop" ) // Type represents the cache type. type Type string // Type constants for the cache type. const ( Unknown Type = "" NOOP Type = "noop" LRU Type = "lru" ) // Option represents the cache option, which includes the cache size and time-to-live. type Option struct { Size int TTL time.Duration } // IssuanceChainCache is an interface which allows CTFE binaries to use different cache implementations for issuance chains. type IssuanceChainCache interface { // Get returns the issuance chain associated with the provided hash. Get(ctx context.Context, key []byte) ([]byte, error) // Set inserts the key-value pair of issuance chain. Set(ctx context.Context, key []byte, chain []byte) error } // NewIssuanceChainCache returns noop.IssuanceChainCache for noop type or lru.IssuanceChainCache for lru cache type. func NewIssuanceChainCache(_ context.Context, cacheType Type, option Option) (IssuanceChainCache, error) { switch cacheType { case Unknown, NOOP: return &noop.IssuanceChainCache{}, nil case LRU: if option.Size < 0 { return nil, errors.New("invalid cache_size flag") } if option.TTL < 0*time.Second { return nil, errors.New("invalid cache_ttl flag") } return lru.NewIssuanceChainCache(lru.CacheOption{Size: option.Size, TTL: option.TTL}), nil } return nil, errors.New("invalid cache_type flag") } google-certificate-transparency-go-2308f62/trillian/ctfe/cache/lru/000077500000000000000000000000001462611535200252455ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/cache/lru/lru.go000066400000000000000000000026361462611535200264050ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package lru defines the IssuanceChainCache type, which implements IssuanceChainCache interface with Get and Set operations. package lru import ( "context" "time" "github.com/hashicorp/golang-lru/v2/expirable" ) type CacheOption struct { Size int TTL time.Duration } type IssuanceChainCache struct { opt CacheOption cache *expirable.LRU[string, []byte] } func NewIssuanceChainCache(opt CacheOption) *IssuanceChainCache { cache := expirable.NewLRU[string, []byte](int(opt.Size), nil, opt.TTL) return &IssuanceChainCache{ opt: opt, cache: cache, } } func (c *IssuanceChainCache) Get(_ context.Context, key []byte) ([]byte, error) { chain, _ := c.cache.Get(string(key)) return chain, nil } func (c *IssuanceChainCache) Set(_ context.Context, key []byte, chain []byte) error { c.cache.Add(string(key), chain) return nil } google-certificate-transparency-go-2308f62/trillian/ctfe/cache/lru/lru_test.go000066400000000000000000000031101462611535200274300ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lru import ( "bytes" "context" "crypto/sha256" "os" "testing" ) func TestLRUIssuanceChainCache(t *testing.T) { cacheSize := 3 tests := setupTestData(t, "leaf00.chain", "leaf01.chain", "leaf02.chain", ) cache := NewIssuanceChainCache(CacheOption{Size: cacheSize}) for key, val := range tests { if err := cache.Set(context.Background(), []byte(key), val); err != nil { t.Errorf("cache.Set: %v", err) } } for key, want := range tests { got, err := cache.Get(context.Background(), []byte(key)) if err != nil { t.Errorf("cache.Get: %v", err) } if !bytes.Equal(got, want) { t.Errorf("got: %v, want: %v", got, want) } } } func setupTestData(t *testing.T, filenames ...string) map[string][]byte { t.Helper() data := make(map[string][]byte, len(filenames)) for _, filename := range filenames { val, err := os.ReadFile("../../../testdata/" + filename) if err != nil { t.Fatal(err) } key := sha256.Sum256(val) data[string(key[:])] = val } return data } google-certificate-transparency-go-2308f62/trillian/ctfe/cache/noop/000077500000000000000000000000001462611535200254165ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/cache/noop/noop.go000066400000000000000000000020551462611535200267220ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package noop defines the IssuanceChainCache type, which implements IssuanceChainCache interface with Get and Set operations. package noop import "context" // IssuanceChainCache is a no-op implementation of the IssuanceChainCache interface. type IssuanceChainCache struct{} func (c *IssuanceChainCache) Get(_ context.Context, key []byte) ([]byte, error) { return nil, nil } func (c *IssuanceChainCache) Set(_ context.Context, key []byte, chain []byte) error { return nil } google-certificate-transparency-go-2308f62/trillian/ctfe/cert_checker.go000066400000000000000000000154051462611535200263550ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bytes" "errors" "fmt" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) var ( ErrNoRFCCompliantPathFound = errors.New("no RFC compliant path to root found when trying to validate chain") ) // IsPrecertificate tests if a certificate is a pre-certificate as defined in CT. // An error is returned if the CT extension is present but is not ASN.1 NULL as defined // by the spec. func IsPrecertificate(cert *x509.Certificate) (bool, error) { for _, ext := range cert.Extensions { if x509.OIDExtensionCTPoison.Equal(ext.Id) { if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) { return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext) } return true, nil } } return false, nil } // ValidateChain takes the certificate chain as it was parsed from a JSON request. Ensures all // elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the // end entity certificate in the chain to a trusted root cert, possibly using the intermediates // supplied in the chain. Then applies the RFC requirement that the path must involve all // the submitted chain in the order of submission. func ValidateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) { // First make sure the certs parse as X.509 chain := make([]*x509.Certificate, 0, len(rawChain)) intermediatePool := x509util.NewPEMCertPool() for i, certBytes := range rawChain { cert, err := x509.ParseCertificate(certBytes) if x509.IsFatal(err) { return nil, err } chain = append(chain, cert) // All but the first cert form part of the intermediate pool if i > 0 { intermediatePool.AddCert(cert) } } naStart := validationOpts.notAfterStart naLimit := validationOpts.notAfterLimit cert := chain[0] // Check whether the expiry date of the cert is within the acceptable range. if naStart != nil && cert.NotAfter.Before(*naStart) { return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart) } if naLimit != nil && !cert.NotAfter.Before(*naLimit) { return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit) } if validationOpts.acceptOnlyCA && !cert.IsCA { return nil, errors.New("only certificates with CA bit set are accepted") } now := validationOpts.currentTime if now.IsZero() { now = time.Now() } expired := now.After(cert.NotAfter) if validationOpts.rejectExpired && expired { return nil, errors.New("rejecting expired certificate") } if validationOpts.rejectUnexpired && !expired { return nil, errors.New("rejecting unexpired certificate") } // Check for unwanted extension types, if required. // TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and // pre-calc this in there if len(validationOpts.rejectExtIds) != 0 { badIDs := make(map[string]bool) for _, id := range validationOpts.rejectExtIds { badIDs[id.String()] = true } for idx, ext := range cert.Extensions { extOid := ext.Id.String() if _, ok := badIDs[extOid]; ok { return nil, fmt.Errorf("rejecting certificate containing extension %v at index %d", extOid, idx) } } } // TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and // pre-calc this in there too. if len(validationOpts.extKeyUsages) > 0 { acceptEKUs := make(map[x509.ExtKeyUsage]bool) for _, eku := range validationOpts.extKeyUsages { acceptEKUs[eku] = true } good := false for _, certEKU := range cert.ExtKeyUsage { if _, ok := acceptEKUs[certEKU]; ok { good = true break } } if !good { return nil, fmt.Errorf("rejecting certificate without EKU in %v", validationOpts.extKeyUsages) } } // We can now do the verification. Use fairly lax options for verification, as // CT is intended to observe certificates rather than police them. verifyOpts := x509.VerifyOptions{ Roots: validationOpts.trustedRoots.CertPool(), CurrentTime: now, Intermediates: intermediatePool.CertPool(), DisableTimeChecks: true, // Precertificates have the poison extension; also the Go library code does not // support the standard PolicyConstraints extension (which is required to be marked // critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions. DisableCriticalExtensionChecks: true, // Pre-issued precertificates have the Certificate Transparency EKU; also some // leaves have unknown EKUs that should not be bounced just because the intermediate // does not also have them (cf. https://github.com/golang/go/issues/24590) so // disable EKU checks inside the x509 library, but we've already done our own check // on the leaf above. DisableEKUChecks: true, // Path length checks get confused by the presence of an additional // pre-issuer intermediate, so disable them. DisablePathLenChecks: true, DisableNameConstraintChecks: true, DisableNameChecks: false, KeyUsages: validationOpts.extKeyUsages, } verifiedChains, err := cert.Verify(verifyOpts) if err != nil { return nil, err } if len(verifiedChains) == 0 { return nil, errors.New("no path to root found when trying to validate chains") } // Verify might have found multiple paths to roots. Now we check that we have a path that // uses all the certs in the order they were submitted so as to comply with RFC 6962 // requirements detailed in Section 3.1. for _, verifiedChain := range verifiedChains { if chainsEquivalent(chain, verifiedChain) { return verifiedChain, nil } } return nil, ErrNoRFCCompliantPathFound } func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool { // The verified chain includes a root, but the input chain may or may not include a // root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate // or a certificate that chains to a known root certificate"). if len(inChain) != len(verifiedChain) && len(inChain) != (len(verifiedChain)-1) { return false } for i, certInChain := range inChain { if !certInChain.Equal(verifiedChain[i]) { return false } } return true } google-certificate-transparency-go-2308f62/trillian/ctfe/cert_checker_test.go000066400000000000000000000601571462611535200274200ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "encoding/base64" "encoding/pem" "strings" "testing" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" "github.com/google/certificate-transparency-go/x509util" ) func wipeExtensions(cert *x509.Certificate) *x509.Certificate { cert.Extensions = cert.Extensions[:0] return cert } func makePoisonNonCritical(cert *x509.Certificate) *x509.Certificate { // Invalid as a pre-cert because poison extension needs to be marked as critical. cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: asn1.NullBytes}} return cert } func makePoisonNonNull(cert *x509.Certificate) *x509.Certificate { // Invalid as a pre-cert because poison extension is not ASN.1 NULL value. cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: []byte{0x42, 0x42, 0x42}}} return cert } func TestIsPrecertificate(t *testing.T) { var tests = []struct { desc string cert *x509.Certificate wantPrecert bool wantErr bool }{ { desc: "valid-precert", cert: pemToCert(t, testonly.PrecertPEMValid), wantPrecert: true, }, { desc: "valid-cert", cert: pemToCert(t, testonly.CACertPEM), wantPrecert: false, }, { desc: "remove-exts-from-precert", cert: wipeExtensions(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, }, { desc: "poison-non-critical", cert: makePoisonNonCritical(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, wantErr: true, }, { desc: "poison-non-null", cert: makePoisonNonNull(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, wantErr: true, }, } for _, test := range tests { gotPrecert, err := IsPrecertificate(test.cert) t.Run(test.desc, func(t *testing.T) { if err != nil { if !test.wantErr { t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert) } return } if test.wantErr { t.Errorf("IsPrecertificate()=%v,%v; want _,%v", gotPrecert, err, test.wantErr) } if gotPrecert != test.wantPrecert { t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert) } }) } } func TestValidateChain(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeRootCACertPEM)) { t.Fatal("failed to load fake root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.CACertPEM)) { t.Fatal("failed to load CA root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.RealPrecertIntermediatePEM)) { t.Fatal("failed to load real intermediate") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, } var tests = []struct { desc string chain [][]byte wantErr bool wantPathLen int modifyOpts func(v *CertValidationOpts) }{ { desc: "missing-intermediate-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM}), wantErr: true, }, { desc: "wrong-cert-order", chain: pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.LeafSignedByFakeIntermediateCertPEM}), wantErr: true, }, { desc: "unrelated-cert-in-chain", chain: pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}), wantErr: true, }, { desc: "unrelated-cert-after-chain", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}), wantErr: true, }, { desc: "valid-chain", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-policyconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-policyconstraints-inc-root", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM, testonly.FakeRootCACertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-nameconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithNameConstraintsCertPEM}), wantPathLen: 3, }, { desc: "chain-with-invalid-nameconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithInvalidNameConstraintsCertPEM}), wantPathLen: 3, }, { desc: "chain-of-len-4", chain: pemFileToDERChain(t, "../testdata/subleaf.chain"), wantPathLen: 4, }, { desc: "misordered-chain-of-len-4", chain: pemFileToDERChain(t, "../testdata/subleaf.misordered.chain"), wantErr: true, }, { desc: "reject-non-existent-ext-id", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{99, 99, 99, 99}} }, wantPathLen: 3, }, { desc: "reject-non-existent-ext-id-precert", chain: pemsToDERChain(t, []string{testonly.PrecertPEMValid}), modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{99, 99, 99, 99}} }, wantPathLen: 2, }, { desc: "reject-ext-id", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{2, 5, 29, 14}} }, }, { desc: "reject-ext-id-precert", chain: pemsToDERChain(t, []string{testonly.PrecertPEMValid}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{2, 5, 29, 14}} }, }, { desc: "reject-eku-not-present-in-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject cert without ExtKeyUsageEmailProtection v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection} }, }, { desc: "allow-eku-present-in-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantPathLen: 3, modifyOpts: func(v *CertValidationOpts) { v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} }, }, { desc: "reject-eku-not-present-in-precert", chain: pemsToDERChain(t, []string{testonly.RealPrecertWithEKUPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject cert without ExtKeyUsageEmailProtection v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection} }, }, { desc: "allow-eku-present-in-precert", chain: pemsToDERChain(t, []string{testonly.RealPrecertWithEKUPEM}), wantPathLen: 2, modifyOpts: func(v *CertValidationOpts) { v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { validateOpts := validateOpts if test.modifyOpts != nil { test.modifyOpts(&validateOpts) } gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) return } if len(gotPath) != test.wantPathLen { t.Errorf("|ValidateChain()|=%d; want %d", len(gotPath), test.wantPathLen) for _, c := range gotPath { t.Logf("Subject: %s Issuer: %s", x509util.NameToString(c.Subject), x509util.NameToString(c.Issuer)) } } }) } } func TestCA(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, } chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) leaf, err := x509.ParseCertificate(chain[0]) if x509.IsFatal(err) { t.Fatalf("Failed to parse golden certificate DER: %v", err) } t.Logf("Cert expiry date: %v", leaf.NotAfter) var tests = []struct { desc string chain [][]byte caOnly bool wantErr bool }{ { desc: "end-entity, allow non-CA", chain: chain, }, { desc: "end-entity, disallow non-CA", chain: chain, caOnly: true, wantErr: true, }, { desc: "intermediate, allow non-CA", chain: chain[1:], }, { desc: "intermediate, disallow non-CA", chain: chain[1:], caOnly: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { validateOpts.acceptOnlyCA = test.caOnly gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) } }) } } func TestNotAfterRange(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, rejectExpired: false, } chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) var tests = []struct { desc string chain [][]byte notAfterStart time.Time notAfterLimit time.Time wantErr bool }{ { desc: "valid-chain, no range", chain: chain, }, { desc: "valid-chain, valid range", chain: chain, notAfterStart: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), notAfterLimit: time.Date(2020, 7, 1, 0, 0, 0, 0, time.UTC), }, { desc: "before valid range", chain: chain, notAfterStart: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), wantErr: true, }, { desc: "after valid range", chain: chain, notAfterLimit: time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC), wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { if !test.notAfterStart.IsZero() { validateOpts.notAfterStart = &test.notAfterStart } if !test.notAfterLimit.IsZero() { validateOpts.notAfterLimit = &test.notAfterLimit } gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) } }) } } func TestRejectExpiredUnexpired(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() // Validity period: Jul 11, 2016 - Jul 11, 2017. if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } // Validity period: May 13, 2016 - Jul 12, 2019. chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, extKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, } beforeValidPeriod := time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC) currentValidPeriod := time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC) afterValidPeriod := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) for _, tc := range []struct { desc string rejectExpired bool rejectUnexpired bool now time.Time wantErr string }{ // No flags: accept anything. { desc: "no-reject-current", now: currentValidPeriod, }, { desc: "no-reject-after", now: afterValidPeriod, }, { desc: "no-reject-before", now: beforeValidPeriod, }, // Reject-Expired: only allow currently-valid and not yet valid { desc: "reject-expired-current", rejectExpired: true, now: currentValidPeriod, }, { desc: "reject-expired-after", rejectExpired: true, now: afterValidPeriod, wantErr: "rejecting expired certificate", }, { desc: "reject-expired-before", rejectExpired: true, now: beforeValidPeriod, }, // Reject-Unexpired: only allow expired { desc: "reject-non-expired-after", rejectUnexpired: true, now: afterValidPeriod, }, { desc: "reject-non-expired-before", rejectUnexpired: true, now: beforeValidPeriod, wantErr: "rejecting unexpired certificate", }, { desc: "reject-non-expired-current", rejectUnexpired: true, now: currentValidPeriod, wantErr: "rejecting unexpired certificate", }, // Reject-Expired AND Reject-Unexpired: nothing allowed { desc: "reject-all-after", rejectExpired: true, rejectUnexpired: true, now: afterValidPeriod, wantErr: "rejecting expired certificate", }, { desc: "reject-all-before", rejectExpired: true, rejectUnexpired: true, now: beforeValidPeriod, wantErr: "rejecting unexpired certificate", }, { desc: "reject-all-current", rejectExpired: true, rejectUnexpired: true, now: currentValidPeriod, wantErr: "rejecting unexpired certificate", }, } { t.Run(tc.desc, func(t *testing.T) { validateOpts.currentTime = tc.now validateOpts.rejectExpired = tc.rejectExpired validateOpts.rejectUnexpired = tc.rejectUnexpired _, err := ValidateChain(chain, validateOpts) if err != nil { if len(tc.wantErr) == 0 { t.Errorf("ValidateChain()=_,%v; want _,nil", err) } else if !strings.Contains(err.Error(), tc.wantErr) { t.Errorf("ValidateChain()=_,%v; want err containing %q", err, tc.wantErr) } } else if len(tc.wantErr) != 0 { t.Errorf("ValidateChain()=_,nil; want err containing %q", tc.wantErr) } }) } } // Builds a chain of DER-encoded certs. // Note: ordering is important func pemsToDERChain(t *testing.T, pemCerts []string) [][]byte { t.Helper() chain := make([][]byte, 0, len(pemCerts)) for _, pemCert := range pemCerts { cert := pemToCert(t, pemCert) chain = append(chain, cert.Raw) } return chain } func pemToCert(t *testing.T, pemData string) *x509.Certificate { t.Helper() bytes, rest := pem.Decode([]byte(pemData)) if len(rest) > 0 { t.Fatalf("Extra data after PEM: %v", rest) return nil } cert, err := x509.ParseCertificate(bytes.Bytes) if x509.IsFatal(err) { t.Fatal(err) } return cert } func pemFileToDERChain(t *testing.T, filename string) [][]byte { t.Helper() rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { t.Fatalf("failed to load testdata: %v", err) } return rawChain } // Validate a chain including a pre-issuer as produced by Google's Compliance Monitor. func TestCMPreIssuedCert(t *testing.T) { var b64Chain = []string{ "MIID+jCCAuKgAwIBAgIHBWW7shJizTANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJHQjEPMA0GA1UEBwwGTG9uZG9uMTowOAYDVQQKDDFHb29nbGUgQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IChQcmVjZXJ0IFNpZ25pbmcpMRkwFwYDVQQFExAxNTE5MjMxNzA0MTczNDg3MB4XDTE4MDIyMTE2NDgyNFoXDTE4MTIwMTIwMzMyN1owYzELMAkGA1UEBhMCR0IxDzANBgNVBAcMBkxvbmRvbjEoMCYGA1UECgwfR29vZ2xlIENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEZMBcGA1UEBRMQMTUxOTIzMTcwNDM5MjM5NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKnKP9TP6hkEuD+d1rPeA8mxo5xFYffhCcEitP8PtTl7G2RqFrndPeAkzgvOxPB3Jrhx7LtMtg0IvS8y7Sy1qDqDou1/OrJgwCeWMc1/KSneuGP8GTX0Rqy4z8+LsiBN/tMDbt94RuiyCeltIAaHGmsNeYXV34ayD3vSIAQbtLUOD39KqrJWO0tQ//nshBuFlebiUrDP7rirPusYYW0stJKiCKeORhHvL3/I8mCYGNO0XIWMpASH2S9LGMwg+AQM13whC1KL65EGuVs4Ta0rO+Tl8Yi0is0RwdUmgdSGtl0evPTzyUXbA1n1BpkLcSQ5E3RxY3O6Ge9Whvtmg9vAJiMCAwEAAaOBoDCBnTATBgNVHSUEDDAKBggrBgEFBQcDATAjBgNVHREEHDAaghhmbG93ZXJzLXRvLXRoZS13b3JsZC5jb20wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRKCM/Ajh0Fu6FFjJ9F4gVWK2oj/jAdBgNVHQ4EFgQUVjYl6wDey3DxvmTG2HL4vdiUt+MwEwYKKwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAAvyEFDIAWr0URsZzrJLZEL8p6FMTzVxY/MOvGP8QMXA6xNVElxYnDPF32JERAl+poR7syByhVFcEjrw7f2FTlMc04+hT/hsYzi8cMAmfX9KA36xUBVjyqvqwofxTwoWYdf+eGZW0EG8Yp1pM7iUy9bdlh3sgdOpmT9Z5XGCRwvdW1+mctv0JMKDdWzxBqYyNMnNjvjHBmkiuHeDDGFsV2zq+wV64RwJa2eVrnkMDMV1mscL6KzNRLPP2ZpNz/8H7SPock+fk4cZrdqj+0IzFt+6ixSoKyltyD+nkbWjRGY4iyboo/nPgTQ1IQCS2OPVHWw3NijFD8hqgAnYvz0Dn+k=", "MIIE4jCCAsqgAwIBAgIHBWW7sg8LrzANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMRcwFQYDVQQKDA5Hb29nbGUgVUsgTHRkLjEhMB8GA1UECwwYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSMwIQYDVQQDDBpNZXJnZSBEZWxheSBJbnRlcm1lZGlhdGUgMTAeFw0xODAyMjExNjQ4MjRaFw0xODEyMDEyMDM0MjdaMHUxCzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xOjA4BgNVBAoMMUdvb2dsZSBDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgKFByZWNlcnQgU2lnbmluZykxGTAXBgNVBAUTEDE1MTkyMzE3MDQxNzM0ODcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKWlc3A43kJ9IzmkCPXcsGwTxlIvtl9sNYBWlx9qqHa1i6tU6rZuH9uXAb3wsn39fqY22HzF/yrx9pd05doFfRq6dvvm4eHNFfFm4cJur1kmPe8vLKpSI/P2DPx4/mRzrHnPAI8Jo9QgKcj91AyYeB689ZFzH30ay32beo6PxQvtoJkzl+dzf9Hs1ezavS7nDCuqDnu1V1Og7J5xTHZeNyTKgD5Kx28ukmIp2wGOvg3omuInABg/ew0VxnG/txKV+69zfV9dhclU3m16L81e3RkJ8Kg4RLb0mh9X3EMn90SpJ9yw0j8FF0Esk6wxuYeUGLShUji8BPnnbactY9B6ORAgMBAAGjbTBrMBgGA1UdJQEB/wQOMAwGCisGAQQB1nkCBAQwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTpPAThgC/ChBMtJnCe8v0az6r+xjAdBgNVHQ4EFgQUSgjPwI4dBbuhRYyfReIFVitqI/4wDQYJKoZIhvcNAQEFBQADggIBAEep2uWAFsdq1nLtLWLGh7DfVPc/K+1lcqNx64ucjpVZbDnMnYKFagf2Z9rHEWqR7kwuLac5xW8woSlLa/NHmJmdg18HGUhlS+x8iMPv7dK6hfNsRFdjLZkZOFneuf9j1b0dV+rXoRvyY+Oq+lomC98bEr+g9zq+M7wJ4wS/KeaNHpPw1pBeTtCdw+1c4ZgRTOEa2OUUpkpueJ+9psD/hbp6HLF+WYijWQ0/iYSxJ4TbjTC+omKRsGhvxSLbP8cSMt3X1pJgrFK1BvH4lqqEXGDNEiVNoPCHraEa8JtMZIo47/Af13lDfp6sBdZ0lvLAVDduWgg/2RkWCbHefAe81h+cYdDS775TF2TCMTwsR6GsM9sVCbfPvHXI/pUzamRn0i0CrhyccBBdPrUhj+cXuc9kqSkLegun9D8EBDMM9va5wb1HM0ruSno+YuLtfhCdBRHr/RG2BKJi7uUDjJ8goHov/EUJmHjAIARKz74IPWRkxMrnOvGhnNa2Hz+da3hpusz0Mj4rsqv1EKTC2wbCs6Rk2MRPSxdRbywdWLSmGn249SMfXK4An+dqoRk1fwKqdXc4swoUvxnGUi5ajBaRtc6631zBTmvmSFQnvGmS42aF7q2PjfvWPIuO+d//m8KgN6o2YyjrdPDDslI2RZUE5ngOR+JynvhjYrrB7Bat1EY7", "MIIFyDCCA7CgAwIBAgICEAEwDQYJKoZIhvcNAQEFBQAwfTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEhMB8GA1UEAwwYTWVyZ2UgRGVsYXkgTW9uaXRvciBSb290MB4XDTE0MDcxNzEyMjYzMFoXDTE5MDcxNjEyMjYzMFowfzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEGA1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDB6HT+/5ru8wO7+mNFOIH6r43BwiwJZB2vQwOB8zvBV79sTIqNV7Grx5KFnSDyGRUJxZfEN7FGc96lr0vqFDlt1DbcYgVV15U+Dt4B9/+0Tz/3zeZO0kVjTg3wqvzpw6xetj2N4dlpysiFQZVAOp+dHUw9zu3xNR7dlFdDvFSrdFsgT7Uln+Pt9pXCz5C4hsSP9oC3RP7CaRtDRSQrMcNvMRi3J8XeXCXsGqMKTCRhxRGe9ruQ2Bbm5ExbmVW/ou00Fr9uSlPJL6+sDR8Li/PTW+DU9hygXSj8Zi36WI+6PuA4BHDAEt7Z5Ru/Hnol76dFeExJ0F6vjc7gUnNh7JExJgBelyz0uGORT4NhWC7SRWP/ngPFLoqcoyZMVsGGtOxSt+aVzkKuF+x64CVxMeHb9I8t3iQubpHqMEmIE1oVSCsF/AkTVTKLOeWG6N06SjoUy5fu9o+faXKMKR8hldLM5z1K6QhFsb/F+uBAuU/DWaKVEZgbmWautW06fF5I+OyoFeW+hrPTbmon4OLE3ubjDxKnyTa4yYytWSisojjfw5z58sUkbLu7KAy2+Z60m/0deAiVOQcsFkxwgzcXRt7bxN7By5Q5Bzrz8uYPjFBfBnlhqMU5RU/FNBFY7Mx4Uy8+OcMYfJQ5/A/4julXEx1HjfBj3VCyrT/noHDpBeOGiwIDAQABo1AwTjAdBgNVHQ4EFgQU6TwE4YAvwoQTLSZwnvL9Gs+q/sYwHwYDVR0jBBgwFoAU8197dUnjeEE5aiC2fGtMXMk9WEEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEACFjL1UXy6S4JkGrDnz1VwTYHplFDY4bG6Q8Sh3Og6z9HJdivNft/iAQ2tIHyz0eAGCXeVPE/j1kgvz2RbnUxQd5eWdLeu/w/wiZyHxWhbTt6RhjqBVFjnx0st7n6rRt+Bw8jpugZfD11SbumVT/V20Gc45lHf2oEgbkPUcnTB9gssFz5Z4KKGs5lIHz4a20WeSJF3PJLTBefkRhHNufi/LhjpLXImwrC82g5ChBZS5XIVuJZx3VkMWiYz4emgX0YWF/JdtaB2dUQ7yrTforQ5J9b1JnJ7H/o9DsX3/ubfQ39gwDBxTicnqC+Q3Dcv3i9PvwjCNJQuGa7ygMcDEn/d6elQg2qHxtqRE02ZlOXTC0XnDAJhx7myJFA/Knv3yO9S4jG6665KG9Y88/CHkh08YLR7NYFiRmwOxjbe3lb6csl/FFmqUXvjhEzzWAxKjI09GSd9hZkB8u17Mg46eEYwF3ufIlqmYdlWufjSc2BZuaNNN6jtK6JKp8jhQUycehgtUK+NlBQOXTzu28miDdasoSH2mdR0PLDo1547+MLGdV4COvqLERTmQrYHrliicD5nFCA+CCSvGEjo0DGOmF/O8StwSmNiKJ4ppPvk2iGEdO07e0LbQI+2fbC6og2SDGXUlsbG85wqQw0A7CU1fQSqhFBuZZauDFMUvdy3v/BAIw=", "MIIFzTCCA7WgAwIBAgIJAJ7TzLHRLKJyMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMMGE1lcmdlIERlbGF5IE1vbml0b3IgUm9vdDAeFw0xNDA3MTcxMjA1NDNaFw00MTEyMDIxMjA1NDNaMH0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMMGE1lcmdlIERlbGF5IE1vbml0b3IgUm9vdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoWHPIgXtgaxWVIPNpCaj2y5Yj9t1ixe5PqjWhJXVNKAbpPbNHA/AoSivecBm3FTD9DfgW6J17mHb+cvbKSgYNzgTk5e2GJrnOP7yubYJpt2OCw0OILJD25NsApzcIiCvLA4aXkqkGgBq9FiVfisReNJxVu8MtxfhbVQCXZf0PpkW+yQPuF99V5Ri+grHbHYlaEN1C/HM3+t2yMR4hkd2RNXsMjViit9qCchIi/pQNt5xeQgVGmtYXyc92ftTMrmvduj7+pHq9DEYFt3ifFxE8v0GzCIE1xR/d7prFqKl/KRwAjYUcpU4vuazywcmRxODKuwWFVDrUBkGgCIVIjrMJWStH5i7WTSSTrVtOD/HWYvkXInZlSgcDvsNIG0pptJaEKSP4jUzI3nFymnoNZn6pnfdIII/XISpYSVeyl1IcdVMod8HdKoRew9CzW6f2n6KSKU5I8X5QEM1NUTmRLWmVi5c75/CvS/PzOMyMzXPf+fE2Dwbf4OcR5AZLTupqp8yCTqo7ny+cIBZ1TjcZjzKG4JTMaqDZ1Sg0T3mO/ZbbiBE3N8EHxoMWpw8OP50z1dtRRwj6qUZ2zLvngOb2EihlMO15BpVZC3Cg929c9Hdl65pUd4YrYnQBQB/rn6IvHo8zot8zElgOg22fHbViijUt3qnRggB40N30MXkYGwuJbAgMBAAGjUDBOMB0GA1UdDgQWBBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAfBgNVHSMEGDAWgBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQB3HP6jRXmpdSDYwkI9aOzQeJH4x/HDi/PNMOqdNje/xdNzUy7HZWVYvvSVBkZ1DG/ghcUtn/wJ5m6/orBn3ncnyzgdKyXbWLnCGX/V61PgIPQpuGo7HzegenYaZqWz7NeXxGaVo3/y1HxUEmvmvSiioQM1cifGtz9/aJsJtIkn5umlImenKKEV1Ly7R3Uz3Cjz/Ffac1o+xU+8NpkLF/67fkazJCCMH6dCWgy6SL3AOB6oKFIVJhw8SD8vptHaDbpJSRBxifMtcop/85XUNDCvO4zkvlB1vPZ9ZmYZQdyL43NA+PkoKy0qrdaQZZMq1Jdp+Lx/yeX255/zkkILp43jFyd44rZ+TfGEQN1WHlp4RMjvoGwOX1uGlfoGkRSgBRj7TBn514VYMbXu687RS4WY2v+kny3PUFv/ZBfYSyjoNZnU4Dce9kstgv+gaKMQRPcyL+4vZU7DV8nBIfNFilCXKMN/VnNBKtDV52qmtOsVghgai+QE09w15x7dg+44gIfWFHxNhvHKys+s4BBN8fSxAMLOsb5NGFHE8x58RAkmIYWHjyPM6zB5AUPw1b2A0sDtQmCqoxJZfZUKrzyLz8gS2aVujRYN13KklHQ3EKfkeKBG2KXVBe5rjMN/7Anf1MtXxsTY6O8qIuHZ5QlXhSYzE41yIlPlG6d7AGnTiBIgeg==", } rawChain := make([][]byte, len(b64Chain)) for i, b64Data := range b64Chain { var err error rawChain[i], err = base64.StdEncoding.DecodeString(b64Data) if err != nil { t.Fatalf("failed to base64.Decode(chain[%d]): %v", i, err) } } root, err := x509.ParseCertificate(rawChain[len(rawChain)-1]) if err != nil { t.Fatalf("failed to parse root cert: %v", err) } cmRoot := x509util.NewPEMCertPool() cmRoot.AddCert(root) for _, tc := range []struct { desc string eku []x509.ExtKeyUsage }{ { desc: "no EKU specified", }, { desc: "EKU ServerAuth", eku: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, }, } { t.Run(tc.desc, func(t *testing.T) { opts := CertValidationOpts{ trustedRoots: cmRoot, extKeyUsages: tc.eku, } chain, err := ValidateChain(rawChain, opts) if err != nil { t.Fatalf("failed to ValidateChain: %v", err) } for i, c := range chain { t.Logf("chain[%d] = \n%s", i, x509util.CertificateToString(c)) } }) } } google-certificate-transparency-go-2308f62/trillian/ctfe/cert_quota.go000066400000000000000000000030001462611535200260660ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "crypto/sha256" "encoding/hex" "fmt" "strings" "github.com/google/certificate-transparency-go/x509" ) // CertificateQuotaUserPrefix is prepended to all User quota ids association // with intermediate certificates. const CertificateQuotaUserPrefix = "@intermediate" // QuotaUserForCert returns a User quota id string for the passed in // certificate. // This is intended to be used for quota limiting by intermediate certificates, // but the function does not enforce anything about the passed in cert. // // Format returned is: // // "CertificateQuotaUserPrefix Subject hex(SHA256(SubjectPublicKeyInfo)[0:5])" // // See tests for examples. func QuotaUserForCert(c *x509.Certificate) string { spkiHash := sha256.Sum256(c.RawSubjectPublicKeyInfo) return fmt.Sprintf("%s %s %s", CertificateQuotaUserPrefix, strings.ReplaceAll(c.Subject.String(), "/", "%2F"), hex.EncodeToString(spkiHash[0:5])) } google-certificate-transparency-go-2308f62/trillian/ctfe/cert_quota_test.go000066400000000000000000000032651462611535200271420ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "testing" "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) func mustDePEM(t *testing.T, pem string) *x509.Certificate { t.Helper() c, err := x509util.CertificateFromPEM([]byte(pem)) if x509.IsFatal(err) { t.Fatalf("Failed to parse PEM: %v", err) } return c } func TestQuotaUserForCert(t *testing.T) { for _, test := range []struct { desc string cert *x509.Certificate want string }{ { desc: "cacert", cert: mustDePEM(t, testonly.CACertPEM), want: "@intermediate O=Certificate Transparency CA,L=Erw Wen,ST=Wales,C=GB 02adddca08", }, { desc: "intermediate", cert: mustDePEM(t, testonly.FakeIntermediateCertPEM), want: "@intermediate CN=FakeIntermediateAuthority,OU=Eng,O=Google,L=London,ST=London,C=GB 6e62e56f67", }, } { t.Run(test.desc, func(t *testing.T) { if got := QuotaUserForCert(test.cert); got != test.want { t.Fatalf("QuotaUserForCert() = %q, want %q", got, test.want) } }) } } google-certificate-transparency-go-2308f62/trillian/ctfe/config.go000066400000000000000000000321621462611535200252000ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "crypto" "errors" "fmt" "os" "strings" "time" "github.com/go-sql-driver/mysql" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" "k8s.io/klog/v2" ) // ValidatedLogConfig represents the LogConfig with the information that has // been successfully parsed as a result of validating it. type ValidatedLogConfig struct { Config *configpb.LogConfig PubKey crypto.PublicKey PrivKey proto.Message KeyUsages []x509.ExtKeyUsage NotAfterStart *time.Time NotAfterLimit *time.Time FrozenSTH *ct.SignedTreeHead CTFEStorageConnectionString string ExtraDataIssuanceChainStorageBackend configpb.LogConfig_IssuanceChainStorageBackend } // LogConfigFromFile creates a slice of LogConfig options from the given // filename, which should contain text or binary-encoded protobuf configuration // data. func LogConfigFromFile(filename string) ([]*configpb.LogConfig, error) { cfgBytes, err := os.ReadFile(filename) if err != nil { return nil, err } var cfg configpb.LogConfigSet if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil { if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil { return nil, fmt.Errorf("failed to parse LogConfigSet from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr) } } if len(cfg.Config) == 0 { return nil, errors.New("empty log config found") } return cfg.Config, nil } // ToMultiLogConfig creates a multi backend config proto from the data // loaded from a single-backend configuration file. All the log configs // reference a default backend spec as provided. func ToMultiLogConfig(cfg []*configpb.LogConfig, beSpec string) *configpb.LogMultiConfig { defaultBackend := &configpb.LogBackend{Name: "default", BackendSpec: beSpec} for _, c := range cfg { c.LogBackendName = defaultBackend.Name } return &configpb.LogMultiConfig{ LogConfigs: &configpb.LogConfigSet{Config: cfg}, Backends: &configpb.LogBackendSet{Backend: []*configpb.LogBackend{defaultBackend}}, } } // MultiLogConfigFromFile creates a LogMultiConfig proto from the given // filename, which should contain text or binary-encoded protobuf configuration data. // Does not do full validation of the config but checks that it is non empty. func MultiLogConfigFromFile(filename string) (*configpb.LogMultiConfig, error) { cfgBytes, err := os.ReadFile(filename) if err != nil { return nil, err } var cfg configpb.LogMultiConfig if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil { if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil { return nil, fmt.Errorf("failed to parse LogMultiConfig from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr) } } if len(cfg.LogConfigs.GetConfig()) == 0 || len(cfg.Backends.GetBackend()) == 0 { return nil, errors.New("config is missing backends and/or log configs") } return &cfg, nil } // ValidateLogConfig checks that a single log config is valid. In particular: // - A mirror log has a valid public key and no private key. // - A non-mirror log has a private, and optionally a public key (both valid). // - Each of NotBeforeStart and NotBeforeLimit, if set, is a valid timestamp // proto. If both are set then NotBeforeStart <= NotBeforeLimit. // - Merge delays (if present) are correct. // - Frozen STH (if present) is correct and signed by the provided public key. // // Returns the validated structures (useful to avoid double validation). func ValidateLogConfig(cfg *configpb.LogConfig) (*ValidatedLogConfig, error) { if cfg.LogId == 0 { return nil, errors.New("empty log ID") } vCfg := ValidatedLogConfig{Config: cfg} // Validate the public key. if pubKey := cfg.PublicKey; pubKey != nil { var err error if vCfg.PubKey, err = x509.ParsePKIXPublicKey(pubKey.Der); err != nil { return nil, fmt.Errorf("x509.ParsePKIXPublicKey: %w", err) } } else if cfg.IsMirror { return nil, errors.New("empty public key for mirror") } else if cfg.FrozenSth != nil { return nil, errors.New("empty public key for frozen STH") } // Validate the private key. if !cfg.IsMirror { if cfg.PrivateKey == nil { return nil, errors.New("empty private key") } privKey, err := cfg.PrivateKey.UnmarshalNew() if err != nil { return nil, fmt.Errorf("invalid private key: %v", err) } vCfg.PrivKey = privKey } else if cfg.PrivateKey != nil { return nil, errors.New("unnecessary private key for mirror") } if cfg.RejectExpired && cfg.RejectUnexpired { return nil, errors.New("rejecting all certificates") } // Validate the extended key usages list. if len(cfg.ExtKeyUsages) > 0 { for _, kuStr := range cfg.ExtKeyUsages { if ku, ok := stringToKeyUsage[kuStr]; ok { // If "Any" is specified, then we can ignore the entire list and // just disable EKU checking. if ku == x509.ExtKeyUsageAny { klog.Infof("%s: Found ExtKeyUsageAny, allowing all EKUs", cfg.Prefix) vCfg.KeyUsages = nil break } vCfg.KeyUsages = append(vCfg.KeyUsages, ku) } else { return nil, fmt.Errorf("unknown extended key usage: %s", kuStr) } } } // Validate the time interval. start, limit := cfg.NotAfterStart, cfg.NotAfterLimit if start != nil { vCfg.NotAfterStart = &time.Time{} if err := start.CheckValid(); err != nil { return nil, fmt.Errorf("invalid start timestamp: %v", err) } *vCfg.NotAfterStart = start.AsTime() } if limit != nil { vCfg.NotAfterLimit = &time.Time{} if err := limit.CheckValid(); err != nil { return nil, fmt.Errorf("invalid limit timestamp: %v", err) } *vCfg.NotAfterLimit = limit.AsTime() } if start != nil && limit != nil && (*vCfg.NotAfterLimit).Before(*vCfg.NotAfterStart) { return nil, errors.New("limit before start") } switch { case cfg.MaxMergeDelaySec < 0: return nil, errors.New("negative maximum merge delay") case cfg.ExpectedMergeDelaySec < 0: return nil, errors.New("negative expected merge delay") case cfg.ExpectedMergeDelaySec > cfg.MaxMergeDelaySec: return nil, errors.New("expected merge delay exceeds MMD") } if sth := cfg.FrozenSth; sth != nil { verifier, err := ct.NewSignatureVerifier(vCfg.PubKey) if err != nil { return nil, fmt.Errorf("failed to create signature verifier: %v", err) } if vCfg.FrozenSTH, err = (&ct.GetSTHResponse{ TreeSize: uint64(sth.TreeSize), Timestamp: uint64(sth.Timestamp), SHA256RootHash: sth.Sha256RootHash, TreeHeadSignature: sth.TreeHeadSignature, }).ToSignedTreeHead(); err != nil { return nil, fmt.Errorf("invalid frozen STH: %v", err) } if err := verifier.VerifySTHSignature(*vCfg.FrozenSTH); err != nil { return nil, fmt.Errorf("signature verification failed: %v", err) } } switch cfg.ExtraDataIssuanceChainStorageBackend { case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE: // Validate the combination of CtfeStorageConnectionString and ExtraDataIssuanceChainStorageBackend if len(cfg.CtfeStorageConnectionString) == 0 { return nil, errors.New("missing ctfe_storage_connection_string when issuance chain storage backend is CTFE") } // Validate CTFEStorageConnectionString if !strings.HasPrefix(cfg.CtfeStorageConnectionString, "mysql") { return nil, errors.New("unsupported driver in ctfe_storage_connection_string") } if _, err := mysql.ParseDSN(strings.Split(cfg.CtfeStorageConnectionString, "://")[1]); err != nil { return nil, errors.New("failed to parse ctfe_storage_connection_string for mysql driver") } vCfg.CTFEStorageConnectionString = cfg.CtfeStorageConnectionString case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC: // Nothing to validate for Trillian gRPC } vCfg.ExtraDataIssuanceChainStorageBackend = cfg.ExtraDataIssuanceChainStorageBackend return &vCfg, nil } // LogBackendMap is a map from log backend names to LogBackend objects. type LogBackendMap = map[string]*configpb.LogBackend // BuildLogBackendMap returns a map from log backend names to the corresponding // LogBackend objects. It returns an error unless all backends have unique // non-empty names and specifications. func BuildLogBackendMap(lbs *configpb.LogBackendSet) (LogBackendMap, error) { lbm := make(LogBackendMap) specs := make(map[string]bool) for _, be := range lbs.Backend { if len(be.Name) == 0 { return nil, fmt.Errorf("empty backend name: %v", be) } if len(be.BackendSpec) == 0 { return nil, fmt.Errorf("empty backend spec: %v", be) } if _, ok := lbm[be.Name]; ok { return nil, fmt.Errorf("duplicate backend name: %v", be) } if ok := specs[be.BackendSpec]; ok { return nil, fmt.Errorf("duplicate backend spec: %v", be) } lbm[be.Name] = be specs[be.BackendSpec] = true } return lbm, nil } func validateConfigs(cfg []*configpb.LogConfig) error { // Check that logs have no duplicate or empty prefixes. Apply other LogConfig // specific checks. logNameMap := make(map[string]bool) for _, logCfg := range cfg { if _, err := ValidateLogConfig(logCfg); err != nil { return fmt.Errorf("log config: %v: %v", err, logCfg) } if len(logCfg.Prefix) == 0 { return fmt.Errorf("log config: empty prefix: %v", logCfg) } if logNameMap[logCfg.Prefix] { return fmt.Errorf("log config: duplicate prefix: %s: %v", logCfg.Prefix, logCfg) } logNameMap[logCfg.Prefix] = true } return nil } // ValidateLogConfigs checks that a config is valid for use with a single log // server. The rules applied are: // // 1. All log configs must be valid (see ValidateLogConfig). // 2. The prefixes of configured logs must all be distinct and must not be // empty. // 3. The set of tree IDs must be distinct. func ValidateLogConfigs(cfg []*configpb.LogConfig) error { if err := validateConfigs(cfg); err != nil { return err } // Check that logs have no duplicate tree IDs. treeIDs := make(map[int64]bool) for _, logCfg := range cfg { if treeIDs[logCfg.LogId] { return fmt.Errorf("log config: dup tree id: %d for: %v", logCfg.LogId, logCfg) } treeIDs[logCfg.LogId] = true } return nil } // ValidateLogMultiConfig checks that a config is valid for use with multiple // backend log servers. The rules applied are the same as ValidateLogConfigs, as // well as these additional rules: // // 1. The backend set must define a set of log backends with distinct // (non empty) names and non empty backend specs. // 2. The backend specs must all be distinct. // 3. The log configs must all specify a log backend and each must be one of // those defined in the backend set. // // Also, another difference is that the tree IDs need only to be distinct per // backend. // // TODO(pavelkalinnikov): Replace the returned map with a fully fledged // ValidatedLogMultiConfig that contains a ValidatedLogConfig for each log. func ValidateLogMultiConfig(cfg *configpb.LogMultiConfig) (LogBackendMap, error) { backendMap, err := BuildLogBackendMap(cfg.Backends) if err != nil { return nil, err } if err := validateConfigs(cfg.GetLogConfigs().GetConfig()); err != nil { return nil, err } // Check that logs all reference a defined backend. logIDMap := make(map[string]bool) for _, logCfg := range cfg.LogConfigs.Config { if _, ok := backendMap[logCfg.LogBackendName]; !ok { return nil, fmt.Errorf("log config: references undefined backend: %s: %v", logCfg.LogBackendName, logCfg) } logIDKey := fmt.Sprintf("%s-%d", logCfg.LogBackendName, logCfg.LogId) if ok := logIDMap[logIDKey]; ok { return nil, fmt.Errorf("log config: dup tree id: %d for: %v", logCfg.LogId, logCfg) } logIDMap[logIDKey] = true } return backendMap, nil } var stringToKeyUsage = map[string]x509.ExtKeyUsage{ "Any": x509.ExtKeyUsageAny, "ServerAuth": x509.ExtKeyUsageServerAuth, "ClientAuth": x509.ExtKeyUsageClientAuth, "CodeSigning": x509.ExtKeyUsageCodeSigning, "EmailProtection": x509.ExtKeyUsageEmailProtection, "IPSECEndSystem": x509.ExtKeyUsageIPSECEndSystem, "IPSECTunnel": x509.ExtKeyUsageIPSECTunnel, "IPSECUser": x509.ExtKeyUsageIPSECUser, "TimeStamping": x509.ExtKeyUsageTimeStamping, "OCSPSigning": x509.ExtKeyUsageOCSPSigning, "MicrosoftServerGatedCrypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto, "NetscapeServerGatedCrypto": x509.ExtKeyUsageNetscapeServerGatedCrypto, } google-certificate-transparency-go-2308f62/trillian/ctfe/config_test.go000066400000000000000000000442441462611535200262430ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "os" "strings" "testing" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/trillian/crypto/keyspb" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/timestamppb" ) var ( invalidTimestamp = ×tamppb.Timestamp{Nanos: int32(1e9)} // validSTH is an STH signed by "../testdata/ct-http-server.privkey.pem". validSTH = &configpb.SignedTreeHead{ TreeSize: 766, Timestamp: 1538659276115, Sha256RootHash: mustDecodeBase64("rMWSvrYQ+n6kAmU6sMJuVV5LjoKBGK2OL719X5a+T9Y="), TreeHeadSignature: mustDecodeBase64("BAMASDBGAiEApo+OIdPXIVEwdnS1v5Iu1gQHaiWmCY73h28zfKMmHrYCIQD1U6qj1mKBXYIQVP52YCdaxdHJH4jDsL1JlaA+J/MqCw=="), } ) func mustMarshalAny(pb proto.Message) *anypb.Any { ret, err := anypb.New(pb) if err != nil { panic(fmt.Sprintf("MarshalAny failed: %v", err)) } return ret } func mustReadPublicKey(path string) *keyspb.PublicKey { keyPEM, err := os.ReadFile(path) if err != nil { panic(fmt.Sprintf("os.ReadFile(%q): %v", path, err)) } block, _ := pem.Decode(keyPEM) pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { panic(fmt.Sprintf("ReadPublicKeyFile(): %v", err)) } keyDER, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { panic(fmt.Sprintf("x509.MarshalPKIXPublicKey(): %v", err)) } return &keyspb.PublicKey{Der: keyDER} } func mustDecodeBase64(str string) []byte { data, err := base64.StdEncoding.DecodeString(str) if err != nil { panic(fmt.Sprintf("base64: DecodeString failed: %v", err)) } return data } func TestValidateLogConfig(t *testing.T) { pubKey := mustReadPublicKey("../testdata/ct-http-server.pubkey.pem") privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"}) corruptedSTH := proto.Clone(validSTH).(*configpb.SignedTreeHead) corruptedSTH.TreeSize = 1234 for _, tc := range []struct { desc string cfg *configpb.LogConfig wantErr string }{ { desc: "empty-log-ID", wantErr: "empty log ID", cfg: &configpb.LogConfig{}, }, { desc: "empty-public-key-mirror", wantErr: "empty public key for mirror", cfg: &configpb.LogConfig{LogId: 123, IsMirror: true}, }, { desc: "empty-public-key-frozen", wantErr: "empty public key for frozen STH", cfg: &configpb.LogConfig{LogId: 123, FrozenSth: &configpb.SignedTreeHead{}}, }, { desc: "invalid-public-key-empty", wantErr: "x509.ParsePKIXPublicKey", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: &keyspb.PublicKey{}, IsMirror: true, }, }, { desc: "invalid-public-key-abacaba", wantErr: "x509.ParsePKIXPublicKey", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: &keyspb.PublicKey{Der: []byte("abacaba")}, IsMirror: true, }, }, { desc: "empty-private-key", wantErr: "empty private key", cfg: &configpb.LogConfig{LogId: 123}, }, { desc: "invalid-private-key", wantErr: "invalid private key", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: &anypb.Any{}, }, }, { desc: "unnecessary-private-key", wantErr: "unnecessary private key", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: pubKey, PrivateKey: privKey, IsMirror: true, }, }, { desc: "rejecting-all", wantErr: "rejecting all certificates", cfg: &configpb.LogConfig{ LogId: 123, RejectExpired: true, RejectUnexpired: true, PrivateKey: privKey, }, }, { desc: "unknown-ext-key-usage-1", wantErr: "unknown extended key usage", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtKeyUsages: []string{"wrong_usage"}, }, }, { desc: "unknown-ext-key-usage-2", wantErr: "unknown extended key usage", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtKeyUsages: []string{"ClientAuth", "ServerAuth", "TimeStomping"}, }, }, { desc: "unknown-ext-key-usage-3", wantErr: "unknown extended key usage", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtKeyUsages: []string{"Any "}, }, }, { desc: "invalid-start-timestamp", wantErr: "invalid start timestamp", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterStart: invalidTimestamp, }, }, { desc: "invalid-limit-timestamp", wantErr: "invalid limit timestamp", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterLimit: invalidTimestamp, }, }, { desc: "limit-before-start", wantErr: "limit before start", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterStart: ×tamppb.Timestamp{Seconds: 200}, NotAfterLimit: ×tamppb.Timestamp{Seconds: 100}, }, }, { desc: "negative-maximum-merge", wantErr: "negative maximum merge", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, MaxMergeDelaySec: -100, }, }, { desc: "negative-expected-merge", wantErr: "negative expected merge", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExpectedMergeDelaySec: -100, }, }, { desc: "expected-exceeds-max", wantErr: "expected merge delay exceeds MMD", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, MaxMergeDelaySec: 50, ExpectedMergeDelaySec: 100, }, }, { desc: "invalid-frozen-STH", wantErr: "invalid frozen STH", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: pubKey, IsMirror: true, FrozenSth: &configpb.SignedTreeHead{ TreeSize: 10, Timestamp: 100500, }, }, }, { desc: "signature-verification-failed", wantErr: "signature verification failed", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: pubKey, PrivateKey: privKey, FrozenSth: corruptedSTH, }, }, { desc: "invalid-ctfe-storage-connection-string-mysql", wantErr: "failed to parse ctfe_storage_connection_string for mysql driver", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, CtfeStorageConnectionString: "mysql://test:zaphod@localhost:3306/test", ExtraDataIssuanceChainStorageBackend: configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE, }, }, { desc: "unsupported-driver-in-ctfe-storage-connection-string", wantErr: "unsupported driver in ctfe_storage_connection_string", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, CtfeStorageConnectionString: "spanner://test:zaphod@tcp(localhost:3306)/test", ExtraDataIssuanceChainStorageBackend: configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE, }, }, { desc: "missing-ctfe-storage-connection-string-when-issuance-chain-storage-backend-ctfe", wantErr: "missing ctfe_storage_connection_string when issuance chain storage backend is CTFE", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtraDataIssuanceChainStorageBackend: configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE, }, }, { desc: "ok", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, }, }, { // Note: Substituting an arbitrary proto.Message as a PrivateKey will not // fail the validation because the actual key loading happens at runtime. // TODO(pavelkalinnikov): Decouple key protos validation and loading, and // make this test fail. desc: "ok-not-a-key", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: mustMarshalAny(&configpb.LogConfig{}), }, }, { desc: "ok-mirror", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: pubKey, IsMirror: true, }, }, { desc: "ok-ext-key-usages", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtKeyUsages: []string{"ServerAuth", "ClientAuth", "OCSPSigning"}, }, }, { desc: "ok-start-timestamp", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterStart: ×tamppb.Timestamp{Seconds: 100}, }, }, { desc: "ok-limit-timestamp", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterLimit: ×tamppb.Timestamp{Seconds: 200}, }, }, { desc: "ok-range-timestamp", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, NotAfterStart: ×tamppb.Timestamp{Seconds: 300}, NotAfterLimit: ×tamppb.Timestamp{Seconds: 400}, }, }, { desc: "ok-merge-delay", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, MaxMergeDelaySec: 86400, ExpectedMergeDelaySec: 7200, }, }, { desc: "ok-frozen-STH", cfg: &configpb.LogConfig{ LogId: 123, PublicKey: pubKey, PrivateKey: privKey, FrozenSth: validSTH, }, }, { desc: "ok-ctfe-storage-connection-string-mysql", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, CtfeStorageConnectionString: "mysql://test:zaphod@tcp(localhost:3306)/test", }, }, { desc: "ok-extra-data-issuance-chain-storage-backend-trillian", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtraDataIssuanceChainStorageBackend: configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC, }, }, { desc: "ok-extra-data-issuance-chain-storage-backend-ctfe", cfg: &configpb.LogConfig{ LogId: 123, PrivateKey: privKey, ExtraDataIssuanceChainStorageBackend: configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE, CtfeStorageConnectionString: "mysql://test:zaphod@tcp(localhost:3306)/test", }, }, } { t.Run(tc.desc, func(t *testing.T) { vc, err := ValidateLogConfig(tc.cfg) if len(tc.wantErr) == 0 && err != nil { t.Errorf("ValidateLogConfig()=%v, want nil", err) } if len(tc.wantErr) > 0 && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { t.Errorf("ValidateLogConfig()=%v, want err containing %q", err, tc.wantErr) } if err == nil && vc == nil { t.Error("err and ValidatedLogConfig are both nil") } // TODO(pavelkalinnikov): Test that ValidatedLogConfig is correct. }) } } func TestValidateLogMultiConfig(t *testing.T) { privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"}) for _, tc := range []struct { desc string cfg *configpb.LogMultiConfig wantErr string }{ { desc: "empty-backend-name", wantErr: "empty backend name", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {BackendSpec: "testspec"}, }, }, }, }, { desc: "empty-backend-spec", wantErr: "empty backend spec", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1"}, }, }, }, }, { desc: "duplicate-backend-name", wantErr: "duplicate backend name", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "dup", BackendSpec: "testspec"}, {Name: "dup", BackendSpec: "testspec"}, }, }, }, }, { desc: "duplicate-backend-spec", wantErr: "duplicate backend spec", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec"}, {Name: "log2", BackendSpec: "testspec"}, }, }, }, }, { desc: "invalid-log-config", wantErr: "log config: empty log ID", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {Prefix: "pref"}, }, }, }, }, { desc: "empty-prefix", wantErr: "empty prefix", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, PrivateKey: privKey, LogBackendName: "log1"}, }, }, }, }, { desc: "duplicate-prefix", wantErr: "duplicate prefix", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec1"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 3, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"}, }, }, }, }, { desc: "references-undefined-backend", wantErr: "references undefined backend", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"}, }, }, }, }, { desc: "dup-tree-id-on-same-backend", wantErr: "dup tree id", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec1"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 1, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log1"}, }, }, }, }, { desc: "ok-all-distinct", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec1"}, {Name: "log2", BackendSpec: "testspec2"}, {Name: "log3", BackendSpec: "testspec3"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"}, {LogId: 3, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log3"}, }, }, }, }, { desc: "ok-dup-tree-ids-on-different-backends", cfg: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{ {Name: "log1", BackendSpec: "testspec1"}, {Name: "log2", BackendSpec: "testspec2"}, {Name: "log3", BackendSpec: "testspec3"}, }, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"}, {LogId: 1, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"}, {LogId: 1, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log3"}, }, }, }, }, } { t.Run(tc.desc, func(t *testing.T) { _, err := ValidateLogMultiConfig(tc.cfg) if len(tc.wantErr) == 0 && err != nil { t.Fatalf("ValidateLogMultiConfig()=%v, want nil", err) } if len(tc.wantErr) > 0 && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { t.Errorf("ValidateLogMultiConfig()=%v, want err containing %q", err, tc.wantErr) } }) } } func TestToMultiLogConfig(t *testing.T) { // TODO(pavelkalinnikov): Log configs in this test are not valid (they don't // have keys etc). In addition, we should have tests to ensure that valid log // configs result in valid MultiLogConfig. for _, tc := range []struct { desc string cfg []*configpb.LogConfig want *configpb.LogMultiConfig }{ { desc: "ok-one-config", cfg: []*configpb.LogConfig{ {LogId: 1, Prefix: "test"}, }, want: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{{Name: "default", BackendSpec: "spec"}}, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "test", LogBackendName: "default"}, }, }, }, }, { desc: "ok-three-configs", cfg: []*configpb.LogConfig{ {LogId: 1, Prefix: "test1"}, {LogId: 2, Prefix: "test2"}, {LogId: 3, Prefix: "test3"}, }, want: &configpb.LogMultiConfig{ Backends: &configpb.LogBackendSet{ Backend: []*configpb.LogBackend{{Name: "default", BackendSpec: "spec"}}, }, LogConfigs: &configpb.LogConfigSet{ Config: []*configpb.LogConfig{ {LogId: 1, Prefix: "test1", LogBackendName: "default"}, {LogId: 2, Prefix: "test2", LogBackendName: "default"}, {LogId: 3, Prefix: "test3", LogBackendName: "default"}, }, }, }, }, } { t.Run(tc.desc, func(t *testing.T) { got := ToMultiLogConfig(tc.cfg, "spec") if !proto.Equal(got, tc.want) { t.Errorf("TestToMultiLogConfig()=%v, want %v", got, tc.want) } }) } } google-certificate-transparency-go-2308f62/trillian/ctfe/configpb/000077500000000000000000000000001462611535200251675ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/configpb/config.pb.go000066400000000000000000001156751462611535200274020ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.1 // protoc v3.20.1 // source: trillian/ctfe/configpb/config.proto package configpb import ( keyspb "github.com/google/trillian/crypto/keyspb" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" anypb "google.golang.org/protobuf/types/known/anypb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // An optional storage backend for the issuance chain in ExtraData. // By default, the storage backend is Trillian GRPC. To use CTFE as the // storage backend, the CTFE storage connection string needs to be specified. // Do not change this value during the log's lifetime. type LogConfig_IssuanceChainStorageBackend int32 const ( LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC LogConfig_IssuanceChainStorageBackend = 0 LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE LogConfig_IssuanceChainStorageBackend = 1 ) // Enum value maps for LogConfig_IssuanceChainStorageBackend. var ( LogConfig_IssuanceChainStorageBackend_name = map[int32]string{ 0: "ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC", 1: "ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE", } LogConfig_IssuanceChainStorageBackend_value = map[string]int32{ "ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC": 0, "ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE": 1, } ) func (x LogConfig_IssuanceChainStorageBackend) Enum() *LogConfig_IssuanceChainStorageBackend { p := new(LogConfig_IssuanceChainStorageBackend) *p = x return p } func (x LogConfig_IssuanceChainStorageBackend) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (LogConfig_IssuanceChainStorageBackend) Descriptor() protoreflect.EnumDescriptor { return file_trillian_ctfe_configpb_config_proto_enumTypes[0].Descriptor() } func (LogConfig_IssuanceChainStorageBackend) Type() protoreflect.EnumType { return &file_trillian_ctfe_configpb_config_proto_enumTypes[0] } func (x LogConfig_IssuanceChainStorageBackend) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use LogConfig_IssuanceChainStorageBackend.Descriptor instead. func (LogConfig_IssuanceChainStorageBackend) EnumDescriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{3, 0} } type LogBackend struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // name defines the name of the log backend for use in LogConfig messages and must be unique. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // backend_spec defines the RPC endpoint that clients should use to send requests // to this log backend. These should be in the same format as rpcBackendFlag in the // CTFE main and must not be an empty string. BackendSpec string `protobuf:"bytes,2,opt,name=backend_spec,json=backendSpec,proto3" json:"backend_spec,omitempty"` } func (x *LogBackend) Reset() { *x = LogBackend{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogBackend) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogBackend) ProtoMessage() {} func (x *LogBackend) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogBackend.ProtoReflect.Descriptor instead. func (*LogBackend) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{0} } func (x *LogBackend) GetName() string { if x != nil { return x.Name } return "" } func (x *LogBackend) GetBackendSpec() string { if x != nil { return x.BackendSpec } return "" } // LogBackendSet supports a configuration where a single set of frontends handle // requests for multiple backends. For example this could be used to run different // backends in different geographic regions. type LogBackendSet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Backend []*LogBackend `protobuf:"bytes,1,rep,name=backend,proto3" json:"backend,omitempty"` } func (x *LogBackendSet) Reset() { *x = LogBackendSet{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogBackendSet) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogBackendSet) ProtoMessage() {} func (x *LogBackendSet) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogBackendSet.ProtoReflect.Descriptor instead. func (*LogBackendSet) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{1} } func (x *LogBackendSet) GetBackend() []*LogBackend { if x != nil { return x.Backend } return nil } // LogConfigSet is a set of LogConfig messages. type LogConfigSet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Config []*LogConfig `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty"` } func (x *LogConfigSet) Reset() { *x = LogConfigSet{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogConfigSet) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogConfigSet) ProtoMessage() {} func (x *LogConfigSet) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogConfigSet.ProtoReflect.Descriptor instead. func (*LogConfigSet) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{2} } func (x *LogConfigSet) GetConfig() []*LogConfig { if x != nil { return x.Config } return nil } // LogConfig describes the configuration options for a log instance. // // NEXT_ID: 22 type LogConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The ID of a Trillian tree that stores the log data. The tree type must be // LOG for regular CT logs. For mirror logs it must be either PREORDERED_LOG // or LOG, and can change at runtime. CTFE in mirror mode uses only read API // which is common for both types. LogId int64 `protobuf:"varint,1,opt,name=log_id,json=logId,proto3" json:"log_id,omitempty"` // prefix is the name of the log. It will come after the global or // override handler prefix. For example if the handler prefix is "/logs" // and prefix is "vogon" the get-sth handler for this log will be // available at "/logs/vogon/ct/v1/get-sth". The prefix cannot be empty // and must not include "/" path separator characters. Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty"` // override_handler_prefix if set to a non empty value overrides the global // handler prefix for an individual log. For example this field is set to // "/otherlogs" then a log with prefix "vogon" will make it's get-sth handler // available at "/otherlogs/vogon/ct/v1/get-sth" regardless of what the // global prefix is. Can be set to '/' to make the get-sth handler register // at "/vogon/ct/v1/get-sth". OverrideHandlerPrefix string `protobuf:"bytes,13,opt,name=override_handler_prefix,json=overrideHandlerPrefix,proto3" json:"override_handler_prefix,omitempty"` // Paths to the files containing root certificates that are acceptable to the // log. The certs are served through get-roots endpoint. Optional in mirrors. RootsPemFile []string `protobuf:"bytes,3,rep,name=roots_pem_file,json=rootsPemFile,proto3" json:"roots_pem_file,omitempty"` // The private key used for signing STHs etc. Not required for mirrors. PrivateKey *anypb.Any `protobuf:"bytes,4,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` // The public key matching the above private key (if both are present). It is // used only by mirror logs for verifying the source log's signatures, but can // be specified for regular logs as well for the convenience of test tools. PublicKey *keyspb.PublicKey `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // If reject_expired is true then the certificate validity period will be // checked against the current time during the validation of submissions. // This will cause expired certificates to be rejected. RejectExpired bool `protobuf:"varint,6,opt,name=reject_expired,json=rejectExpired,proto3" json:"reject_expired,omitempty"` // If reject_unexpired is true then CTFE rejects certificates that are either // currently valid or not yet valid. RejectUnexpired bool `protobuf:"varint,17,opt,name=reject_unexpired,json=rejectUnexpired,proto3" json:"reject_unexpired,omitempty"` // If set, ext_key_usages will restrict the set of such usages that the // server will accept. By default all are accepted. The values specified // must be ones known to the x509 package. ExtKeyUsages []string `protobuf:"bytes,7,rep,name=ext_key_usages,json=extKeyUsages,proto3" json:"ext_key_usages,omitempty"` // not_after_start defines the start of the range of acceptable NotAfter // values, inclusive. // Leaving this unset implies no lower bound to the range. NotAfterStart *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=not_after_start,json=notAfterStart,proto3" json:"not_after_start,omitempty"` // not_after_limit defines the end of the range of acceptable NotAfter values, // exclusive. // Leaving this unset implies no upper bound to the range. NotAfterLimit *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=not_after_limit,json=notAfterLimit,proto3" json:"not_after_limit,omitempty"` // accept_only_ca controls whether or not *only* certificates with the CA bit // set will be accepted. AcceptOnlyCa bool `protobuf:"varint,10,opt,name=accept_only_ca,json=acceptOnlyCa,proto3" json:"accept_only_ca,omitempty"` // backend_name if set indicates which backend serves this log. The name must be // one of those defined in the LogBackendSet. LogBackendName string `protobuf:"bytes,11,opt,name=log_backend_name,json=logBackendName,proto3" json:"log_backend_name,omitempty"` // If set, the log is a mirror, i.e. it serves the data of another (source) // log. It doesn't handle write requests (add-chain, etc.), so it's not a // fully fledged RFC-6962 log, but the tree read requests like get-entries and // get-consistency-proof are compatible. A mirror doesn't have the source // log's key and can't sign STHs. Consequently, the log operator must ensure // to channel source log's STHs into CTFE. IsMirror bool `protobuf:"varint,12,opt,name=is_mirror,json=isMirror,proto3" json:"is_mirror,omitempty"` // If set, the log serves only read endpoints, and rejects writes through the // add-[pre-]chain endpoint. IsReadonly bool `protobuf:"varint,19,opt,name=is_readonly,json=isReadonly,proto3" json:"is_readonly,omitempty"` // The Maximum Merge Delay (MMD) of this log in seconds. See RFC6962 section 3 // for definition of MMD. If zero, the log does not provide an MMD guarantee // (for example, it is a frozen log). MaxMergeDelaySec int32 `protobuf:"varint,14,opt,name=max_merge_delay_sec,json=maxMergeDelaySec,proto3" json:"max_merge_delay_sec,omitempty"` // The merge delay that the underlying log implementation is able/targeting to // provide. This option is exposed in CTFE metrics, and can be particularly // useful to catch when the log is behind but has not yet violated the strict // MMD limit. // Log operator should decide what exactly EMD means for them. For example, it // can be a 99-th percentile of merge delays that they observe, and they can // alert on the actual merge delay going above a certain multiple of this EMD. ExpectedMergeDelaySec int32 `protobuf:"varint,15,opt,name=expected_merge_delay_sec,json=expectedMergeDelaySec,proto3" json:"expected_merge_delay_sec,omitempty"` // The STH that this log will serve permanently (if present). Frozen STH must // be signed by this log's private key, and will be verified using the public // key specified in this config. FrozenSth *SignedTreeHead `protobuf:"bytes,16,opt,name=frozen_sth,json=frozenSth,proto3" json:"frozen_sth,omitempty"` // A list of X.509 extension OIDs, in dotted string form (e.g. "2.3.4.5") // which should cause submissions to be rejected. RejectExtensions []string `protobuf:"bytes,18,rep,name=reject_extensions,json=rejectExtensions,proto3" json:"reject_extensions,omitempty"` // CTFE storage connection string in the following format in general: // driver://[username[:password]@][protocol[(host[:port])]][/[schema|database][?options]] // // MySQL/MariaDB: // mysql://[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] // // This is required when the issuance chain storage backend is CTFE. // // Warning: CT log operators are advised not to re-use the same connection // string across multiple LogConfigs due to the log lifecycle. CtfeStorageConnectionString string `protobuf:"bytes,20,opt,name=ctfe_storage_connection_string,json=ctfeStorageConnectionString,proto3" json:"ctfe_storage_connection_string,omitempty"` ExtraDataIssuanceChainStorageBackend LogConfig_IssuanceChainStorageBackend `protobuf:"varint,21,opt,name=extra_data_issuance_chain_storage_backend,json=extraDataIssuanceChainStorageBackend,proto3,enum=configpb.LogConfig_IssuanceChainStorageBackend" json:"extra_data_issuance_chain_storage_backend,omitempty"` } func (x *LogConfig) Reset() { *x = LogConfig{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogConfig) ProtoMessage() {} func (x *LogConfig) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogConfig.ProtoReflect.Descriptor instead. func (*LogConfig) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{3} } func (x *LogConfig) GetLogId() int64 { if x != nil { return x.LogId } return 0 } func (x *LogConfig) GetPrefix() string { if x != nil { return x.Prefix } return "" } func (x *LogConfig) GetOverrideHandlerPrefix() string { if x != nil { return x.OverrideHandlerPrefix } return "" } func (x *LogConfig) GetRootsPemFile() []string { if x != nil { return x.RootsPemFile } return nil } func (x *LogConfig) GetPrivateKey() *anypb.Any { if x != nil { return x.PrivateKey } return nil } func (x *LogConfig) GetPublicKey() *keyspb.PublicKey { if x != nil { return x.PublicKey } return nil } func (x *LogConfig) GetRejectExpired() bool { if x != nil { return x.RejectExpired } return false } func (x *LogConfig) GetRejectUnexpired() bool { if x != nil { return x.RejectUnexpired } return false } func (x *LogConfig) GetExtKeyUsages() []string { if x != nil { return x.ExtKeyUsages } return nil } func (x *LogConfig) GetNotAfterStart() *timestamppb.Timestamp { if x != nil { return x.NotAfterStart } return nil } func (x *LogConfig) GetNotAfterLimit() *timestamppb.Timestamp { if x != nil { return x.NotAfterLimit } return nil } func (x *LogConfig) GetAcceptOnlyCa() bool { if x != nil { return x.AcceptOnlyCa } return false } func (x *LogConfig) GetLogBackendName() string { if x != nil { return x.LogBackendName } return "" } func (x *LogConfig) GetIsMirror() bool { if x != nil { return x.IsMirror } return false } func (x *LogConfig) GetIsReadonly() bool { if x != nil { return x.IsReadonly } return false } func (x *LogConfig) GetMaxMergeDelaySec() int32 { if x != nil { return x.MaxMergeDelaySec } return 0 } func (x *LogConfig) GetExpectedMergeDelaySec() int32 { if x != nil { return x.ExpectedMergeDelaySec } return 0 } func (x *LogConfig) GetFrozenSth() *SignedTreeHead { if x != nil { return x.FrozenSth } return nil } func (x *LogConfig) GetRejectExtensions() []string { if x != nil { return x.RejectExtensions } return nil } func (x *LogConfig) GetCtfeStorageConnectionString() string { if x != nil { return x.CtfeStorageConnectionString } return "" } func (x *LogConfig) GetExtraDataIssuanceChainStorageBackend() LogConfig_IssuanceChainStorageBackend { if x != nil { return x.ExtraDataIssuanceChainStorageBackend } return LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC } // LogMultiConfig wraps up a LogBackendSet and corresponding LogConfigSet so // that they can easily be parsed as a single proto. type LogMultiConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The set of backends that this configuration will use to send requests to. // The names of the backends in the LogBackendSet must all be distinct. Backends *LogBackendSet `protobuf:"bytes,1,opt,name=backends,proto3" json:"backends,omitempty"` // The set of logs that will use the above backends. All the protos in this // LogConfigSet must set a valid log_backend_name for the config to be usable. LogConfigs *LogConfigSet `protobuf:"bytes,2,opt,name=log_configs,json=logConfigs,proto3" json:"log_configs,omitempty"` } func (x *LogMultiConfig) Reset() { *x = LogMultiConfig{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LogMultiConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogMultiConfig) ProtoMessage() {} func (x *LogMultiConfig) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogMultiConfig.ProtoReflect.Descriptor instead. func (*LogMultiConfig) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{4} } func (x *LogMultiConfig) GetBackends() *LogBackendSet { if x != nil { return x.Backends } return nil } func (x *LogMultiConfig) GetLogConfigs() *LogConfigSet { if x != nil { return x.LogConfigs } return nil } // SignedTreeHead represents the structure returned by the get-sth CT method. // See RFC6962 sections 3.5 and 4.3 for reference. // TODO(pavelkalinnikov): Find a better place for this type. type SignedTreeHead struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TreeSize int64 `protobuf:"varint,1,opt,name=tree_size,json=treeSize,proto3" json:"tree_size,omitempty"` Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Sha256RootHash []byte `protobuf:"bytes,3,opt,name=sha256_root_hash,json=sha256RootHash,proto3" json:"sha256_root_hash,omitempty"` TreeHeadSignature []byte `protobuf:"bytes,4,opt,name=tree_head_signature,json=treeHeadSignature,proto3" json:"tree_head_signature,omitempty"` } func (x *SignedTreeHead) Reset() { *x = SignedTreeHead{} if protoimpl.UnsafeEnabled { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SignedTreeHead) String() string { return protoimpl.X.MessageStringOf(x) } func (*SignedTreeHead) ProtoMessage() {} func (x *SignedTreeHead) ProtoReflect() protoreflect.Message { mi := &file_trillian_ctfe_configpb_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SignedTreeHead.ProtoReflect.Descriptor instead. func (*SignedTreeHead) Descriptor() ([]byte, []int) { return file_trillian_ctfe_configpb_config_proto_rawDescGZIP(), []int{5} } func (x *SignedTreeHead) GetTreeSize() int64 { if x != nil { return x.TreeSize } return 0 } func (x *SignedTreeHead) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *SignedTreeHead) GetSha256RootHash() []byte { if x != nil { return x.Sha256RootHash } return nil } func (x *SignedTreeHead) GetTreeHeadSignature() []byte { if x != nil { return x.TreeHeadSignature } return nil } var File_trillian_ctfe_configpb_config_proto protoreflect.FileDescriptor var file_trillian_ctfe_configpb_config_proto_rawDesc = []byte{ 0x0a, 0x23, 0x74, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x63, 0x74, 0x66, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x1a, 0x1a, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x43, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x70, 0x65, 0x63, 0x22, 0x3f, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0x3b, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xa7, 0x09, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x36, 0x0a, 0x17, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x6d, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x50, 0x65, 0x6d, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x6e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x63, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x43, 0x61, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x63, 0x12, 0x37, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x63, 0x12, 0x37, 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x73, 0x74, 0x68, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x65, 0x65, 0x48, 0x65, 0x61, 0x64, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x53, 0x74, 0x68, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x63, 0x74, 0x66, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x63, 0x74, 0x66, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x88, 0x01, 0x0a, 0x29, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x24, 0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74, 0x61, 0x49, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0x78, 0x0a, 0x1b, 0x49, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x2c, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x5f, 0x54, 0x52, 0x49, 0x4c, 0x4c, 0x49, 0x41, 0x4e, 0x5f, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, 0x27, 0x0a, 0x23, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x5f, 0x43, 0x54, 0x46, 0x45, 0x10, 0x01, 0x22, 0x7e, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x74, 0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0xa5, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x65, 0x65, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x52, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x74, 0x72, 0x65, 0x65, 0x48, 0x65, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x63, 0x74, 0x66, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_trillian_ctfe_configpb_config_proto_rawDescOnce sync.Once file_trillian_ctfe_configpb_config_proto_rawDescData = file_trillian_ctfe_configpb_config_proto_rawDesc ) func file_trillian_ctfe_configpb_config_proto_rawDescGZIP() []byte { file_trillian_ctfe_configpb_config_proto_rawDescOnce.Do(func() { file_trillian_ctfe_configpb_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_trillian_ctfe_configpb_config_proto_rawDescData) }) return file_trillian_ctfe_configpb_config_proto_rawDescData } var file_trillian_ctfe_configpb_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_trillian_ctfe_configpb_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_trillian_ctfe_configpb_config_proto_goTypes = []interface{}{ (LogConfig_IssuanceChainStorageBackend)(0), // 0: configpb.LogConfig.IssuanceChainStorageBackend (*LogBackend)(nil), // 1: configpb.LogBackend (*LogBackendSet)(nil), // 2: configpb.LogBackendSet (*LogConfigSet)(nil), // 3: configpb.LogConfigSet (*LogConfig)(nil), // 4: configpb.LogConfig (*LogMultiConfig)(nil), // 5: configpb.LogMultiConfig (*SignedTreeHead)(nil), // 6: configpb.SignedTreeHead (*anypb.Any)(nil), // 7: google.protobuf.Any (*keyspb.PublicKey)(nil), // 8: keyspb.PublicKey (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } var file_trillian_ctfe_configpb_config_proto_depIdxs = []int32{ 1, // 0: configpb.LogBackendSet.backend:type_name -> configpb.LogBackend 4, // 1: configpb.LogConfigSet.config:type_name -> configpb.LogConfig 7, // 2: configpb.LogConfig.private_key:type_name -> google.protobuf.Any 8, // 3: configpb.LogConfig.public_key:type_name -> keyspb.PublicKey 9, // 4: configpb.LogConfig.not_after_start:type_name -> google.protobuf.Timestamp 9, // 5: configpb.LogConfig.not_after_limit:type_name -> google.protobuf.Timestamp 6, // 6: configpb.LogConfig.frozen_sth:type_name -> configpb.SignedTreeHead 0, // 7: configpb.LogConfig.extra_data_issuance_chain_storage_backend:type_name -> configpb.LogConfig.IssuanceChainStorageBackend 2, // 8: configpb.LogMultiConfig.backends:type_name -> configpb.LogBackendSet 3, // 9: configpb.LogMultiConfig.log_configs:type_name -> configpb.LogConfigSet 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_trillian_ctfe_configpb_config_proto_init() } func file_trillian_ctfe_configpb_config_proto_init() { if File_trillian_ctfe_configpb_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_trillian_ctfe_configpb_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogBackend); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_ctfe_configpb_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogBackendSet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_ctfe_configpb_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogConfigSet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_ctfe_configpb_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_ctfe_configpb_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogMultiConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_ctfe_configpb_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SignedTreeHead); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_trillian_ctfe_configpb_config_proto_rawDesc, NumEnums: 1, NumMessages: 6, NumExtensions: 0, NumServices: 0, }, GoTypes: file_trillian_ctfe_configpb_config_proto_goTypes, DependencyIndexes: file_trillian_ctfe_configpb_config_proto_depIdxs, EnumInfos: file_trillian_ctfe_configpb_config_proto_enumTypes, MessageInfos: file_trillian_ctfe_configpb_config_proto_msgTypes, }.Build() File_trillian_ctfe_configpb_config_proto = out.File file_trillian_ctfe_configpb_config_proto_rawDesc = nil file_trillian_ctfe_configpb_config_proto_goTypes = nil file_trillian_ctfe_configpb_config_proto_depIdxs = nil } google-certificate-transparency-go-2308f62/trillian/ctfe/configpb/config.proto000066400000000000000000000203771462611535200275320ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; option go_package = "github.com/google/certificate-transparency-go/trillian/ctfe/configpb"; package configpb; import "crypto/keyspb/keyspb.proto"; import "google/protobuf/any.proto"; import "google/protobuf/timestamp.proto"; message LogBackend { // name defines the name of the log backend for use in LogConfig messages and must be unique. string name = 1; // backend_spec defines the RPC endpoint that clients should use to send requests // to this log backend. These should be in the same format as rpcBackendFlag in the // CTFE main and must not be an empty string. string backend_spec = 2; } // LogBackendSet supports a configuration where a single set of frontends handle // requests for multiple backends. For example this could be used to run different // backends in different geographic regions. message LogBackendSet { repeated LogBackend backend = 1; } // LogConfigSet is a set of LogConfig messages. message LogConfigSet { repeated LogConfig config = 1; } // LogConfig describes the configuration options for a log instance. // // NEXT_ID: 22 message LogConfig { // The ID of a Trillian tree that stores the log data. The tree type must be // LOG for regular CT logs. For mirror logs it must be either PREORDERED_LOG // or LOG, and can change at runtime. CTFE in mirror mode uses only read API // which is common for both types. int64 log_id = 1; // prefix is the name of the log. It will come after the global or // override handler prefix. For example if the handler prefix is "/logs" // and prefix is "vogon" the get-sth handler for this log will be // available at "/logs/vogon/ct/v1/get-sth". The prefix cannot be empty // and must not include "/" path separator characters. string prefix = 2; // override_handler_prefix if set to a non empty value overrides the global // handler prefix for an individual log. For example this field is set to // "/otherlogs" then a log with prefix "vogon" will make it's get-sth handler // available at "/otherlogs/vogon/ct/v1/get-sth" regardless of what the // global prefix is. Can be set to '/' to make the get-sth handler register // at "/vogon/ct/v1/get-sth". string override_handler_prefix = 13; // Paths to the files containing root certificates that are acceptable to the // log. The certs are served through get-roots endpoint. Optional in mirrors. repeated string roots_pem_file = 3; // The private key used for signing STHs etc. Not required for mirrors. google.protobuf.Any private_key = 4; // The public key matching the above private key (if both are present). It is // used only by mirror logs for verifying the source log's signatures, but can // be specified for regular logs as well for the convenience of test tools. keyspb.PublicKey public_key = 5; // If reject_expired is true then the certificate validity period will be // checked against the current time during the validation of submissions. // This will cause expired certificates to be rejected. bool reject_expired = 6; // If reject_unexpired is true then CTFE rejects certificates that are either // currently valid or not yet valid. bool reject_unexpired = 17; // If set, ext_key_usages will restrict the set of such usages that the // server will accept. By default all are accepted. The values specified // must be ones known to the x509 package. repeated string ext_key_usages = 7; // not_after_start defines the start of the range of acceptable NotAfter // values, inclusive. // Leaving this unset implies no lower bound to the range. google.protobuf.Timestamp not_after_start = 8; // not_after_limit defines the end of the range of acceptable NotAfter values, // exclusive. // Leaving this unset implies no upper bound to the range. google.protobuf.Timestamp not_after_limit = 9; // accept_only_ca controls whether or not *only* certificates with the CA bit // set will be accepted. bool accept_only_ca = 10; // backend_name if set indicates which backend serves this log. The name must be // one of those defined in the LogBackendSet. string log_backend_name = 11; // If set, the log is a mirror, i.e. it serves the data of another (source) // log. It doesn't handle write requests (add-chain, etc.), so it's not a // fully fledged RFC-6962 log, but the tree read requests like get-entries and // get-consistency-proof are compatible. A mirror doesn't have the source // log's key and can't sign STHs. Consequently, the log operator must ensure // to channel source log's STHs into CTFE. bool is_mirror = 12; // If set, the log serves only read endpoints, and rejects writes through the // add-[pre-]chain endpoint. bool is_readonly = 19; // The Maximum Merge Delay (MMD) of this log in seconds. See RFC6962 section 3 // for definition of MMD. If zero, the log does not provide an MMD guarantee // (for example, it is a frozen log). int32 max_merge_delay_sec = 14; // The merge delay that the underlying log implementation is able/targeting to // provide. This option is exposed in CTFE metrics, and can be particularly // useful to catch when the log is behind but has not yet violated the strict // MMD limit. // Log operator should decide what exactly EMD means for them. For example, it // can be a 99-th percentile of merge delays that they observe, and they can // alert on the actual merge delay going above a certain multiple of this EMD. int32 expected_merge_delay_sec = 15; // The STH that this log will serve permanently (if present). Frozen STH must // be signed by this log's private key, and will be verified using the public // key specified in this config. SignedTreeHead frozen_sth = 16; // A list of X.509 extension OIDs, in dotted string form (e.g. "2.3.4.5") // which should cause submissions to be rejected. repeated string reject_extensions = 18; // CTFE storage connection string in the following format in general: // driver://[username[:password]@][protocol[(host[:port])]][/[schema|database][?options]] // // MySQL/MariaDB: // mysql://[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] // // This is required when the issuance chain storage backend is CTFE. // // Warning: CT log operators are advised not to re-use the same connection // string across multiple LogConfigs due to the log lifecycle. string ctfe_storage_connection_string = 20; // An optional storage backend for the issuance chain in ExtraData. // By default, the storage backend is Trillian GRPC. To use CTFE as the // storage backend, the CTFE storage connection string needs to be specified. // Do not change this value during the log's lifetime. enum IssuanceChainStorageBackend { ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC = 0; ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE = 1; } IssuanceChainStorageBackend extra_data_issuance_chain_storage_backend = 21; } // LogMultiConfig wraps up a LogBackendSet and corresponding LogConfigSet so // that they can easily be parsed as a single proto. message LogMultiConfig { // The set of backends that this configuration will use to send requests to. // The names of the backends in the LogBackendSet must all be distinct. LogBackendSet backends = 1; // The set of logs that will use the above backends. All the protos in this // LogConfigSet must set a valid log_backend_name for the config to be usable. LogConfigSet log_configs = 2; } // SignedTreeHead represents the structure returned by the get-sth CT method. // See RFC6962 sections 3.5 and 4.3 for reference. // TODO(pavelkalinnikov): Find a better place for this type. message SignedTreeHead { int64 tree_size = 1; int64 timestamp = 2; bytes sha256_root_hash = 3; bytes tree_head_signature = 4; } google-certificate-transparency-go-2308f62/trillian/ctfe/ct_server/000077500000000000000000000000001462611535200253745ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/ct_server/main.go000066400000000000000000000365141462611535200266600ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // The ct_server binary runs the CT personality. package main import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "flag" "fmt" "net/http" "os" "os/signal" "strings" "sync" "syscall" "time" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/cache" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/trillian" "github.com/google/trillian/crypto/keys" "github.com/google/trillian/crypto/keys/der" "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keys/pkcs11" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/monitoring/opencensus" "github.com/google/trillian/monitoring/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/cors" "github.com/tomasen/realip" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/naming/endpoints" "google.golang.org/grpc" "google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver/manual" "google.golang.org/protobuf/proto" "k8s.io/klog/v2" ) // Global flags that affect all log instances. var ( httpEndpoint = flag.String("http_endpoint", "localhost:6962", "Endpoint for HTTP (host:port)") metricsEndpoint = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will be visible on --http_endpoint") rpcBackend = flag.String("log_rpc_server", "", "Backend specification; comma-separated list or etcd service name (if --etcd_servers specified). If unset backends are specified in config (as a LogMultiConfig proto)") rpcDeadline = flag.Duration("rpc_deadline", time.Second*10, "Deadline for backend RPC requests") getSTHInterval = flag.Duration("get_sth_interval", time.Second*180, "Interval between internal get-sth operations (0 to disable)") logConfig = flag.String("log_config", "", "File holding log config in text proto format") maxGetEntries = flag.Int64("max_get_entries", 0, "Max number of entries we allow in a get-entries request (0=>use default 1000)") etcdServers = flag.String("etcd_servers", "", "A comma-separated list of etcd servers") etcdHTTPService = flag.String("etcd_http_service", "trillian-ctfe-http", "Service name to announce our HTTP endpoint under") etcdMetricsService = flag.String("etcd_metrics_service", "trillian-ctfe-metrics-http", "Service name to announce our HTTP metrics endpoint under") maskInternalErrors = flag.Bool("mask_internal_errors", false, "Don't return error strings with Internal Server Error HTTP responses") tracing = flag.Bool("tracing", false, "If true opencensus Stackdriver tracing will be enabled. See https://opencensus.io/.") tracingProjectID = flag.String("tracing_project_id", "", "project ID to pass to stackdriver. Can be empty for GCP, consult docs for other platforms.") tracingPercent = flag.Int("tracing_percent", 0, "Percent of requests to be traced. Zero is a special case to use the DefaultSampler") quotaRemote = flag.Bool("quota_remote", true, "Enable requesting of quota for IP address sending incoming requests") quotaIntermediate = flag.Bool("quota_intermediate", true, "Enable requesting of quota for intermediate certificates in submitted chains") handlerPrefix = flag.String("handler_prefix", "", "If set e.g. to '/logs' will prefix all handlers that don't define a custom prefix") pkcs11ModulePath = flag.String("pkcs11_module_path", "", "Path to the PKCS#11 module to use for keys that use the PKCS#11 interface") cacheType = flag.String("cache_type", "noop", "Supported cache type: noop, lru (Default: noop)") cacheSize = flag.Int("cache_size", -1, "Size parameter set to 0 makes cache of unlimited size") cacheTTL = flag.Duration("cache_ttl", -1*time.Second, "Providing 0 TTL turns expiring off") ) const unknownRemoteUser = "UNKNOWN_REMOTE" // nolint:staticcheck func main() { klog.InitFlags(nil) flag.Parse() ctx := context.Background() keys.RegisterHandler(&keyspb.PEMKeyFile{}, pem.FromProto) keys.RegisterHandler(&keyspb.PrivateKey{}, der.FromProto) keys.RegisterHandler(&keyspb.PKCS11Config{}, func(ctx context.Context, pb proto.Message) (crypto.Signer, error) { if cfg, ok := pb.(*keyspb.PKCS11Config); ok { return pkcs11.FromConfig(*pkcs11ModulePath, cfg) } return nil, fmt.Errorf("pkcs11: got %T, want *keyspb.PKCS11Config", pb) }) if *maxGetEntries > 0 { ctfe.MaxGetEntriesAllowed = *maxGetEntries } var cfg *configpb.LogMultiConfig var err error // Get log config from file before we start. This is a different proto // type if we're using a multi backend configuration (no rpcBackend set // in flags). The single-backend config is converted to a multi config so // they can be treated the same. if len(*rpcBackend) > 0 { var cfgs []*configpb.LogConfig if cfgs, err = ctfe.LogConfigFromFile(*logConfig); err == nil { cfg = ctfe.ToMultiLogConfig(cfgs, *rpcBackend) } } else { cfg, err = ctfe.MultiLogConfigFromFile(*logConfig) } if err != nil { klog.Exitf("Failed to read config: %v", err) } beMap, err := ctfe.ValidateLogMultiConfig(cfg) if err != nil { klog.Exitf("Invalid config: %v", err) } klog.CopyStandardLogTo("WARNING") klog.Info("**** CT HTTP Server Starting ****") metricsAt := *metricsEndpoint if metricsAt == "" { metricsAt = *httpEndpoint } dialOpts := []grpc.DialOption{grpc.WithInsecure()} if len(*etcdServers) > 0 { // Use etcd to provide endpoint resolution. cfg := clientv3.Config{Endpoints: strings.Split(*etcdServers, ","), DialTimeout: 5 * time.Second} client, err := clientv3.New(cfg) if err != nil { klog.Exitf("Failed to connect to etcd at %v: %v", *etcdServers, err) } httpManager, err := endpoints.NewManager(client, *etcdHTTPService) if err != nil { klog.Exitf("Failed to create etcd http manager: %v", err) } metricsManager, err := endpoints.NewManager(client, *etcdMetricsService) if err != nil { klog.Exitf("Failed to create etcd metrics manager: %v", err) } etcdHTTPKey := fmt.Sprintf("%s/%s", *etcdHTTPService, *httpEndpoint) klog.Infof("Announcing our presence at %v with %+v", etcdHTTPKey, *httpEndpoint) if err := httpManager.AddEndpoint(ctx, etcdHTTPKey, endpoints.Endpoint{Addr: *httpEndpoint}); err != nil { klog.Errorf("AddEndpoint(): %v", err) } etcdMetricsKey := fmt.Sprintf("%s/%s", *etcdMetricsService, metricsAt) klog.Infof("Announcing our presence in %v with %+v", *etcdMetricsService, metricsAt) if err := metricsManager.AddEndpoint(ctx, etcdMetricsKey, endpoints.Endpoint{Addr: metricsAt}); err != nil { klog.Errorf("AddEndpoint(): %v", err) } defer func() { klog.Infof("Removing our presence in %v", etcdHTTPKey) if err := httpManager.DeleteEndpoint(ctx, etcdHTTPKey); err != nil { klog.Errorf("DeleteEndpoint(): %v", err) } klog.Infof("Removing our presence in %v", etcdMetricsKey) if err := metricsManager.DeleteEndpoint(ctx, etcdMetricsKey); err != nil { klog.Errorf("DeleteEndpoint(): %v", err) } }() } else if strings.Contains(*rpcBackend, ",") { // This should probably not be used in production. Either use etcd or a gRPC // load balancer. It's only used by the integration tests. klog.Warning("Multiple RPC backends from flags not recommended for production. Should probably be using etcd or a gRPC load balancer / proxy.") res := manual.NewBuilderWithScheme("whatever") backends := strings.Split(*rpcBackend, ",") endpoints := make([]resolver.Endpoint, 0, len(backends)) for _, backend := range backends { endpoints = append(endpoints, resolver.Endpoint{Addresses: []resolver.Address{{Addr: backend}}}) } res.InitialState(resolver.State{Endpoints: endpoints}) resolver.SetDefaultScheme(res.Scheme()) dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`), grpc.WithResolvers(res)) } else { klog.Infof("Using regular DNS resolver") dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`)) } // Dial all our log backends. clientMap := make(map[string]trillian.TrillianLogClient) for _, be := range beMap { klog.Infof("Dialling backend: %v", be) if len(beMap) == 1 { // If there's only one of them we use the blocking option as we can't // serve anything until connected. dialOpts = append(dialOpts, grpc.WithBlock()) } conn, err := grpc.Dial(be.BackendSpec, dialOpts...) if err != nil { klog.Exitf("Could not dial RPC server: %v: %v", be, err) } defer func() { if err := conn.Close(); err != nil { klog.Errorf("Could not close RPC connection: %v", err) } }() clientMap[be.Name] = trillian.NewTrillianLogClient(conn) } // Allow cross-origin requests to all handlers registered on corsMux. // This is safe for CT log handlers because the log is public and // unauthenticated so cross-site scripting attacks are not a concern. corsMux := http.NewServeMux() corsHandler := cors.AllowAll().Handler(corsMux) http.Handle("/", corsHandler) // Register handlers for all the configured logs using the correct RPC // client. var publicKeys []crypto.PublicKey for _, c := range cfg.LogConfigs.Config { inst, err := setupAndRegister(ctx, clientMap[c.LogBackendName], *rpcDeadline, c, corsMux, *handlerPrefix, *maskInternalErrors, cache.Type(*cacheType), cache.Option{ Size: *cacheSize, TTL: *cacheTTL, }, ) if err != nil { klog.Exitf("Failed to set up log instance for %+v: %v", cfg, err) } if *getSTHInterval > 0 { go inst.RunUpdateSTH(ctx, *getSTHInterval) } // Ensure that this log does not share the same private key as any other // log that has already been set up and registered. if publicKey := inst.GetPublicKey(); publicKey != nil { for _, p := range publicKeys { switch pub := publicKey.(type) { case *ecdsa.PublicKey: if pub.Equal(p) { klog.Exitf("Same private key used by more than one log") } case ed25519.PublicKey: if pub.Equal(p) { klog.Exitf("Same private key used by more than one log") } case *rsa.PublicKey: if pub.Equal(p) { klog.Exitf("Same private key used by more than one log") } } } publicKeys = append(publicKeys, publicKey) } } // Return a 200 on the root, for GCE default health checking :/ corsMux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { if req.URL.Path == "/" { resp.WriteHeader(http.StatusOK) } else { resp.WriteHeader(http.StatusNotFound) } }) // Export a healthz target. corsMux.HandleFunc("/healthz", func(resp http.ResponseWriter, req *http.Request) { // TODO(al): Wire this up to tell the truth. if _, err := resp.Write([]byte("ok")); err != nil { klog.Errorf("resp.Write(): %v", err) } }) if metricsAt != *httpEndpoint { // Run a separate handler for metrics. go func() { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) metricsServer := http.Server{Addr: metricsAt, Handler: mux} err := metricsServer.ListenAndServe() klog.Warningf("Metrics server exited: %v", err) }() } else { // Handle metrics on the DefaultServeMux. http.Handle("/metrics", promhttp.Handler()) } // If we're enabling tracing we need to use an instrumented http.Handler. var handler http.Handler if *tracing { handler, err = opencensus.EnableHTTPServerTracing(*tracingProjectID, *tracingPercent) if err != nil { klog.Exitf("Failed to initialize stackdriver / opencensus tracing: %v", err) } } // Bring up the HTTP server and serve until we get a signal not to. srv := http.Server{Addr: *httpEndpoint, Handler: handler} shutdownWG := new(sync.WaitGroup) go awaitSignal(func() { shutdownWG.Add(1) defer shutdownWG.Done() // Allow 60s for any pending requests to finish then terminate any stragglers ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() klog.Info("Shutting down HTTP server...") if err := srv.Shutdown(ctx); err != nil { klog.Errorf("srv.Shutdown(): %v", err) } klog.Info("HTTP server shutdown") }) err = srv.ListenAndServe() if err != http.ErrServerClosed { klog.Warningf("Server exited: %v", err) } // Wait will only block if the function passed to awaitSignal was called, // in which case it'll block until the HTTP server has gracefully shutdown shutdownWG.Wait() klog.Flush() } // awaitSignal waits for standard termination signals, then runs the given // function; it should be run as a separate goroutine. func awaitSignal(doneFn func()) { // Arrange notification for the standard set of signals used to terminate a server sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // Now block main and wait for a signal sig := <-sigs klog.Warningf("Signal received: %v", sig) klog.Flush() doneFn() } func setupAndRegister(ctx context.Context, client trillian.TrillianLogClient, deadline time.Duration, cfg *configpb.LogConfig, mux *http.ServeMux, globalHandlerPrefix string, maskInternalErrors bool, cacheType cache.Type, cacheOption cache.Option) (*ctfe.Instance, error) { vCfg, err := ctfe.ValidateLogConfig(cfg) if err != nil { return nil, err } opts := ctfe.InstanceOptions{ Validated: vCfg, Client: client, Deadline: deadline, MetricFactory: prometheus.MetricFactory{}, RequestLog: new(ctfe.DefaultRequestLog), MaskInternalErrors: maskInternalErrors, CacheType: cacheType, CacheOption: cacheOption, } if *quotaRemote { klog.Info("Enabling quota for requesting IP") opts.RemoteQuotaUser = func(r *http.Request) string { var remoteUser = realip.FromRequest(r) if len(remoteUser) == 0 { return unknownRemoteUser } return remoteUser } } if *quotaIntermediate { klog.Info("Enabling quota for intermediate certificates") opts.CertificateQuotaUser = ctfe.QuotaUserForCert } // Full handler pattern will be of the form "/logs/yyz/ct/v1/add-chain", where "/logs" is the // HandlerPrefix and "yyz" is the c.Prefix for this particular log. Use the default // HandlerPrefix unless the log config overrides it. The custom prefix in // the log configuration intended for use in migration scenarios where logs // have an existing URL path that differs from the global one. For example // if all new logs are served on "/logs/log/..." and a previously existing // log is at "/log/..." this is now supported. lhp := globalHandlerPrefix if ohPrefix := cfg.OverrideHandlerPrefix; len(ohPrefix) > 0 { klog.Infof("Log with prefix: %s is using a custom HandlerPrefix: %s", cfg.Prefix, ohPrefix) lhp = "/" + strings.Trim(ohPrefix, "/") } inst, err := ctfe.SetUpInstance(ctx, opts) if err != nil { return nil, err } for path, handler := range inst.Handlers { mux.Handle(lhp+path, handler) } return inst, nil } google-certificate-transparency-go-2308f62/trillian/ctfe/doc.go000066400000000000000000000021001462611535200244650ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package ctfe contains a usage example by providing an implementation of an RFC6962 compatible CT log server using a Trillian log server as backend storage via its GRPC API. IMPORTANT: Only code rooted within this part of the tree should refer to the CT GitHub repository. Other parts of the system must not assume that the data they're processing is X.509 or CT related. The CT repository can be found at: https://github.com/google/certificate-transparency */ package ctfe google-certificate-transparency-go-2308f62/trillian/ctfe/handlers.go000066400000000000000000001304651462611535200255400ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "flag" "fmt" "io" "net/http" "strconv" "strings" "sync" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/util" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian" "github.com/google/trillian/monitoring" "github.com/google/trillian/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/prototext" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) var ( alignGetEntries = flag.Bool("align_getentries", true, "Enable get-entries request alignment") getEntriesMetrics = flag.Bool("getentries_metrics", false, "Export get-entries distribution metrics") ) const ( // HTTP Cache-Control header cacheControlHeader = "Cache-Control" // Value for Cache-Control header when response contains immutable data, i.e. entries or proofs. Allows the response to be cached for 1 day. cacheControlImmutable = "public, max-age=86400" // HTTP content type header contentTypeHeader string = "Content-Type" // MIME content type for JSON contentTypeJSON string = "application/json" // The name of the JSON response map key in get-roots responses jsonMapKeyCertificates string = "certificates" // The name of the get-entries start parameter getEntriesParamStart = "start" // The name of the get-entries end parameter getEntriesParamEnd = "end" // The name of the get-proof-by-hash parameter getProofParamHash = "hash" // The name of the get-proof-by-hash tree size parameter getProofParamTreeSize = "tree_size" // The name of the get-sth-consistency first snapshot param getSTHConsistencyParamFirst = "first" // The name of the get-sth-consistency second snapshot param getSTHConsistencyParamSecond = "second" // The name of the get-entry-and-proof index parameter getEntryAndProofParamLeafIndex = "leaf_index" // The name of the get-entry-and-proof tree size parameter getEntryAndProofParamTreeSize = "tree_size" ) var ( // MaxGetEntriesAllowed is the number of entries we allow in a get-entries request MaxGetEntriesAllowed int64 = 1000 // Use an explicitly empty slice for empty proofs so it gets JSON-encoded as // '[]' rather than 'null'. emptyProof = make([][]byte, 0) ) // EntrypointName identifies a CT entrypoint as defined in section 4 of RFC 6962. type EntrypointName string // Constants for entrypoint names, as exposed in statistics/logging. const ( AddChainName = EntrypointName("AddChain") AddPreChainName = EntrypointName("AddPreChain") GetSTHName = EntrypointName("GetSTH") GetSTHConsistencyName = EntrypointName("GetSTHConsistency") GetProofByHashName = EntrypointName("GetProofByHash") GetEntriesName = EntrypointName("GetEntries") GetRootsName = EntrypointName("GetRoots") GetEntryAndProofName = EntrypointName("GetEntryAndProof") ) var ( // Metrics are all per-log (label "logid"), but may also be // per-entrypoint (label "ep") or per-return-code (label "rc"). once sync.Once knownLogs monitoring.Gauge // logid => value (always 1.0) isMirrorLog monitoring.Gauge // logid => value (either 0.0 or 1.0) maxMergeDelay monitoring.Gauge // logid => value expMergeDelay monitoring.Gauge // logid => value lastSCTTimestamp monitoring.Gauge // logid => value lastSTHTimestamp monitoring.Gauge // logid => value lastSTHTreeSize monitoring.Gauge // logid => value frozenSTHTimestamp monitoring.Gauge // logid => value reqsCounter monitoring.Counter // logid, ep => value rspsCounter monitoring.Counter // logid, ep, rc => value rspLatency monitoring.Histogram // logid, ep, rc => value alignedGetEntries monitoring.Counter // logid, aligned => count getEntriesStartPercentiles monitoring.Histogram // logid => percentile ) // setupMetrics initializes all the exported metrics. func setupMetrics(mf monitoring.MetricFactory) { knownLogs = mf.NewGauge("known_logs", "Set to 1 for known logs", "logid") isMirrorLog = mf.NewGauge("is_mirror", "Set to 1 for mirror logs", "logid") maxMergeDelay = mf.NewGauge("max_merge_delay", "Maximum Merge Delay in seconds", "logid") expMergeDelay = mf.NewGauge("expected_merge_delay", "Expected Merge Delay in seconds", "logid") lastSCTTimestamp = mf.NewGauge("last_sct_timestamp", "Time of last SCT in ms since epoch", "logid") lastSTHTimestamp = mf.NewGauge("last_sth_timestamp", "Time of last STH in ms since epoch", "logid") lastSTHTreeSize = mf.NewGauge("last_sth_treesize", "Size of tree at last STH", "logid") frozenSTHTimestamp = mf.NewGauge("frozen_sth_timestamp", "Time of the frozen STH in ms since epoch", "logid") reqsCounter = mf.NewCounter("http_reqs", "Number of requests", "logid", "ep") rspsCounter = mf.NewCounter("http_rsps", "Number of responses", "logid", "ep", "rc") rspLatency = mf.NewHistogram("http_latency", "Latency of responses in seconds", "logid", "ep", "rc") alignedGetEntries = mf.NewCounter("aligned_get_entries", "Number of get-entries requests which were aligned to size limit boundaries", "logid", "aligned") getEntriesStartPercentiles = mf.NewHistogramWithBuckets( "get_leaves_start_percentiles", "Start index of GetLeavesByRange request using percentage of current log size at the time", monitoring.PercentileBuckets(5), "logid", ) } // Entrypoints is a list of entrypoint names as exposed in statistics/logging. var Entrypoints = []EntrypointName{AddChainName, AddPreChainName, GetSTHName, GetSTHConsistencyName, GetProofByHashName, GetEntriesName, GetRootsName, GetEntryAndProofName} // PathHandlers maps from a path to the relevant AppHandler instance. type PathHandlers map[string]AppHandler // AppHandler holds a logInfo and a handler function that uses it, and is // an implementation of the http.Handler interface. type AppHandler struct { Info *logInfo Handler func(context.Context, *logInfo, http.ResponseWriter, *http.Request) (int, error) Name EntrypointName Method string // http.MethodGet or http.MethodPost } // ServeHTTP for an AppHandler invokes the underlying handler function but // does additional common error and stats processing. func (a AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var statusCode int label0 := strconv.FormatInt(a.Info.logID, 10) label1 := string(a.Name) reqsCounter.Inc(label0, label1) startTime := a.Info.TimeSource.Now() logCtx := a.Info.RequestLog.Start(r.Context()) a.Info.RequestLog.LogPrefix(logCtx, a.Info.LogPrefix) defer func() { latency := a.Info.TimeSource.Now().Sub(startTime).Seconds() rspLatency.Observe(latency, label0, label1, strconv.Itoa(statusCode)) }() klog.V(2).Infof("%s: request %v %q => %s", a.Info.LogPrefix, r.Method, r.URL, a.Name) if r.Method != a.Method { klog.Warningf("%s: %s wrong HTTP method: %v", a.Info.LogPrefix, a.Name, r.Method) a.Info.SendHTTPError(w, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed: %s", r.Method)) a.Info.RequestLog.Status(logCtx, http.StatusMethodNotAllowed) return } // For GET requests all params come as form encoded so we might as well parse them now. // POSTs will decode the raw request body as JSON later. if r.Method == http.MethodGet { if err := r.ParseForm(); err != nil { a.Info.SendHTTPError(w, http.StatusBadRequest, fmt.Errorf("failed to parse form data: %s", err)) a.Info.RequestLog.Status(logCtx, http.StatusBadRequest) return } } // Many/most of the handlers forward the request on to the Log RPC server; impose a deadline // on this onward request. ctx, cancel := context.WithDeadline(logCtx, getRPCDeadlineTime(a.Info)) defer cancel() var err error statusCode, err = a.Handler(ctx, a.Info, w, r) a.Info.RequestLog.Status(ctx, statusCode) klog.V(2).Infof("%s: %s <= st=%d", a.Info.LogPrefix, a.Name, statusCode) rspsCounter.Inc(label0, label1, strconv.Itoa(statusCode)) if err != nil { klog.Warningf("%s: %s handler error: %v", a.Info.LogPrefix, a.Name, err) a.Info.SendHTTPError(w, statusCode, err) return } // Additional check, for consistency the handler must return an error for non-200 st if statusCode != http.StatusOK { klog.Warningf("%s: %s handler non 200 without error: %d %v", a.Info.LogPrefix, a.Name, statusCode, err) a.Info.SendHTTPError(w, http.StatusInternalServerError, fmt.Errorf("http handler misbehaved, st: %d", statusCode)) return } } // CertValidationOpts contains various parameters for certificate chain validation type CertValidationOpts struct { // trustedRoots is a pool of certificates that defines the roots the CT log will accept trustedRoots *x509util.PEMCertPool // currentTime is the time used for checking a certificate's validity period // against. If it's zero then time.Now() is used. Only for testing. currentTime time.Time // rejectExpired indicates that expired certificates will be rejected. rejectExpired bool // rejectUnexpired indicates that certificates that are currently valid or not yet valid will be rejected. rejectUnexpired bool // notAfterStart is the earliest notAfter date which will be accepted. // nil means no lower bound on the accepted range. notAfterStart *time.Time // notAfterLimit defines the cut off point of notAfter dates - only notAfter // dates strictly *before* notAfterLimit will be accepted. // nil means no upper bound on the accepted range. notAfterLimit *time.Time // acceptOnlyCA will reject any certificate without the CA bit set. acceptOnlyCA bool // extKeyUsages contains the list of EKUs to use during chain verification extKeyUsages []x509.ExtKeyUsage // rejectExtIds contains a list of X.509 extension IDs to reject during chain verification. rejectExtIds []asn1.ObjectIdentifier } // NewCertValidationOpts builds validation options based on parameters. func NewCertValidationOpts(trustedRoots *x509util.PEMCertPool, currentTime time.Time, rejectExpired bool, rejectUnexpired bool, notAfterStart *time.Time, notAfterLimit *time.Time, acceptOnlyCA bool, extKeyUsages []x509.ExtKeyUsage) CertValidationOpts { var vOpts CertValidationOpts vOpts.trustedRoots = trustedRoots vOpts.currentTime = currentTime vOpts.rejectExpired = rejectExpired vOpts.rejectUnexpired = rejectUnexpired vOpts.notAfterStart = notAfterStart vOpts.notAfterLimit = notAfterLimit vOpts.acceptOnlyCA = acceptOnlyCA vOpts.extKeyUsages = extKeyUsages return vOpts } // logInfo holds information for a specific log instance. type logInfo struct { // LogPrefix is a pre-formatted string identifying the log for diagnostics LogPrefix string // TimeSource is a util.TimeSource that can be injected for testing TimeSource util.TimeSource // RequestLog is a logger for various request / processing / response debug // information. RequestLog RequestLog // Instance-wide options instanceOpts InstanceOptions // logID is the tree ID that identifies this log in node storage logID int64 // validationOpts contains the certificate chain validation parameters validationOpts CertValidationOpts // rpcClient is the client used to communicate with the Trillian backend rpcClient trillian.TrillianLogClient // signer signs objects (e.g. STHs, SCTs) for regular logs signer crypto.Signer // sthGetter provides STHs for the log sthGetter STHGetter // issuanceChainService provides the issuance chain add and get operations issuanceChainService *issuanceChainService } // newLogInfo creates a new instance of logInfo. func newLogInfo( instanceOpts InstanceOptions, validationOpts CertValidationOpts, signer crypto.Signer, timeSource util.TimeSource, issuanceChainService *issuanceChainService, ) *logInfo { vCfg := instanceOpts.Validated cfg := vCfg.Config logID, prefix := cfg.LogId, cfg.Prefix li := &logInfo{ logID: logID, LogPrefix: fmt.Sprintf("%s{%d}", prefix, logID), rpcClient: instanceOpts.Client, signer: signer, TimeSource: timeSource, instanceOpts: instanceOpts, validationOpts: validationOpts, RequestLog: instanceOpts.RequestLog, } once.Do(func() { setupMetrics(instanceOpts.MetricFactory) }) label := strconv.FormatInt(logID, 10) knownLogs.Set(1.0, label) switch { case vCfg.FrozenSTH != nil: li.sthGetter = &FrozenSTHGetter{sth: vCfg.FrozenSTH} frozenSTHTimestamp.Set(float64(vCfg.FrozenSTH.Timestamp), label) case cfg.IsMirror: st := instanceOpts.STHStorage if st == nil { st = DefaultMirrorSTHStorage{} } li.sthGetter = &MirrorSTHGetter{li: li, st: st} default: li.sthGetter = &LogSTHGetter{li: li} } if cfg.IsMirror { isMirrorLog.Set(1.0, label) } else { isMirrorLog.Set(0.0, label) } maxMergeDelay.Set(float64(cfg.MaxMergeDelaySec), label) expMergeDelay.Set(float64(cfg.ExpectedMergeDelaySec), label) li.issuanceChainService = issuanceChainService return li } // Handlers returns a map from URL paths (with the given prefix) and AppHandler instances // to handle those entrypoints. func (li *logInfo) Handlers(prefix string) PathHandlers { if !strings.HasPrefix(prefix, "/") { prefix = "/" + prefix } prefix = strings.TrimRight(prefix, "/") // Bind the logInfo instance to give an AppHandler instance for each endpoint. ph := PathHandlers{ prefix + ct.AddChainPath: AppHandler{Info: li, Handler: addChain, Name: AddChainName, Method: http.MethodPost}, prefix + ct.AddPreChainPath: AppHandler{Info: li, Handler: addPreChain, Name: AddPreChainName, Method: http.MethodPost}, prefix + ct.GetSTHPath: AppHandler{Info: li, Handler: getSTH, Name: GetSTHName, Method: http.MethodGet}, prefix + ct.GetSTHConsistencyPath: AppHandler{Info: li, Handler: getSTHConsistency, Name: GetSTHConsistencyName, Method: http.MethodGet}, prefix + ct.GetProofByHashPath: AppHandler{Info: li, Handler: getProofByHash, Name: GetProofByHashName, Method: http.MethodGet}, prefix + ct.GetEntriesPath: AppHandler{Info: li, Handler: getEntries, Name: GetEntriesName, Method: http.MethodGet}, prefix + ct.GetRootsPath: AppHandler{Info: li, Handler: getRoots, Name: GetRootsName, Method: http.MethodGet}, prefix + ct.GetEntryAndProofPath: AppHandler{Info: li, Handler: getEntryAndProof, Name: GetEntryAndProofName, Method: http.MethodGet}, } // Remove endpoints not provided by readonly logs and mirrors. if li.instanceOpts.Validated.Config.IsReadonly || li.instanceOpts.Validated.Config.IsMirror { delete(ph, prefix+ct.AddChainPath) delete(ph, prefix+ct.AddPreChainPath) } return ph } // SendHTTPError generates a custom error page to give more information on why something didn't work func (li *logInfo) SendHTTPError(w http.ResponseWriter, statusCode int, err error) { errorBody := http.StatusText(statusCode) if !li.instanceOpts.MaskInternalErrors || statusCode != http.StatusInternalServerError { errorBody += fmt.Sprintf("\n%v", err) } http.Error(w, errorBody, statusCode) } // getSTH returns the current STH as known to the STH getter, and updates tree // size / timestamp metrics correspondingly. func (li *logInfo) getSTH(ctx context.Context) (*ct.SignedTreeHead, error) { sth, err := li.sthGetter.GetSTH(ctx) if err != nil { return nil, err } logID := strconv.FormatInt(li.logID, 10) lastSTHTimestamp.Set(float64(sth.Timestamp), logID) lastSTHTreeSize.Set(float64(sth.TreeSize), logID) return sth, nil } // ParseBodyAsJSONChain tries to extract cert-chain out of request. func ParseBodyAsJSONChain(r *http.Request) (ct.AddChainRequest, error) { body, err := io.ReadAll(r.Body) if err != nil { klog.V(1).Infof("Failed to read request body: %v", err) return ct.AddChainRequest{}, err } var req ct.AddChainRequest if err := json.Unmarshal(body, &req); err != nil { klog.V(1).Infof("Failed to parse request body: %v", err) return ct.AddChainRequest{}, err } // The cert chain is not allowed to be empty. We'll defer other validation for later if len(req.Chain) == 0 { klog.V(1).Infof("Request chain is empty: %q", body) return ct.AddChainRequest{}, errors.New("cert chain was empty") } return req, nil } // appendUserCharge adds the specified user to the passed in ChargeTo and // and returns the result. // If the passed-in ChargeTo is nil, then a new one is created with the passed // in user and returned. func appendUserCharge(a *trillian.ChargeTo, user string) *trillian.ChargeTo { if a == nil { a = &trillian.ChargeTo{} } a.User = append(a.User, user) return a } // chargeUser returns a trillian.ChargeTo containing an ID for the remote User, // or nil if instanceOpts does not have a RemoteQuotaUser function set. func (li *logInfo) chargeUser(r *http.Request) *trillian.ChargeTo { if li.instanceOpts.RemoteQuotaUser != nil { return &trillian.ChargeTo{User: []string{li.instanceOpts.RemoteQuotaUser(r)}} } return nil } // addChainInternal is called by add-chain and add-pre-chain as the logic involved in // processing these requests is almost identical func addChainInternal(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request, isPrecert bool) (int, error) { var method EntrypointName var etype ct.LogEntryType if isPrecert { method = AddPreChainName etype = ct.PrecertLogEntryType } else { method = AddChainName etype = ct.X509LogEntryType } // Check the contents of the request and convert to slice of certificates. addChainReq, err := ParseBodyAsJSONChain(r) if err != nil { return http.StatusBadRequest, fmt.Errorf("%s: failed to parse add-chain body: %s", li.LogPrefix, err) } // Log the DERs now because they might not parse as valid X.509. for _, der := range addChainReq.Chain { li.RequestLog.AddDERToChain(ctx, der) } chain, err := verifyAddChain(li, addChainReq, isPrecert) if err != nil { return http.StatusBadRequest, fmt.Errorf("failed to verify add-chain contents: %s", err) } for _, cert := range chain { li.RequestLog.AddCertToChain(ctx, cert) } // Get the current time in the form used throughout RFC6962, namely milliseconds since Unix // epoch, and use this throughout. timeMillis := uint64(li.TimeSource.Now().UnixNano() / millisPerNano) // Build the MerkleTreeLeaf that gets sent to the backend, and make a trillian.LogLeaf for it. merkleLeaf, err := ct.MerkleTreeLeafFromChain(chain, etype, timeMillis) if err != nil { return http.StatusBadRequest, fmt.Errorf("failed to build MerkleTreeLeaf: %s", err) } leaf, err := li.issuanceChainService.BuildLogLeaf(ctx, chain, li.LogPrefix, merkleLeaf, isPrecert) if err != nil { return http.StatusInternalServerError, err } // Send the Merkle tree leaf on to the Log server. req := trillian.QueueLeafRequest{ LogId: li.logID, Leaf: leaf, ChargeTo: li.chargeUser(r), } if li.instanceOpts.CertificateQuotaUser != nil { // TODO(al): ignore pre-issuers? Probably doesn't matter for _, cert := range chain[1:] { req.ChargeTo = appendUserCharge(req.ChargeTo, li.instanceOpts.CertificateQuotaUser(cert)) } } klog.V(2).Infof("%s: %s => grpc.QueueLeaves", li.LogPrefix, method) rsp, err := li.rpcClient.QueueLeaf(ctx, &req) klog.V(2).Infof("%s: %s <= grpc.QueueLeaves err=%v", li.LogPrefix, method, err) if err != nil { return li.toHTTPStatus(err), fmt.Errorf("backend QueueLeaves request failed: %s", err) } if rsp == nil { return http.StatusInternalServerError, errors.New("missing QueueLeaves response") } if rsp.QueuedLeaf == nil { return http.StatusInternalServerError, errors.New("QueueLeaf did not return the leaf") } // Always use the returned leaf as the basis for an SCT. var loggedLeaf ct.MerkleTreeLeaf if rest, err := tls.Unmarshal(rsp.QueuedLeaf.Leaf.LeafValue, &loggedLeaf); err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to reconstruct MerkleTreeLeaf: %s", err) } else if len(rest) > 0 { return http.StatusInternalServerError, fmt.Errorf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest)) } // As the Log server has definitely got the Merkle tree leaf, we can // generate an SCT and respond with it. sct, err := buildV1SCT(li.signer, &loggedLeaf) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to generate SCT: %s", err) } sctBytes, err := tls.Marshal(*sct) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to marshall SCT: %s", err) } // We could possibly fail to issue the SCT after this but it's v. unlikely. li.RequestLog.IssueSCT(ctx, sctBytes) err = marshalAndWriteAddChainResponse(sct, li.signer, w) if err != nil { // reason is logged and http status is already set return http.StatusInternalServerError, fmt.Errorf("failed to write response: %s", err) } klog.V(3).Infof("%s: %s <= SCT", li.LogPrefix, method) if sct.Timestamp == timeMillis { lastSCTTimestamp.Set(float64(sct.Timestamp), strconv.FormatInt(li.logID, 10)) } return http.StatusOK, nil } func addChain(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { return addChainInternal(ctx, li, w, r, false) } func addPreChain(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { return addChainInternal(ctx, li, w, r, true) } func getSTH(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { qctx := ctx if li.instanceOpts.RemoteQuotaUser != nil { rqu := li.instanceOpts.RemoteQuotaUser(r) qctx = context.WithValue(qctx, remoteQuotaCtxKey, rqu) } sth, err := li.getSTH(qctx) if err != nil { return li.toHTTPStatus(err), err } if err := writeSTH(sth, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // writeSTH marshals the STH to JSON and writes it to HTTP response. func writeSTH(sth *ct.SignedTreeHead, w http.ResponseWriter) error { jsonRsp := ct.GetSTHResponse{ TreeSize: sth.TreeSize, SHA256RootHash: sth.SHA256RootHash[:], Timestamp: sth.Timestamp, } var err error jsonRsp.TreeHeadSignature, err = tls.Marshal(sth.TreeHeadSignature) if err != nil { return fmt.Errorf("failed to tls.Marshal signature: %s", err) } w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&jsonRsp) if err != nil { return fmt.Errorf("failed to marshal response: %s", err) } _, err = w.Write(jsonData) if err != nil { // Probably too late for this as headers might have been written but we // don't know for sure. return fmt.Errorf("failed to write response data: %s", err) } return nil } // nolint:staticcheck func getSTHConsistency(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { first, second, err := parseGetSTHConsistencyRange(r) if err != nil { return http.StatusBadRequest, fmt.Errorf("failed to parse consistency range: %s", err) } li.RequestLog.FirstAndSecond(ctx, first, second) var jsonRsp ct.GetSTHConsistencyResponse if first != 0 { req := trillian.GetConsistencyProofRequest{ LogId: li.logID, FirstTreeSize: first, SecondTreeSize: second, ChargeTo: li.chargeUser(r), } klog.V(2).Infof("%s: GetSTHConsistency(%d, %d) => grpc.GetConsistencyProof %+v", li.LogPrefix, first, second, prototext.Format(&req)) rsp, err := li.rpcClient.GetConsistencyProof(ctx, &req) klog.V(2).Infof("%s: GetSTHConsistency <= grpc.GetConsistencyProof err=%v", li.LogPrefix, err) if err != nil { return li.toHTTPStatus(err), fmt.Errorf("backend GetConsistencyProof request failed: %s", err) } var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot()) } // We can get here with a tree size too small to satisfy the proof. if currentRoot.TreeSize < uint64(second) { return http.StatusBadRequest, fmt.Errorf("need tree size: %d for proof but only got: %d", second, currentRoot.TreeSize) } // Additional sanity checks, none of the hashes in the returned path should be empty if !checkAuditPath(rsp.Proof.Hashes) { return http.StatusInternalServerError, fmt.Errorf("backend returned invalid proof: %v", rsp.Proof) } // We got a valid response from the server. Marshal it as JSON and return it to the client jsonRsp.Consistency = rsp.Proof.Hashes if jsonRsp.Consistency == nil { jsonRsp.Consistency = emptyProof } } else { klog.V(2).Infof("%s: GetSTHConsistency(%d, %d) starts from 0 so return empty proof", li.LogPrefix, first, second) jsonRsp.Consistency = emptyProof } w.Header().Set(cacheControlHeader, cacheControlImmutable) w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&jsonRsp) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-sth-consistency resp: %s", err) } _, err = w.Write(jsonData) if err != nil { // Probably too late for this as headers might have been written but we don't know for sure return http.StatusInternalServerError, fmt.Errorf("failed to write get-sth-consistency resp: %s", err) } return http.StatusOK, nil } // nolint:staticcheck func getProofByHash(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { // Accept any non empty hash that decodes from base64 and let the backend validate it further hash := r.FormValue(getProofParamHash) if len(hash) == 0 { return http.StatusBadRequest, errors.New("get-proof-by-hash: missing / empty hash param for get-proof-by-hash") } leafHash, err := base64.StdEncoding.DecodeString(hash) if err != nil { return http.StatusBadRequest, fmt.Errorf("get-proof-by-hash: invalid base64 hash: %s", err) } treeSize, err := strconv.ParseInt(r.FormValue(getProofParamTreeSize), 10, 64) if err != nil || treeSize < 1 { return http.StatusBadRequest, fmt.Errorf("get-proof-by-hash: missing or invalid tree_size: %v", r.FormValue(getProofParamTreeSize)) } li.RequestLog.LeafHash(ctx, leafHash) li.RequestLog.TreeSize(ctx, treeSize) // Per RFC 6962 section 4.5 the API returns a single proof. This should be the lowest leaf index // Because we request order by sequence and we only passed one hash then the first result is // the correct proof to return req := trillian.GetInclusionProofByHashRequest{ LogId: li.logID, LeafHash: leafHash, TreeSize: treeSize, OrderBySequence: true, ChargeTo: li.chargeUser(r), } rsp, err := li.rpcClient.GetInclusionProofByHash(ctx, &req) if err != nil { return li.toHTTPStatus(err), fmt.Errorf("backend GetInclusionProofByHash request failed: %s", err) } var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot()) } // We could fail to get the proof because the tree size that the server has // is not large enough. if currentRoot.TreeSize < uint64(treeSize) { return http.StatusNotFound, fmt.Errorf("log returned tree size: %d but we expected: %d", currentRoot.TreeSize, treeSize) } // Additional sanity checks on the response. if len(rsp.Proof) == 0 { // The backend returns the STH even when there is no proof, so explicitly // map this to 4xx. return http.StatusNotFound, errors.New("get-proof-by-hash: backend did not return a proof") } if !checkAuditPath(rsp.Proof[0].Hashes) { return http.StatusInternalServerError, fmt.Errorf("get-proof-by-hash: backend returned invalid proof: %v", rsp.Proof[0]) } // All checks complete, marshal and return the response proofRsp := ct.GetProofByHashResponse{ LeafIndex: rsp.Proof[0].LeafIndex, AuditPath: rsp.Proof[0].Hashes, } if proofRsp.AuditPath == nil { proofRsp.AuditPath = emptyProof } w.Header().Set(cacheControlHeader, cacheControlImmutable) w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&proofRsp) if err != nil { klog.Warningf("%s: Failed to marshal get-proof-by-hash resp: %v", li.LogPrefix, proofRsp) return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-proof-by-hash resp: %s", err) } _, err = w.Write(jsonData) if err != nil { // Probably too late for this as headers might have been written but we don't know for sure return http.StatusInternalServerError, fmt.Errorf("failed to write get-proof-by-hash resp: %s", err) } return http.StatusOK, nil } // nolint:staticcheck func getEntries(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { // The first job is to parse the params and make sure they're sensible. We just make // sure the range is valid. We don't do an extra roundtrip to get the current tree // size and prefer to let the backend handle this case start, end, err := parseGetEntriesRange(r, MaxGetEntriesAllowed, li.logID) if err != nil { return http.StatusBadRequest, fmt.Errorf("bad range on get-entries request: %s", err) } li.RequestLog.StartAndEnd(ctx, start, end) // Now make a request to the backend to get the relevant leaves var leaves []*trillian.LogLeaf count := end + 1 - start req := trillian.GetLeavesByRangeRequest{ LogId: li.logID, StartIndex: start, Count: count, ChargeTo: li.chargeUser(r), } rsp, httpStatus, err := rpcGetLeavesByRange(ctx, li, &req) if err != nil { return httpStatus, err } var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot()) } if currentRoot.TreeSize <= uint64(start) { // If the returned tree is too small to contain any leaves return the 4xx // explicitly here. return http.StatusBadRequest, fmt.Errorf("need tree size: %d to get leaves but only got: %d", start+1, currentRoot.TreeSize) } if *getEntriesMetrics { label := strconv.FormatInt(req.LogId, 10) recordStartPercent(start, currentRoot.TreeSize, label) } // Do some sanity checks on the result. if len(rsp.Leaves) > int(count) { return http.StatusInternalServerError, fmt.Errorf("backend returned too many leaves: %d vs [%d,%d]", len(rsp.Leaves), start, end) } for i, leaf := range rsp.Leaves { if leaf.LeafIndex != start+int64(i) { return http.StatusInternalServerError, fmt.Errorf("backend returned unexpected leaf index: rsp.Leaves[%d].LeafIndex=%d for range [%d,%d]", i, leaf.LeafIndex, start, end) } } leaves = rsp.Leaves // Now we've checked the RPC response and it seems to be valid we need // to serialize the leaves in JSON format for the HTTP response. Doing a // round trip via the leaf deserializer gives us another chance to // prevent bad / corrupt data from reaching the client. jsonRsp, err := marshalGetEntriesResponse(li, leaves) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to process leaves returned from backend: %s", err) } w.Header().Set(cacheControlHeader, cacheControlImmutable) w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&jsonRsp) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-entries resp: %s", err) } _, err = w.Write(jsonData) if err != nil { // Probably too late for this as headers might have been written but we don't know for sure return http.StatusInternalServerError, fmt.Errorf("failed to write get-entries resp: %s", err) } return http.StatusOK, nil } // rpcGetLeavesByRange calls Trillian GetLeavesByRange RPC and fixes issuance chain in each log leaf if necessary. func rpcGetLeavesByRange(ctx context.Context, li *logInfo, req *trillian.GetLeavesByRangeRequest) (*trillian.GetLeavesByRangeResponse, int, error) { rsp, err := li.rpcClient.GetLeavesByRange(ctx, req) if err != nil { return nil, li.toHTTPStatus(err), fmt.Errorf("backend GetLeavesByRange request failed: %s", err) } for _, leaf := range rsp.Leaves { if err := li.issuanceChainService.FixLogLeaf(ctx, leaf); err != nil { return nil, http.StatusInternalServerError, fmt.Errorf("failed to fix log leaf: %v", rsp) } } return rsp, http.StatusOK, nil } func getRoots(_ context.Context, li *logInfo, w http.ResponseWriter, _ *http.Request) (int, error) { // Pull out the raw certificates from the parsed versions rawCerts := make([][]byte, 0, len(li.validationOpts.trustedRoots.RawCertificates())) for _, cert := range li.validationOpts.trustedRoots.RawCertificates() { rawCerts = append(rawCerts, cert.Raw) } jsonMap := make(map[string]interface{}) jsonMap[jsonMapKeyCertificates] = rawCerts enc := json.NewEncoder(w) err := enc.Encode(jsonMap) if err != nil { klog.Warningf("%s: get_roots failed: %v", li.LogPrefix, err) return http.StatusInternalServerError, fmt.Errorf("get-roots failed with: %s", err) } return http.StatusOK, nil } // See RFC 6962 Section 4.8. // nolint:staticcheck func getEntryAndProof(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) { // Ensure both numeric params are present and look reasonable. leafIndex, treeSize, err := parseGetEntryAndProofParams(r) if err != nil { return http.StatusBadRequest, fmt.Errorf("failed to parse get-entry-and-proof params: %s", err) } li.RequestLog.LeafIndex(ctx, leafIndex) li.RequestLog.TreeSize(ctx, treeSize) req := trillian.GetEntryAndProofRequest{ LogId: li.logID, LeafIndex: leafIndex, TreeSize: treeSize, ChargeTo: li.chargeUser(r), } rsp, httpStatus, err := rpcGetEntryAndProof(ctx, li, &req) if err != nil { return httpStatus, err } var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot()) } if currentRoot.TreeSize < uint64(treeSize) { // If tree size is not large enough return the 4xx here, would previously // have come from the error status mapping above. return http.StatusBadRequest, fmt.Errorf("need tree size: %d for proof but only got: %d", req.TreeSize, currentRoot.TreeSize) } // Apply some checks that we got reasonable data from the backend if rsp.Leaf == nil || len(rsp.Leaf.LeafValue) == 0 || rsp.Proof == nil { return http.StatusInternalServerError, fmt.Errorf("got RPC bad response, possible extra info: %v", rsp) } if treeSize > 1 && len(rsp.Proof.Hashes) == 0 { return http.StatusInternalServerError, fmt.Errorf("got RPC bad response (missing proof), possible extra info: %v", rsp) } // Build and marshal the response to the client jsonRsp := ct.GetEntryAndProofResponse{ LeafInput: rsp.Leaf.LeafValue, ExtraData: rsp.Leaf.ExtraData, AuditPath: rsp.Proof.Hashes, } w.Header().Set(cacheControlHeader, cacheControlImmutable) w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&jsonRsp) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-entry-and-proof resp: %s", err) } _, err = w.Write(jsonData) if err != nil { // Probably too late for this as headers might have been written but we don't know for sure return http.StatusInternalServerError, fmt.Errorf("failed to write get-entry-and-proof resp: %s", err) } return http.StatusOK, nil } // rpcGetEntryAndProof calls Trillian GetEntryAndProof RPC and fixes issuance chain in the log leaf if necessary. func rpcGetEntryAndProof(ctx context.Context, li *logInfo, req *trillian.GetEntryAndProofRequest) (*trillian.GetEntryAndProofResponse, int, error) { rsp, err := li.rpcClient.GetEntryAndProof(ctx, req) if err != nil { return nil, li.toHTTPStatus(err), fmt.Errorf("backend GetEntryAndProof request failed: %s", err) } if err := li.issuanceChainService.FixLogLeaf(ctx, rsp.Leaf); err != nil { return nil, http.StatusInternalServerError, fmt.Errorf("failed to fix log leaf: %v", rsp) } return rsp, http.StatusOK, nil } // getRPCDeadlineTime calculates the future time an RPC should expire based on our config func getRPCDeadlineTime(li *logInfo) time.Time { return li.TimeSource.Now().Add(li.instanceOpts.Deadline) } // verifyAddChain is used by add-chain and add-pre-chain. It does the checks that the supplied // cert is of the correct type and chains to a trusted root. func verifyAddChain(li *logInfo, req ct.AddChainRequest, expectingPrecert bool) ([]*x509.Certificate, error) { // We already checked that the chain is not empty so can move on to verification validPath, err := ValidateChain(req.Chain, li.validationOpts) if err != nil { // We rejected it because the cert failed checks or we could not find a path to a root etc. // Lots of possible causes for errors return nil, fmt.Errorf("chain failed to verify: %s", err) } isPrecert, err := IsPrecertificate(validPath[0]) if err != nil { return nil, fmt.Errorf("precert test failed: %s", err) } // The type of the leaf must match the one the handler expects if isPrecert != expectingPrecert { if expectingPrecert { klog.Warningf("%s: Cert (or precert with invalid CT ext) submitted as precert chain: %q", li.LogPrefix, req.Chain) } else { klog.Warningf("%s: Precert (or cert with invalid CT ext) submitted as cert chain: %q", li.LogPrefix, req.Chain) } return nil, fmt.Errorf("cert / precert mismatch: %T", expectingPrecert) } return validPath, nil } func extractRawCerts(chain []*x509.Certificate) []ct.ASN1Cert { raw := make([]ct.ASN1Cert, len(chain)) for i, cert := range chain { raw[i] = ct.ASN1Cert{Data: cert.Raw} } return raw } // marshalAndWriteAddChainResponse is used by add-chain and add-pre-chain to create and write // the JSON response to the client func marshalAndWriteAddChainResponse(sct *ct.SignedCertificateTimestamp, signer crypto.Signer, w http.ResponseWriter) error { logID, err := GetCTLogID(signer.Public()) if err != nil { return fmt.Errorf("failed to marshal logID: %s", err) } sig, err := tls.Marshal(sct.Signature) if err != nil { return fmt.Errorf("failed to marshal signature: %s", err) } rsp := ct.AddChainResponse{ SCTVersion: sct.SCTVersion, Timestamp: sct.Timestamp, ID: logID[:], Extensions: base64.StdEncoding.EncodeToString(sct.Extensions), Signature: sig, } w.Header().Set(contentTypeHeader, contentTypeJSON) jsonData, err := json.Marshal(&rsp) if err != nil { return fmt.Errorf("failed to marshal add-chain: %s", err) } _, err = w.Write(jsonData) if err != nil { return fmt.Errorf("failed to write add-chain resp: %s", err) } return nil } func parseGetEntriesRange(r *http.Request, maxRange, logID int64) (int64, int64, error) { start, err := strconv.ParseInt(r.FormValue(getEntriesParamStart), 10, 64) if err != nil { return 0, 0, err } end, err := strconv.ParseInt(r.FormValue(getEntriesParamEnd), 10, 64) if err != nil { return 0, 0, err } if start < 0 || end < 0 { return 0, 0, fmt.Errorf("start (%d) and end (%d) parameters must be >= 0", start, end) } if start > end { return 0, 0, fmt.Errorf("start (%d) and end (%d) is not a valid range", start, end) } count := end - start + 1 if count > maxRange { end = start + maxRange - 1 } if *alignGetEntries && count >= maxRange { // Truncate a "maximally sized" get-entries request at the next multiple // of MaxGetEntriesAllowed. // This is intended to coerce large runs of get-entries requests (e.g. by // monitors/mirrors) into all requesting the same start/end ranges, // thereby making the responses more readily cacheable. d := (end + 1) % maxRange end = end - d alignedGetEntries.Inc(strconv.FormatInt(logID, 10), strconv.FormatBool(d == 0)) } return start, end, nil } func parseGetEntryAndProofParams(r *http.Request) (int64, int64, error) { leafIndex, err := strconv.ParseInt(r.FormValue(getEntryAndProofParamLeafIndex), 10, 64) if err != nil { return 0, 0, err } treeSize, err := strconv.ParseInt(r.FormValue(getEntryAndProofParamTreeSize), 10, 64) if err != nil { return 0, 0, err } if treeSize <= 0 { return 0, 0, fmt.Errorf("tree_size must be > 0, got: %d", treeSize) } if leafIndex < 0 { return 0, 0, fmt.Errorf("leaf_index must be >= 0, got: %d", treeSize) } if leafIndex >= treeSize { return 0, 0, fmt.Errorf("leaf_index %d out of range for tree of size %d", leafIndex, treeSize) } return leafIndex, treeSize, nil } func parseGetSTHConsistencyRange(r *http.Request) (int64, int64, error) { firstVal := r.FormValue(getSTHConsistencyParamFirst) secondVal := r.FormValue(getSTHConsistencyParamSecond) if firstVal == "" { return 0, 0, errors.New("parameter 'first' is required") } if secondVal == "" { return 0, 0, errors.New("parameter 'second' is required") } first, err := strconv.ParseInt(firstVal, 10, 64) if err != nil { return 0, 0, errors.New("parameter 'first' is malformed") } second, err := strconv.ParseInt(secondVal, 10, 64) if err != nil { return 0, 0, errors.New("parameter 'second' is malformed") } if first < 0 || second < 0 { return 0, 0, fmt.Errorf("first and second params cannot be <0: %d %d", first, second) } if second < first { return 0, 0, fmt.Errorf("invalid first, second params: %d %d", first, second) } return first, second, nil } // marshalGetEntriesResponse does the conversion from the backend response to the one we need for // an RFC compliant JSON response to the client. func marshalGetEntriesResponse(li *logInfo, leaves []*trillian.LogLeaf) (ct.GetEntriesResponse, error) { jsonRsp := ct.GetEntriesResponse{} for _, leaf := range leaves { // We're only deserializing it to ensure it's valid, don't need the result. We still // return the data if it fails to deserialize as otherwise the root hash could not // be verified. However this indicates a potentially serious failure in log operation // or data storage that should be investigated. var treeLeaf ct.MerkleTreeLeaf if rest, err := tls.Unmarshal(leaf.LeafValue, &treeLeaf); err != nil { klog.Errorf("%s: Failed to deserialize Merkle leaf from backend: %d", li.LogPrefix, leaf.LeafIndex) } else if len(rest) > 0 { klog.Errorf("%s: Trailing data after Merkle leaf from backend: %d", li.LogPrefix, leaf.LeafIndex) } extraData := leaf.ExtraData if len(extraData) == 0 { klog.Errorf("%s: Missing ExtraData for leaf %d", li.LogPrefix, leaf.LeafIndex) } jsonRsp.Entries = append(jsonRsp.Entries, ct.LeafEntry{ LeafInput: leaf.LeafValue, ExtraData: extraData, }) } return jsonRsp, nil } // checkAuditPath does a quick scan of the proof we got from the backend for consistency. // All the hashes should be non zero length. func checkAuditPath(path [][]byte) bool { for _, node := range path { if len(node) != sha256.Size { return false } } return true } func (li *logInfo) toHTTPStatus(err error) int { if li.instanceOpts.ErrorMapper != nil { if status, ok := li.instanceOpts.ErrorMapper(err); ok { return status } } rpcStatus, ok := status.FromError(err) if !ok { return http.StatusInternalServerError } switch rpcStatus.Code() { case codes.OK: return http.StatusOK case codes.Canceled, codes.DeadlineExceeded: return http.StatusGatewayTimeout case codes.InvalidArgument, codes.OutOfRange, codes.AlreadyExists: return http.StatusBadRequest case codes.NotFound: return http.StatusNotFound case codes.PermissionDenied: return http.StatusForbidden case codes.ResourceExhausted: return http.StatusTooManyRequests case codes.Unauthenticated: return http.StatusUnauthorized case codes.FailedPrecondition: return http.StatusPreconditionFailed case codes.Aborted: return http.StatusConflict case codes.Unimplemented: return http.StatusNotImplemented case codes.Unavailable: return http.StatusServiceUnavailable default: return http.StatusInternalServerError } } // recordStartPercent works out what percentage of the current log size an index corresponds to, // and records this to the getEntriesStartPercentiles histogram. func recordStartPercent(leafIndex int64, treeSize uint64, labelVals ...string) { if treeSize > 0 { percent := float64(leafIndex) / float64(treeSize) * 100.0 getEntriesStartPercentiles.Observe(percent, labelVals...) } } google-certificate-transparency-go-2308f62/trillian/ctfe/handlers_test.go000066400000000000000000002212751462611535200265770ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bufio" "bytes" "context" "crypto" "crypto/sha256" "encoding/hex" "encoding/json" "encoding/pem" "errors" "fmt" "io" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/golang/mock/gomock" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/mockclient" "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/google/certificate-transparency-go/trillian/util" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/trillian" "github.com/google/trillian/monitoring" "github.com/google/trillian/types" "github.com/kylelemons/godebug/pretty" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" cttestonly "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" ) // Arbitrary time for use in tests var fakeTime = time.Date(2016, 7, 22, 11, 01, 13, 0, time.UTC) var fakeTimeMillis = uint64(fakeTime.UnixNano() / millisPerNano) // The deadline should be the above bumped by 500ms var fakeDeadlineTime = time.Date(2016, 7, 22, 11, 01, 13, 500*1000*1000, time.UTC) var fakeTimeSource = util.NewFixedTimeSource(fakeTime) const caCertB64 string = `MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg==` const intermediateCertB64 string = `MIIC3TCCAkagAwIBAgIBCTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMGIxCzAJBgNVBAYTAkdCMTEwLwYDVQQKEyhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgSW50ZXJtZWRpYXRlIENBMQ4wDAYDVQQIEwVXYWxlczEQMA4GA1UE BxMHRXJ3IFdlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA12pnjRFvUi5V /4IckGQlCLcHSxTXcRWQZPeSfv3tuHE1oTZe594Yy9XOhl+GDHj0M7TQ09NAdwLn o+9UKx3+m7qnzflNxZdfxyn4bxBfOBskNTXPnIAPXKeAwdPIRADuZdFu6c9S24rf /lD1xJM1CyGQv1DVvDbzysWo2q6SzYsCAwEAAaOBrzCBrDAdBgNVHQ4EFgQUllUI BQJ4R56Hc3ZBMbwUOkfiKaswfQYDVR0jBHYwdIAUX52IDchz5lTU+A3Y5rDBJLRH w1WhWaRXMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuggEA MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAIgbascZrcdzglcP2qi73 LPd2G+er1/w5wxpM/hvZbWc0yoLyLd5aDIu73YJde28+dhKtjbMAp+IRaYhgIyYi hMOqXSGR79oQv5I103s6KjQNWUGblKSFZvP6w82LU9Wk6YJw6tKXsHIQ+c5KITix iBEUO5P6TnqH3TfhOF8sKQg=` const caAndIntermediateCertsPEM = "-----BEGIN CERTIFICATE-----\n" + caCertB64 + "\n-----END CERTIFICATE-----\n" + "\n-----BEGIN CERTIFICATE-----\n" + intermediateCertB64 + "\n-----END CERTIFICATE-----\n" const remoteQuotaUser = "Moneybags" type handlerTestInfo struct { mockCtrl *gomock.Controller roots *x509util.PEMCertPool client *mockclient.MockTrillianLogClient li *logInfo } const certQuotaPrefix = "CERT:" func quotaUserForCert(c *x509.Certificate) string { return fmt.Sprintf("%s %s", certQuotaPrefix, c.Subject.String()) } func quotaUsersForIssuers(t *testing.T, pem ...string) []string { t.Helper() r := make([]string, 0) for _, p := range pem { c, err := x509util.CertificateFromPEM([]byte(p)) if x509.IsFatal(err) { t.Fatalf("Failed to parse pem: %v", err) } r = append(r, quotaUserForCert(c)) } return r } func (info *handlerTestInfo) setRemoteQuotaUser(u string) { if len(u) > 0 { info.li.instanceOpts.RemoteQuotaUser = func(_ *http.Request) string { return u } } else { info.li.instanceOpts.RemoteQuotaUser = nil } } func (info *handlerTestInfo) enableCertQuota(e bool) { if e { info.li.instanceOpts.CertificateQuotaUser = quotaUserForCert } else { info.li.instanceOpts.CertificateQuotaUser = nil } } // setupTest creates mock objects and contexts. Caller should invoke info.mockCtrl.Finish(). func setupTest(t *testing.T, pemRoots []string, signer crypto.Signer) handlerTestInfo { t.Helper() info := handlerTestInfo{ mockCtrl: gomock.NewController(t), roots: x509util.NewPEMCertPool(), } info.client = mockclient.NewMockTrillianLogClient(info.mockCtrl) vOpts := CertValidationOpts{ trustedRoots: info.roots, rejectExpired: false, } cfg := &configpb.LogConfig{LogId: 0x42, Prefix: "test", IsMirror: false} vCfg := &ValidatedLogConfig{Config: cfg} iOpts := InstanceOptions{Validated: vCfg, Client: info.client, Deadline: time.Millisecond * 500, MetricFactory: monitoring.InertMetricFactory{}, RequestLog: new(DefaultRequestLog)} info.li = newLogInfo(iOpts, vOpts, signer, fakeTimeSource, newIssuanceChainService(nil, nil)) for _, pemRoot := range pemRoots { if !info.roots.AppendCertsFromPEM([]byte(pemRoot)) { klog.Fatal("failed to load cert pool") } } return info } func (info handlerTestInfo) getHandlers() map[string]AppHandler { return map[string]AppHandler{ "get-sth": {Info: info.li, Handler: getSTH, Name: "GetSTH", Method: http.MethodGet}, "get-sth-consistency": {Info: info.li, Handler: getSTHConsistency, Name: "GetSTHConsistency", Method: http.MethodGet}, "get-proof-by-hash": {Info: info.li, Handler: getProofByHash, Name: "GetProofByHash", Method: http.MethodGet}, "get-entries": {Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet}, "get-roots": {Info: info.li, Handler: getRoots, Name: "GetRoots", Method: http.MethodGet}, "get-entry-and-proof": {Info: info.li, Handler: getEntryAndProof, Name: "GetEntryAndProof", Method: http.MethodGet}, } } func (info handlerTestInfo) postHandlers() map[string]AppHandler { return map[string]AppHandler{ "add-chain": {Info: info.li, Handler: addChain, Name: "AddChain", Method: http.MethodPost}, "add-pre-chain": {Info: info.li, Handler: addPreChain, Name: "AddPreChain", Method: http.MethodPost}, } } func TestPostHandlersRejectGet(t *testing.T) { info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil) defer info.mockCtrl.Finish() // Anything in the post handler list should reject GET for path, handler := range info.postHandlers() { t.Run(path, func(t *testing.T) { s := httptest.NewServer(handler) defer s.Close() resp, err := http.Get(s.URL + "/ct/v1/" + path) if err != nil { t.Fatalf("http.Get(%s)=(_,%q); want (_,nil)", path, err) } if got, want := resp.StatusCode, http.StatusMethodNotAllowed; got != want { t.Errorf("http.Get(%s)=(%d,nil); want (%d,nil)", path, got, want) } }) } } func TestGetHandlersRejectPost(t *testing.T) { info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil) defer info.mockCtrl.Finish() // Anything in the get handler list should reject POST. for path, handler := range info.getHandlers() { t.Run(path, func(t *testing.T) { s := httptest.NewServer(handler) defer s.Close() resp, err := http.Post(s.URL+"/ct/v1/"+path, "application/json", nil) if err != nil { t.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", path, err) } if got, want := resp.StatusCode, http.StatusMethodNotAllowed; got != want { t.Errorf("http.Post(%s)=(%d,nil); want (%d,nil)", path, got, want) } }) } } func TestPostHandlersFailure(t *testing.T) { var tests = []struct { descr string body io.Reader want int }{ {"nil", nil, http.StatusBadRequest}, {"''", strings.NewReader(""), http.StatusBadRequest}, {"malformed-json", strings.NewReader("{ !$%^& not valid json "), http.StatusBadRequest}, {"empty-chain", strings.NewReader(`{ "chain": [] }`), http.StatusBadRequest}, {"wrong-chain", strings.NewReader(`{ "chain": [ "test" ] }`), http.StatusBadRequest}, } info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil) defer info.mockCtrl.Finish() for path, handler := range info.postHandlers() { t.Run(path, func(t *testing.T) { s := httptest.NewServer(handler) for _, test := range tests { resp, err := http.Post(s.URL+"/ct/v1/"+path, "application/json", test.body) if err != nil { t.Errorf("http.Post(%s,%s)=(_,%q); want (_,nil)", path, test.descr, err) continue } if resp.StatusCode != test.want { t.Errorf("http.Post(%s,%s)=(%d,nil); want (%d,nil)", path, test.descr, resp.StatusCode, test.want) } } }) } } func TestHandlers(t *testing.T) { path := "/test-prefix/ct/v1/add-chain" info := setupTest(t, nil, nil) defer info.mockCtrl.Finish() for _, test := range []string{ "/test-prefix/", "test-prefix/", "/test-prefix", "test-prefix", } { t.Run(test, func(t *testing.T) { handlers := info.li.Handlers(test) if h, ok := handlers[path]; !ok { t.Errorf("Handlers(%s)[%q]=%+v; want _", test, path, h) } else if h.Name != "AddChain" { t.Errorf("Handlers(%s)[%q].Name=%q; want 'AddChain'", test, path, h.Name) } // Check each entrypoint has a handler if got, want := len(handlers), len(Entrypoints); got != want { t.Fatalf("len(Handlers(%s))=%d; want %d", test, got, want) } // We want to see the same set of handler names that we think we registered. var hNames []EntrypointName for _, v := range handlers { hNames = append(hNames, v.Name) } if !cmp.Equal(Entrypoints, hNames, cmpopts.SortSlices(func(n1, n2 EntrypointName) bool { return n1 < n2 })) { t.Errorf("Handler names mismatch got: %v, want: %v", hNames, Entrypoints) } }) } } func TestGetRoots(t *testing.T) { info := setupTest(t, []string{caAndIntermediateCertsPEM}, nil) defer info.mockCtrl.Finish() handler := AppHandler{Info: info.li, Handler: getRoots, Name: "GetRoots", Method: http.MethodGet} req, err := http.NewRequest(http.MethodGet, "http://example.com/ct/v1/get-roots", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("http.Get(get-roots)=%d; want %d", got, want) } var parsedJSON map[string][]string if err := json.Unmarshal(w.Body.Bytes(), &parsedJSON); err != nil { t.Fatalf("json.Unmarshal(%q)=%q; want nil", w.Body.Bytes(), err) } if got := len(parsedJSON); got != 1 { t.Errorf("len(json)=%d; want 1", got) } certs := parsedJSON[jsonMapKeyCertificates] if got := len(certs); got != 2 { t.Fatalf("len(%q)=%d; want 2", certs, got) } if got, want := certs[0], strings.Replace(caCertB64, "\n", "", -1); got != want { t.Errorf("certs[0]=%s; want %s", got, want) } if got, want := certs[1], strings.Replace(intermediateCertB64, "\n", "", -1); got != want { t.Errorf("certs[1]=%s; want %s", got, want) } } func TestAddChainWhitespace(t *testing.T) { signer, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("Failed to create test signer: %v", err) } info := setupTest(t, []string{cttestonly.FakeCACertPEM}, signer) defer info.mockCtrl.Finish() // Throughout we use variants of a hard-coded POST body derived from a chain of: pemChain := []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM} // Break the JSON into chunks: intro := "{\"chain\"" // followed by colon then the first line of the PEM file chunk1a := "[\"MIIH6DCCBtCgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE" // straight into rest of first entry chunk1b := "BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTAeFw0xNjA1MTMxNDI2NDRaFw0xOTA3MTIxNDI2NDRaMIIBWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMxFTATBgNVBAMMDCouZ29vZ2xlLmNvbTGBwzCBwAYDVQQEDIG4UkZDNTI4MCBzNC4yLjEuOSAnVGhlIHBhdGhMZW5Db25zdHJhaW50IGZpZWxkIC4uLiBnaXZlcyB0aGUgbWF4aW11bSBudW1iZXIgb2Ygbm9uLXNlbGYtaXNzdWVkIGludGVybWVkaWF0ZSBjZXJ0aWZpY2F0ZXMgdGhhdCBtYXkgZm9sbG93IHRoaXMgY2VydGlmaWNhdGUgaW4gYSB2YWxpZCBjZXJ0aWZpY2F0aW9uIHBhdGguJzEqMCgGA1UEKgwhSW50ZXJtZWRpYXRlIENBIGNlcnQgdXNlZCB0byBzaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExAk5hPUVjRJUsgKc+QHibTVH1A3QEWFmCTUdyxIUlbI//zW9Io5N/DhQLSLWmB7KoCOvpJZ+MtGCXzFX+yj/N6OCBGMwggRfMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCCA0IGA1UdEQSCAzkwggM1ggwqLmdvb2dsZS5jb22CDSouYW5kcm9pZC5jb22CFiouYXBwZW5naW5lLmdvb2dsZS5jb22CEiouY2xvdWQuZ29vZ2xlLmNvbYIWKi5nb29nbGUtYW5hbHl0aWNzLmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2dsZS5jby5pboIOKi5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2dsZS5jb20uYXKCDyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdvb2dsZS5jb20uY2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8qLmdvb2dsZS5jb20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29nbGUuZnKCCyouZ29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyouZ29vZ2xlLnBsggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xlY29tbWVyY2UuY29tghEqLmdvb2dsZXZpZGVvLmNvbYIMKi5nc3RhdGljLmNugg0qLmdzdGF0aWMuY29tggoqLmd2dDEuY29tggoqLmd2dDIuY29tghQqLm1ldHJpYy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1YmUuY29tghYqLnlvdXR1YmVlZHVjYXRpb24uY29tggsqLnl0aW1nLmNvbYIaYW5kcm9pZC5jbGllbnRzLmdvb2dsZS5jb22CC2FuZHJvaWQuY29tggRnLmNvggZnb28uZ2yCFGdvb2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJjZS5jb22CCnVyY2hpbi5jb22CCHlvdXR1LmJlggt5b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5jb20wDAYDVR0PBAUDAweAADBoBggrBgEFBQcBAQRcMFowKwYIKwYBBQUHMAKGH2h0dHA6Ly9wa2kuZ29vZ2xlLmNvbS9HSUFHMi5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly9jbGllbnRzMS5nb29nbGUuY29tL29jc3AwHQYDVR0OBBYEFNv0bmPu4ty+vzhgT5gx0GRE8WPYMAwGA1UdEwEB/wQCMAAwIQYDVR0gBBowGDAMBgorBgEEAdZ5AgUBMAgGBmeBDAECAjAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAOpm95fThLYPDBdpxOkvUkzhI0cpSVjc8cDNZ4a+5mK1A2Inq+/yLH3ZMsQIMvoDcpj7uYIr+Oxmy0i4/pHg+9it/f9cmqeawA5sqmGnSOZ/lfCYI8+bRbMIULrijCuJwjfGpZZsqOvSBuIOSzRvgGVplcs0dituT2khCFrkblwa/BqIqztvP7LuEmVpjkqt4pC3HvD0XUxs5PIdZZGInfeqymk5feReWHBuPHpPIUObKxmQt+hcw6YsHE+0B84Xtx9BMe4qqUfrqmtWXn9unBwxqSYsCqxHQpQ+70pmuBxlB9s6LStIzE9syaDmUyjxRljKAwINV6z0j7hKQ6MPpE\"" // followed by comma then chunk2 := "\"MIIDnTCCAoWgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEhMB8GA1UEAwwYRmFrZUNlcnRpZmljYXRlQXV0aG9yaXR5MB4XDTE2MDUxMzE0MjY0NFoXDTE5MDcxMjE0MjY0NFowcjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqkDHpt6SYi1GcZyClAxr3LRDnn+oQBHbMEFUg3+lXVmEsq/xQO1s4naynV6I05676XvlMh0qPyJ+9GaBxvhHeFtGh4etQ9UEmJj55rSs50wA/IaDh+roKukQxthyTESPPgjqg+DPjh6H+h3Sn00Os6sjh3DxpOphTEsdtb7fmk8J0e2KjQQCjW/GlECzc359b9KbBwNkcAiYFayVHPLaCAdvzYVyiHgXHkEEs5FlHyhe2gNEG/81Io8c3E3DH5JhT9tmVRL3bpgpT8Kr4aoFhU2LXe45YIB1A9DjUm5TrHZ+iNtvE0YfYMR9L9C1HPppmX1CahEhTdog7laE1198UCAwEAAaM4MDYwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8ECDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwDQYJKoZIhvcNAQELBQADggEBAAHiOgwAvEzhrNMQVAz8a+SsyMIABXQ5P8WbJeHjkIipE4+5ZpkrZVXq9p8wOdkYnOHx4WNi9PVGQbLG9Iufh9fpk8cyyRWDi+V20/CNNtawMq3ClV3dWC98Tj4WX/BXDCeY2jK4jYGV+ds43HYV0ToBmvvrccq/U7zYMGFcQiKBClz5bTE+GMvrZWcO5A/Lh38i2YSF1i8SfDVnAOBlAgZmllcheHpGsWfSnduIllUvTsRvEIsaaqfVLl5QpRXBOq8tbjK85/2g6ear1oxPhJ1w9hds+WTFXkmHkWvKJebY13t3OfSjAyhaRSt8hdzDzHTFwjPjHT8h6dU7/hMdkUg=\"" epilog := "]}\n" // Which (if successful) produces a QueueLeaf response with a Merkle leaf: pool := loadCertsIntoPoolOrDie(t, pemChain) merkleLeaf, err := ct.MerkleTreeLeafFromChain(pool.RawCertificates(), ct.X509LogEntryType, fakeTimeMillis) if err != nil { t.Fatalf("Unexpected error signing SCT: %v", err) } // The generated LogLeaf will include the root cert as well. fullChain := make([]*x509.Certificate, len(pemChain)+1) copy(fullChain, pool.RawCertificates()) fullChain[len(pemChain)] = info.roots.RawCertificates()[0] leaf := logLeafForCert(t, fullChain, merkleLeaf, false) queuedLeaf := &trillian.QueuedLogLeaf{ Leaf: leaf, Status: status.New(codes.OK, "ok").Proto(), } rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf} req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf} var tests = []struct { descr string body string want int }{ { descr: "valid", body: intro + ":" + chunk1a + chunk1b + "," + chunk2 + epilog, want: http.StatusOK, }, { descr: "valid-space-between", body: intro + " : " + chunk1a + chunk1b + " , " + chunk2 + epilog, want: http.StatusOK, }, { descr: "valid-newline-between", body: intro + " : " + chunk1a + chunk1b + ",\n" + chunk2 + epilog, want: http.StatusOK, }, { descr: "invalid-raw-newline-in-string", body: intro + ":" + chunk1a + "\n" + chunk1b + "," + chunk2 + epilog, want: http.StatusBadRequest, }, { descr: "valid-escaped-newline-in-string", body: intro + ":" + chunk1a + "\\n" + chunk1b + "," + chunk2 + epilog, want: http.StatusOK, }, } for _, test := range tests { t.Run(test.descr, func(t *testing.T) { if test.want == http.StatusOK { info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, nil) } recorder := httptest.NewRecorder() handler := AppHandler{Info: info.li, Handler: addChain, Name: "AddChain", Method: http.MethodPost} req, err := http.NewRequest(http.MethodPost, "http://example.com/ct/v1/add-chain", strings.NewReader(test.body)) if err != nil { t.Fatalf("Failed to create POST request: %v", err) } handler.ServeHTTP(recorder, req) if recorder.Code != test.want { t.Fatalf("addChain()=%d (body:%v); want %dv", recorder.Code, recorder.Body, test.want) } }) } } func TestAddChain(t *testing.T) { var tests = []struct { descr string chain []string toSign string // hex-encoded want int err error remoteQuotaUser string enableCertQuota bool // if remote quota enabled, it must be the first entry here wantQuotaUsers []string }{ { descr: "leaf-only", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM}, want: http.StatusBadRequest, }, { descr: "wrong-entry-type", chain: []string{cttestonly.PrecertPEMValid}, want: http.StatusBadRequest, }, { descr: "backend-rpc-fail", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", want: http.StatusInternalServerError, err: status.Errorf(codes.Internal, "error"), }, { descr: "success-without-root", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", want: http.StatusOK, }, { descr: "success", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", want: http.StatusOK, }, { descr: "success-without-root with remote quota", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", remoteQuotaUser: remoteQuotaUser, want: http.StatusOK, wantQuotaUsers: []string{remoteQuotaUser}, }, { descr: "success with remote quota", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", remoteQuotaUser: remoteQuotaUser, want: http.StatusOK, wantQuotaUsers: []string{remoteQuotaUser}, }, { descr: "success with chain quota", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", enableCertQuota: true, want: http.StatusOK, wantQuotaUsers: quotaUsersForIssuers(t, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM), }, { descr: "success with remote and chain quota", chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM}, toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d", remoteQuotaUser: remoteQuotaUser, enableCertQuota: true, want: http.StatusOK, wantQuotaUsers: append([]string{remoteQuotaUser}, quotaUsersForIssuers(t, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM)...), }, } signer, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("Failed to create test signer: %v", err) } info := setupTest(t, []string{cttestonly.FakeCACertPEM}, signer) defer info.mockCtrl.Finish() for _, test := range tests { t.Run(test.descr, func(t *testing.T) { info.setRemoteQuotaUser(test.remoteQuotaUser) info.enableCertQuota(test.enableCertQuota) pool := loadCertsIntoPoolOrDie(t, test.chain) chain := createJSONChain(t, *pool) if len(test.toSign) > 0 { root := info.roots.RawCertificates()[0] merkleLeaf, err := ct.MerkleTreeLeafFromChain(pool.RawCertificates(), ct.X509LogEntryType, fakeTimeMillis) if err != nil { t.Fatalf("Unexpected error signing SCT: %v", err) } leafChain := pool.RawCertificates() if !leafChain[len(leafChain)-1].Equal(root) { // The submitted chain may not include a root, but the generated LogLeaf will fullChain := make([]*x509.Certificate, len(leafChain)+1) copy(fullChain, leafChain) fullChain[len(leafChain)] = root leafChain = fullChain } leaf := logLeafForCert(t, leafChain, merkleLeaf, false) queuedLeaf := &trillian.QueuedLogLeaf{ Leaf: leaf, Status: status.New(codes.OK, "ok").Proto(), } rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf} req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf} if len(test.wantQuotaUsers) > 0 { req.ChargeTo = &trillian.ChargeTo{User: test.wantQuotaUsers} } info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, test.err) } recorder := makeAddChainRequest(t, info.li, chain) if recorder.Code != test.want { t.Fatalf("addChain()=%d (body:%v); want %dv", recorder.Code, recorder.Body, test.want) } if test.want == http.StatusOK { var resp ct.AddChainResponse if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil { t.Fatalf("json.Decode(%s)=%v; want nil", recorder.Body.Bytes(), err) } if got, want := ct.Version(resp.SCTVersion), ct.V1; got != want { t.Errorf("resp.SCTVersion=%v; want %v", got, want) } if got, want := resp.ID, demoLogID[:]; !bytes.Equal(got, want) { t.Errorf("resp.ID=%v; want %v", got, want) } if got, want := resp.Timestamp, uint64(1469185273000); got != want { t.Errorf("resp.Timestamp=%d; want %d", got, want) } if got, want := hex.EncodeToString(resp.Signature), "040300067369676e6564"; got != want { t.Errorf("resp.Signature=%s; want %s", got, want) } } }) } } func TestAddPrechain(t *testing.T) { var tests = []struct { descr string chain []string root string toSign string // hex-encoded err error want int wantQuotaUser string }{ { descr: "leaf-signed-by-different", chain: []string{cttestonly.PrecertPEMValid, cttestonly.FakeIntermediateCertPEM}, want: http.StatusBadRequest, }, { descr: "wrong-entry-type", chain: []string{cttestonly.TestCertPEM}, want: http.StatusBadRequest, }, { descr: "backend-rpc-fail", chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM}, toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a", err: status.Errorf(codes.Internal, "error"), want: http.StatusInternalServerError, }, { descr: "success", chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM}, toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a", want: http.StatusOK, }, { descr: "success with quota", chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM}, toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a", want: http.StatusOK, wantQuotaUser: remoteQuotaUser, }, { descr: "success-without-root", chain: []string{cttestonly.PrecertPEMValid}, toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a", want: http.StatusOK, }, { descr: "success-without-root with quota", chain: []string{cttestonly.PrecertPEMValid}, toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a", want: http.StatusOK, wantQuotaUser: remoteQuotaUser, }, } signer, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("Failed to create test signer: %v", err) } info := setupTest(t, []string{cttestonly.CACertPEM}, signer) defer info.mockCtrl.Finish() for _, test := range tests { t.Run(test.descr, func(t *testing.T) { info.setRemoteQuotaUser(test.wantQuotaUser) pool := loadCertsIntoPoolOrDie(t, test.chain) chain := createJSONChain(t, *pool) if len(test.toSign) > 0 { root := info.roots.RawCertificates()[0] merkleLeaf, err := ct.MerkleTreeLeafFromChain([]*x509.Certificate{pool.RawCertificates()[0], root}, ct.PrecertLogEntryType, fakeTimeMillis) if err != nil { t.Fatalf("Unexpected error signing SCT: %v", err) } leafChain := pool.RawCertificates() if !leafChain[len(leafChain)-1].Equal(root) { // The submitted chain may not include a root, but the generated LogLeaf will fullChain := make([]*x509.Certificate, len(leafChain)+1) copy(fullChain, leafChain) fullChain[len(leafChain)] = root leafChain = fullChain } leaf := logLeafForCert(t, leafChain, merkleLeaf, true) queuedLeaf := &trillian.QueuedLogLeaf{ Leaf: leaf, Status: status.New(codes.OK, "ok").Proto(), } rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf} req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf} if len(test.wantQuotaUser) != 0 { req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, test.err) } recorder := makeAddPrechainRequest(t, info.li, chain) if recorder.Code != test.want { t.Fatalf("addPrechain()=%d (body:%v); want %d", recorder.Code, recorder.Body, test.want) } if test.want == http.StatusOK { var resp ct.AddChainResponse if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil { t.Fatalf("json.Decode(%s)=%v; want nil", recorder.Body.Bytes(), err) } if got, want := ct.Version(resp.SCTVersion), ct.V1; got != want { t.Errorf("resp.SCTVersion=%v; want %v", got, want) } if got, want := resp.ID, demoLogID[:]; !bytes.Equal(got, want) { t.Errorf("resp.ID=%x; want %x", got, want) } if got, want := resp.Timestamp, uint64(1469185273000); got != want { t.Errorf("resp.Timestamp=%d; want %d", got, want) } if got, want := hex.EncodeToString(resp.Signature), "040300067369676e6564"; got != want { t.Errorf("resp.Signature=%s; want %s", got, want) } } }) } } func TestGetSTH(t *testing.T) { var tests = []struct { descr string rpcRsp *trillian.GetLatestSignedLogRootResponse rpcErr error toSign string // hex-encoded signErr error want int wantQuotaUser string errStr string }{ { descr: "backend-failure", rpcErr: errors.New("backendfailure"), want: http.StatusInternalServerError, errStr: "backendfailure", }, { descr: "backend-unimplemented", rpcErr: status.Errorf(codes.Unimplemented, "no-such-thing"), want: http.StatusNotImplemented, errStr: "no-such-thing", }, { descr: "bad-hash", rpcRsp: makeGetRootResponseForTest(t, 12345, 25, []byte("thisisnot32byteslong")), want: http.StatusInternalServerError, errStr: "bad hash size", }, { descr: "signer-fail", rpcRsp: makeGetRootResponseForTest(t, 12345, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")), want: http.StatusInternalServerError, signErr: errors.New("signerfails"), errStr: "signerfails", }, { descr: "ok", rpcRsp: makeGetRootResponseForTest(t, 12345000000, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")), toSign: "1e88546f5157bfaf77ca2454690b602631fedae925bbe7cf708ea275975bfe74", want: http.StatusOK, }, { descr: "ok with quota", rpcRsp: makeGetRootResponseForTest(t, 12345000000, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")), toSign: "1e88546f5157bfaf77ca2454690b602631fedae925bbe7cf708ea275975bfe74", want: http.StatusOK, wantQuotaUser: remoteQuotaUser, }, } block, _ := pem.Decode([]byte(testdata.DemoPublicKey)) key, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatalf("Failed to load public key: %v", err) } for _, test := range tests { // Run deferred funcs at the end of each iteration. func() { var signer crypto.Signer if test.signErr != nil { signer = testdata.NewSignerWithErr(key, test.signErr) } else { signer = testdata.NewSignerWithFixedSig(key, fakeSignature) } info := setupTest(t, []string{cttestonly.CACertPEM}, signer) info.setRemoteQuotaUser(test.wantQuotaUser) defer info.mockCtrl.Finish() srReq := &trillian.GetLatestSignedLogRootRequest{LogId: 0x42} if len(test.wantQuotaUser) != 0 { srReq.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } info.client.EXPECT().GetLatestSignedLogRoot(deadlineMatcher(), cmpMatcher{srReq}).Return(test.rpcRsp, test.rpcErr) req, err := http.NewRequest(http.MethodGet, "http://example.com/ct/v1/get-sth", nil) if err != nil { t.Errorf("Failed to create request: %v", err) return } handler := AppHandler{Info: info.li, Handler: getSTH, Name: "GetSTH", Method: http.MethodGet} w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("GetSTH(%s).Code=%d; want %d", test.descr, got, test.want) } if test.errStr != "" { if body := w.Body.String(); !strings.Contains(body, test.errStr) { t.Errorf("GetSTH(%s)=%q; want to find %q", test.descr, body, test.errStr) } return } var rsp ct.GetSTHResponse if err := json.Unmarshal(w.Body.Bytes(), &rsp); err != nil { t.Errorf("Failed to unmarshal json response: %s", w.Body.Bytes()) return } if got, want := rsp.TreeSize, uint64(25); got != want { t.Errorf("GetSTH(%s).TreeSize=%d; want %d", test.descr, got, want) } if got, want := rsp.Timestamp, uint64(12345); got != want { t.Errorf("GetSTH(%s).Timestamp=%d; want %d", test.descr, got, want) } if got, want := hex.EncodeToString(rsp.SHA256RootHash), "6162636461626364616263646162636461626364616263646162636461626364"; got != want { t.Errorf("GetSTH(%s).SHA256RootHash=%s; want %s", test.descr, got, want) } if got, want := hex.EncodeToString(rsp.TreeHeadSignature), "040300067369676e6564"; got != want { t.Errorf("GetSTH(%s).TreeHeadSignature=%s; want %s", test.descr, got, want) } }() } } func TestGetEntries(t *testing.T) { // Create a couple of valid serialized ct.MerkleTreeLeaf objects merkleLeaf1 := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: 12345, EntryType: ct.X509LogEntryType, X509Entry: &ct.ASN1Cert{Data: []byte("certdatacertdata")}, Extensions: ct.CTExtensions{}, }, } merkleLeaf2 := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: 67890, EntryType: ct.X509LogEntryType, X509Entry: &ct.ASN1Cert{Data: []byte("certdat2certdat2")}, Extensions: ct.CTExtensions{}, }, } merkleBytes1, err1 := tls.Marshal(merkleLeaf1) merkleBytes2, err2 := tls.Marshal(merkleLeaf2) if err1 != nil || err2 != nil { t.Fatalf("failed to tls.Marshal() test data for get-entries: %v %v", err1, err2) } var tests = []struct { descr string req string want int wantQuotaUser string glbrr *trillian.GetLeavesByRangeRequest leaves []*trillian.LogLeaf rpcErr error slr *trillian.SignedLogRoot errStr string }{ { descr: "invalid &&s", req: "start=&&&&&&&&&end=wibble", want: http.StatusBadRequest, }, { descr: "start non numeric", req: "start=fish&end=3", want: http.StatusBadRequest, }, { descr: "end non numeric", req: "start=10&end=wibble", want: http.StatusBadRequest, }, { descr: "both non numeric", req: "start=fish&end=wibble", want: http.StatusBadRequest, }, { descr: "end missing", req: "start=1", want: http.StatusBadRequest, }, { descr: "start missing", req: "end=1", want: http.StatusBadRequest, }, { descr: "both missing", req: "", want: http.StatusBadRequest, }, { descr: "backend rpc error", req: "start=1&end=2", want: http.StatusInternalServerError, rpcErr: errors.New("bang"), errStr: "bang", }, { descr: "invalid log root", req: "start=2&end=3", slr: &trillian.SignedLogRoot{ LogRoot: []byte("not tls encoded data"), }, glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 2, Count: 2}, want: http.StatusInternalServerError, leaves: []*trillian.LogLeaf{{LeafIndex: 2}, {LeafIndex: 3}}, errStr: "failed to unmarshal", }, { descr: "start outside tree size", req: "start=2&end=3", slr: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 2, // Not large enough - only indices 0 and 1 valid. }), glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 2, Count: 2}, want: http.StatusBadRequest, leaves: []*trillian.LogLeaf{{LeafIndex: 2}, {LeafIndex: 3}}, errStr: "need tree size: 3 to get leaves but only got: 2", }, { descr: "backend extra leaves", req: "start=1&end=2", slr: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 2, }), want: http.StatusInternalServerError, leaves: []*trillian.LogLeaf{{LeafIndex: 1}, {LeafIndex: 2}, {LeafIndex: 3}}, errStr: "too many leaves", }, { descr: "backend non-contiguous range", req: "start=1&end=2", slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}), want: http.StatusInternalServerError, leaves: []*trillian.LogLeaf{{LeafIndex: 1}, {LeafIndex: 3}}, errStr: "unexpected leaf index", }, { descr: "backend leaf corrupt", req: "start=1&end=2", slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}), want: http.StatusOK, leaves: []*trillian.LogLeaf{ {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: []byte("NOT A MERKLE TREE LEAF")}, {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: []byte("NOT A MERKLE TREE LEAF")}, }, }, { descr: "leaves ok", req: "start=1&end=2", slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}), want: http.StatusOK, leaves: []*trillian.LogLeaf{ {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes1, ExtraData: []byte("extra1")}, {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes2, ExtraData: []byte("extra2")}, }, }, { descr: "leaves ok with quota", req: "start=1&end=2", slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}), want: http.StatusOK, wantQuotaUser: remoteQuotaUser, leaves: []*trillian.LogLeaf{ {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes1, ExtraData: []byte("extra1")}, {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes2, ExtraData: []byte("extra2")}, }, }, { descr: "tree too small", req: "start=5&end=6", glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2}, want: http.StatusBadRequest, slr: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 5, }), leaves: []*trillian.LogLeaf{}, }, { descr: "tree includes 1 of 2", req: "start=5&end=6", glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2}, want: http.StatusOK, slr: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 6, }), leaves: []*trillian.LogLeaf{ {LeafIndex: 5, MerkleLeafHash: []byte("hash5"), LeafValue: merkleBytes1, ExtraData: []byte("extra5")}, }, }, { descr: "tree includes 2 of 2", req: "start=5&end=6", glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2}, want: http.StatusOK, slr: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 7, }), leaves: []*trillian.LogLeaf{ {LeafIndex: 5, MerkleLeafHash: []byte("hash5"), LeafValue: merkleBytes1, ExtraData: []byte("extra5")}, {LeafIndex: 6, MerkleLeafHash: []byte("hash6"), LeafValue: merkleBytes1, ExtraData: []byte("extra6")}, }, }, } for _, test := range tests { info := setupTest(t, nil, nil) info.setRemoteQuotaUser(test.wantQuotaUser) handler := AppHandler{Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet} path := fmt.Sprintf("/ct/v1/get-entries?%s", test.req) req, err := http.NewRequest(http.MethodGet, path, nil) if err != nil { t.Errorf("Failed to create request: %v", err) continue } slr := test.slr if slr == nil { slr = mustMarshalRoot(t, &types.LogRootV1{}) } if test.leaves != nil || test.rpcErr != nil { var chargeTo *trillian.ChargeTo if len(test.wantQuotaUser) != 0 { chargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } glbrr := &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 1, Count: 2, ChargeTo: chargeTo} if test.glbrr != nil { glbrr = test.glbrr } rsp := trillian.GetLeavesByRangeResponse{SignedLogRoot: slr, Leaves: test.leaves} info.client.EXPECT().GetLeavesByRange(deadlineMatcher(), cmpMatcher{glbrr}).Return(&rsp, test.rpcErr) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("GetEntries(%q)=%d; want %d (because %s)", test.req, got, test.want, test.descr) } if test.errStr != "" { if body := w.Body.String(); !strings.Contains(body, test.errStr) { t.Errorf("GetEntries(%q)=%q; want to find %q (because %s)", test.req, body, test.errStr, test.descr) } continue } if test.want != http.StatusOK { continue } if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) { t.Errorf("GetEntries(%q): Cache-Control response header = %q, want %q", test.req, got, want) } // Leaf data should be passed through as-is even if invalid. var jsonMap map[string][]ct.LeafEntry if err := json.Unmarshal(w.Body.Bytes(), &jsonMap); err != nil { t.Errorf("Failed to unmarshal json response %s: %v", w.Body.Bytes(), err) continue } if got := len(jsonMap); got != 1 { t.Errorf("len(rspMap)=%d; want 1", got) } entries := jsonMap["entries"] if got, want := len(entries), len(test.leaves); got != want { t.Errorf("len(rspMap['entries']=%d; want %d", got, want) continue } for i := 0; i < len(entries); i++ { if got, want := string(entries[i].LeafInput), string(test.leaves[i].LeafValue); got != want { t.Errorf("rspMap['entries'][%d].LeafInput=%s; want %s", i, got, want) } if got, want := string(entries[i].ExtraData), string(test.leaves[i].ExtraData); got != want { t.Errorf("rspMap['entries'][%d].ExtraData=%s; want %s", i, got, want) } } info.mockCtrl.Finish() } } func TestGetEntriesRanges(t *testing.T) { var tests = []struct { desc string start int64 end int64 rpcEnd int64 // same as end if zero want int wantQuotaUser string rpc bool }{ { desc: "-ve start value not allowed", start: -1, end: 0, want: http.StatusBadRequest, }, { desc: "-ve end value not allowed", start: 0, end: -1, want: http.StatusBadRequest, }, { desc: "invalid range end>start", start: 20, end: 10, want: http.StatusBadRequest, }, { desc: "invalid range, -ve end", start: 3000, end: -50, want: http.StatusBadRequest, }, { desc: "valid range", start: 10, end: 20, want: http.StatusInternalServerError, rpc: true, }, { desc: "valid range quota", start: 10, end: 20, want: http.StatusInternalServerError, wantQuotaUser: remoteQuotaUser, rpc: true, }, { desc: "valid range, one entry", start: 10, end: 10, want: http.StatusInternalServerError, rpc: true, }, { desc: "invalid range, edge case", start: 10, end: 9, want: http.StatusBadRequest, }, { desc: "range too large, coerced into alignment", start: 14, end: 50000, want: http.StatusInternalServerError, rpcEnd: MaxGetEntriesAllowed - 1, rpc: true, }, { desc: "range too large, already in alignment", start: MaxGetEntriesAllowed, end: 5000, want: http.StatusInternalServerError, rpcEnd: MaxGetEntriesAllowed + MaxGetEntriesAllowed - 1, rpc: true, }, { desc: "small range straddling boundary, not coerced", start: MaxGetEntriesAllowed - 2, end: MaxGetEntriesAllowed + 2, want: http.StatusInternalServerError, rpcEnd: MaxGetEntriesAllowed + 2, rpc: true, }, } // This tests that only valid ranges make it to the backend for get-entries. // We're testing request handling up to the point where we make the RPC so arrange for // it to fail with a specific error. for _, test := range tests { t.Run(test.desc, func(t *testing.T) { info := setupTest(t, nil, nil) defer info.mockCtrl.Finish() handler := AppHandler{Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet} info.setRemoteQuotaUser(test.wantQuotaUser) if test.rpc { end := test.rpcEnd if end == 0 { end = test.end } var chargeTo *trillian.ChargeTo if len(test.wantQuotaUser) != 0 { chargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } info.client.EXPECT().GetLeavesByRange(deadlineMatcher(), cmpMatcher{&trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: test.start, Count: end + 1 - test.start, ChargeTo: chargeTo}}).Return(nil, errors.New("RPCMADE")) } path := fmt.Sprintf("/ct/v1/get-entries?start=%d&end=%d", test.start, test.end) req, err := http.NewRequest(http.MethodGet, path, nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("getEntries(%d, %d)=%d; want %d for test %s", test.start, test.end, got, test.want, test.desc) } if test.rpc && !strings.Contains(w.Body.String(), "RPCMADE") { // If an RPC was emitted, it should have received and propagated an error. t.Errorf("getEntries(%d, %d)=%q; expect RPCMADE for test %s", test.start, test.end, w.Body, test.desc) } }) } } func TestGetProofByHash(t *testing.T) { auditHashes := [][]byte{ []byte("abcdef78901234567890123456789012"), []byte("ghijkl78901234567890123456789012"), []byte("mnopqr78901234567890123456789012"), } inclusionProof := ct.GetProofByHashResponse{ LeafIndex: 2, AuditPath: auditHashes, } var tests = []struct { req string want int wantQuotaUser string rpcRsp *trillian.GetInclusionProofByHashResponse httpRsp *ct.GetProofByHashResponse httpJSON string rpcErr error errStr string }{ { req: "", want: http.StatusBadRequest, }, { req: "hash=&tree_size=1", want: http.StatusBadRequest, }, { req: "hash=''&tree_size=1", want: http.StatusBadRequest, }, { req: "hash=notbase64data&tree_size=1", want: http.StatusBadRequest, }, { req: "tree_size=-1&hash=aGkK", want: http.StatusBadRequest, }, { req: "tree_size=6&hash=YWhhc2g=", want: http.StatusInternalServerError, rpcErr: errors.New("RPCFAIL"), errStr: "RPCFAIL", }, { req: "tree_size=11&hash=YWhhc2g=", want: http.StatusNotFound, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, // Not large enough to handle the request. }), Proof: []*trillian.Proof{ { LeafIndex: 0, Hashes: nil, }, }, }, }, { req: "tree_size=11&hash=YWhhc2g=", want: http.StatusInternalServerError, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: &trillian.SignedLogRoot{ LogRoot: []byte("not tls encoded data"), }, }, }, { req: "tree_size=1&hash=YWhhc2g=", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 0, Hashes: nil, }, }, }, httpRsp: &ct.GetProofByHashResponse{LeafIndex: 0, AuditPath: nil}, // Check undecoded JSON to confirm use of '[]' not 'null' httpJSON: "{\"leaf_index\":0,\"audit_path\":[]}", }, { req: "tree_size=1&hash=YWhhc2g=", want: http.StatusOK, // Want quota wantQuotaUser: remoteQuotaUser, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 0, Hashes: nil, }, }, }, httpRsp: &ct.GetProofByHashResponse{LeafIndex: 0, AuditPath: nil}, // Check undecoded JSON to confirm use of '[]' not 'null' httpJSON: "{\"leaf_index\":0,\"audit_path\":[]}", }, { req: "tree_size=7&hash=YWhhc2g=", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: auditHashes, }, // Second proof ignored. { LeafIndex: 2, Hashes: [][]byte{[]byte("ghijkl")}, }, }, }, httpRsp: &inclusionProof, }, { req: "tree_size=9&hash=YWhhc2g=", want: http.StatusInternalServerError, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: [][]byte{ auditHashes[0], {}, // missing hash auditHashes[2], }, }, }, }, errStr: "invalid proof", }, { req: "tree_size=7&hash=YWhhc2g=", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: auditHashes, }, }, }, httpRsp: &inclusionProof, }, { // Hash with URL-encoded %2B -> '+'. req: "hash=WtfX3Axbm7UwtY7GhHoAHPCtXJVrY5vZsH%2ByaXOD2GI=&tree_size=1", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: auditHashes, }, }, }, httpRsp: &inclusionProof, }, { req: "tree_size=10&hash=YWhhc2g=", want: http.StatusNotFound, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 5, }), Proof: []*trillian.Proof{ { LeafIndex: 0, Hashes: nil, }, }, }, }, { req: "tree_size=10&hash=YWhhc2g=", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Returned tree large enough to include the leaf. TreeSize: 10, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: auditHashes, }, }, }, httpRsp: &inclusionProof, }, { req: "tree_size=10&hash=YWhhc2g=", want: http.StatusOK, rpcRsp: &trillian.GetInclusionProofByHashResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Returned tree larger than needed to include the leaf. TreeSize: 20, }), Proof: []*trillian.Proof{ { LeafIndex: 2, Hashes: auditHashes, }, }, }, httpRsp: &inclusionProof, }, } info := setupTest(t, nil, nil) defer info.mockCtrl.Finish() handler := AppHandler{Info: info.li, Handler: getProofByHash, Name: "GetProofByHash", Method: http.MethodGet} for _, test := range tests { info.setRemoteQuotaUser(test.wantQuotaUser) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/ct/v1/proof-by-hash?%s", test.req), nil) if err != nil { t.Errorf("Failed to create request: %v", err) continue } if test.rpcRsp != nil || test.rpcErr != nil { info.client.EXPECT().GetInclusionProofByHash(deadlineMatcher(), gomock.Any()).Return(test.rpcRsp, test.rpcErr) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("proofByHash(%s)=%d; want %d", test.req, got, test.want) } if test.errStr != "" { if body := w.Body.String(); !strings.Contains(body, test.errStr) { t.Errorf("proofByHash(%q)=%q; want to find %q", test.req, body, test.errStr) } continue } if test.want != http.StatusOK { continue } if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) { t.Errorf("proofByHash(%q): Cache-Control response header = %q, want %q", test.req, got, want) } jsonData, err := io.ReadAll(w.Body) if err != nil { t.Errorf("failed to read response body: %v", err) continue } var resp ct.GetProofByHashResponse if err = json.Unmarshal(jsonData, &resp); err != nil { t.Errorf("Failed to unmarshal json response %s: %v", jsonData, err) continue } if diff := pretty.Compare(resp, test.httpRsp); diff != "" { t.Errorf("proofByHash(%q) diff:\n%v", test.req, diff) } if test.httpJSON != "" { // Also check the JSON string is as expected if diff := pretty.Compare(string(jsonData), test.httpJSON); diff != "" { t.Errorf("proofByHash(%q) diff:\n%v", test.req, diff) } } } } func TestGetSTHConsistency(t *testing.T) { auditHashes := [][]byte{ []byte("abcdef78901234567890123456789012"), []byte("ghijkl78901234567890123456789012"), []byte("mnopqr78901234567890123456789012"), } var tests = []struct { req string want int wantQuotaUser string first, second int64 rpcRsp *trillian.GetConsistencyProofResponse httpRsp *ct.GetSTHConsistencyResponse httpJSON string rpcErr error errStr string }{ { req: "", want: http.StatusBadRequest, errStr: "parameter 'first' is required", }, { req: "first=apple&second=orange", want: http.StatusBadRequest, errStr: "parameter 'first' is malformed", }, { req: "first=1&last=2", want: http.StatusBadRequest, errStr: "parameter 'second' is required", }, { req: "first=1&second=a", want: http.StatusBadRequest, errStr: "parameter 'second' is malformed", }, { req: "first=a&second=2", want: http.StatusBadRequest, errStr: "parameter 'first' is malformed", }, { req: "first=-1&second=10", want: http.StatusBadRequest, errStr: "first and second params cannot be <0: -1 10", }, { req: "first=10&second=-11", want: http.StatusBadRequest, errStr: "first and second params cannot be <0: 10 -11", }, { req: "first=0&second=1", want: http.StatusOK, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: nil, }, // Check a nil proof is passed through as '[]' not 'null' in raw JSON. httpJSON: "{\"consistency\":[]}", }, { req: "first=0&second=1", want: http.StatusOK, // Want quota wantQuotaUser: remoteQuotaUser, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: nil, }, // Check a nil proof is passed through as '[]' not 'null' in raw JSON. httpJSON: "{\"consistency\":[]}", }, { // Check that unrecognized parameters are ignored. req: "first=0&second=1&third=2&fourth=3", want: http.StatusOK, httpRsp: &ct.GetSTHConsistencyResponse{}, }, { req: "first=998&second=997", want: http.StatusBadRequest, errStr: "invalid first, second params: 998 997", }, { req: "first=1000&second=200", want: http.StatusBadRequest, errStr: "invalid first, second params: 1000 200", }, { req: "first=10", want: http.StatusBadRequest, errStr: "parameter 'second' is required", }, { req: "second=20", want: http.StatusBadRequest, errStr: "parameter 'first' is required", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusInternalServerError, rpcErr: errors.New("RPCFAIL"), errStr: "RPCFAIL", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusInternalServerError, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 50, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ auditHashes[0], {}, // missing hash auditHashes[2], }, }, }, errStr: "invalid proof", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusInternalServerError, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: &trillian.SignedLogRoot{ LogRoot: []byte("not tls encoded data"), }, }, errStr: "failed to unmarshal", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusBadRequest, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 19, // Tree not large enough to serve the request. }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ auditHashes[0], {}, // missing hash auditHashes[2], }, }, }, errStr: "need tree size: 20", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusInternalServerError, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 50, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ auditHashes[0], auditHashes[1][:30], // wrong size hash auditHashes[2], }, }, }, errStr: "invalid proof", }, { req: "first=10&second=20", first: 10, second: 20, want: http.StatusOK, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 50, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: auditHashes, }, }, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: auditHashes, }, }, { req: "first=1&second=2", first: 1, second: 2, want: http.StatusOK, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 50, }), Proof: &trillian.Proof{ LeafIndex: 0, Hashes: nil, }, }, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: nil, }, // Check a nil proof is passed through as '[]' not 'null' in raw JSON. httpJSON: "{\"consistency\":[]}", }, { req: "first=332&second=332", first: 332, second: 332, want: http.StatusOK, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TreeSize: 333, }), Proof: &trillian.Proof{ LeafIndex: 0, Hashes: nil, }, }, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: nil, }, // Check a nil proof is passed through as '[]' not 'null' in raw JSON. httpJSON: "{\"consistency\":[]}", }, { req: "first=332&second=332", first: 332, second: 332, want: http.StatusBadRequest, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Backend returns a tree size too small to satisfy the proof. TreeSize: 331, }), Proof: &trillian.Proof{ Hashes: nil, }, }, }, { req: "first=332&second=332", first: 332, second: 332, want: http.StatusOK, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Backend returns a tree size just large enough to satisfy the proof. TreeSize: 332, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: auditHashes, }, }, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: auditHashes, }, }, { req: "first=332&second=332", first: 332, second: 332, want: http.StatusOK, rpcRsp: &trillian.GetConsistencyProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Backend returns a tree size larger than needed to satisfy the proof. TreeSize: 333, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: auditHashes, }, }, httpRsp: &ct.GetSTHConsistencyResponse{ Consistency: auditHashes, }, }, { req: "first=332&second=331", want: http.StatusBadRequest, }, } info := setupTest(t, nil, nil) defer info.mockCtrl.Finish() handler := AppHandler{Info: info.li, Handler: getSTHConsistency, Name: "GetSTHConsistency", Method: http.MethodGet} for _, test := range tests { info.setRemoteQuotaUser(test.wantQuotaUser) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/ct/v1/get-sth-consistency?%s", test.req), nil) if err != nil { t.Errorf("Failed to create request: %v", err) continue } if test.rpcRsp != nil || test.rpcErr != nil { req := trillian.GetConsistencyProofRequest{ LogId: 0x42, FirstTreeSize: test.first, SecondTreeSize: test.second, } if len(test.wantQuotaUser) > 0 { req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } info.client.EXPECT().GetConsistencyProof(deadlineMatcher(), cmpMatcher{&req}).Return(test.rpcRsp, test.rpcErr) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("getSTHConsistency(%s)=%d; want %d", test.req, got, test.want) } if test.errStr != "" { if body := w.Body.String(); !strings.Contains(body, test.errStr) { t.Errorf("getSTHConsistency(%q)=%q; want to find %q", test.req, body, test.errStr) } continue } if test.want != http.StatusOK { continue } if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) { t.Errorf("getSTHConsistency(%q): Cache-Control response header = %q, want %q", test.req, got, want) } jsonData, err := io.ReadAll(w.Body) if err != nil { t.Errorf("failed to read response body: %v", err) continue } var resp ct.GetSTHConsistencyResponse if err = json.Unmarshal(jsonData, &resp); err != nil { t.Errorf("Failed to unmarshal json response %s: %v", jsonData, err) continue } if diff := pretty.Compare(resp, test.httpRsp); diff != "" { t.Errorf("getSTHConsistency(%q) diff:\n%v", test.req, diff) } if test.httpJSON != "" { // Also check the JSON string is as expected if diff := pretty.Compare(string(jsonData), test.httpJSON); diff != "" { t.Errorf("getSTHConsistency(%q) diff:\n%v", test.req, diff) } } } } func TestGetEntryAndProof(t *testing.T) { merkleLeaf := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: 12345, EntryType: ct.X509LogEntryType, X509Entry: &ct.ASN1Cert{Data: []byte("certdatacertdata")}, Extensions: ct.CTExtensions{}, }, } leafBytes, err := tls.Marshal(merkleLeaf) if err != nil { t.Fatalf("failed to build test Merkle leaf data: %v", err) } proofRsp := ct.GetEntryAndProofResponse{ LeafInput: leafBytes, ExtraData: []byte("extra"), AuditPath: [][]byte{[]byte("abcdef"), []byte("ghijkl"), []byte("mnopqr")}, } var tests = []struct { req string idx, sz int64 want int wantQuotaUser string wantRsp *ct.GetEntryAndProofResponse rpcRsp *trillian.GetEntryAndProofResponse rpcErr error errStr string }{ { req: "", want: http.StatusBadRequest, }, { req: "leaf_index=b", want: http.StatusBadRequest, }, { req: "leaf_index=1&tree_size=-1", want: http.StatusBadRequest, }, { req: "leaf_index=-1&tree_size=1", want: http.StatusBadRequest, }, { req: "leaf_index=1&tree_size=d", want: http.StatusBadRequest, }, { req: "leaf_index=&tree_size=", want: http.StatusBadRequest, }, { req: "leaf_index=", want: http.StatusBadRequest, }, { req: "leaf_index=1&tree_size=0", want: http.StatusBadRequest, }, { req: "leaf_index=10&tree_size=5", want: http.StatusBadRequest, }, { req: "leaf_index=tree_size", want: http.StatusBadRequest, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusInternalServerError, rpcErr: errors.New("RPCFAIL"), errStr: "RPCFAIL", }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusInternalServerError, // No result data in backend response rpcRsp: &trillian.GetEntryAndProofResponse{}, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusOK, wantRsp: &proofRsp, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree not large enough for the proof. TreeSize: 20, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ []byte("abcdef"), []byte("ghijkl"), []byte("mnopqr"), }, }, // To match merkleLeaf above. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusOK, wantRsp: &proofRsp, // wantQuota wantQuotaUser: remoteQuotaUser, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree not large enough for the proof. TreeSize: 20, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ []byte("abcdef"), []byte("ghijkl"), []byte("mnopqr"), }, }, // To match merkleLeaf above. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusBadRequest, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree not large enough for the proof. TreeSize: 2, }), Proof: &trillian.Proof{}, }, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusOK, wantRsp: &proofRsp, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree just large enough for the proof. TreeSize: 3, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ []byte("abcdef"), []byte("ghijkl"), []byte("mnopqr"), }, }, // To match merkleLeaf above. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=1&tree_size=3", idx: 1, sz: 3, want: http.StatusOK, wantRsp: &proofRsp, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree larger than needed for the proof. TreeSize: 300, }), Proof: &trillian.Proof{ LeafIndex: 2, Hashes: [][]byte{ []byte("abcdef"), []byte("ghijkl"), []byte("mnopqr"), }, }, // To match merkleLeaf above. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=0&tree_size=1", idx: 0, sz: 1, want: http.StatusOK, wantRsp: &ct.GetEntryAndProofResponse{ LeafInput: leafBytes, ExtraData: []byte("extra"), }, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree larger than needed for the proof. TreeSize: 300, }), Proof: &trillian.Proof{ // Empty proof OK for requested tree size of 1. LeafIndex: 0, }, // To match merkleLeaf above. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=0&tree_size=1", idx: 0, sz: 1, want: http.StatusInternalServerError, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree larger than needed for the proof. TreeSize: 300, }), // No proof. Leaf: &trillian.LogLeaf{ LeafValue: leafBytes, MerkleLeafHash: []byte("ahash"), ExtraData: []byte("extra"), }, }, }, { req: "leaf_index=0&tree_size=1", idx: 0, sz: 1, want: http.StatusInternalServerError, rpcRsp: &trillian.GetEntryAndProofResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Server returns a tree larger than needed for the proof. TreeSize: 300, }), Proof: &trillian.Proof{ // Empty proof OK for requested tree size of 1. LeafIndex: 0, }, // No leaf. }, }, } info := setupTest(t, nil, nil) defer info.mockCtrl.Finish() handler := AppHandler{Info: info.li, Handler: getEntryAndProof, Name: "GetEntryAndProof", Method: http.MethodGet} for _, test := range tests { info.setRemoteQuotaUser(test.wantQuotaUser) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/ct/v1/get-entry-and-proof?%s", test.req), nil) if err != nil { t.Errorf("Failed to create request: %v", err) continue } if test.rpcRsp != nil || test.rpcErr != nil { req := &trillian.GetEntryAndProofRequest{LogId: 0x42, LeafIndex: test.idx, TreeSize: test.sz} if len(test.wantQuotaUser) > 0 { req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}} } info.client.EXPECT().GetEntryAndProof(deadlineMatcher(), cmpMatcher{req}).Return(test.rpcRsp, test.rpcErr) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) if got := w.Code; got != test.want { t.Errorf("getEntryAndProof(%s)=%d; want %d", test.req, got, test.want) } if test.errStr != "" { if body := w.Body.String(); !strings.Contains(body, test.errStr) { t.Errorf("getEntryAndProof(%q)=%q; want to find %q", test.req, body, test.errStr) } continue } if test.want != http.StatusOK { continue } if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) { t.Errorf("getEntryAndProof(%q): Cache-Control response header = %q, want %q", test.req, got, want) } var resp ct.GetEntryAndProofResponse if err = json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Errorf("Failed to unmarshal json response %s: %v", w.Body.Bytes(), err) continue } // The result we expect after a roundtrip in the successful get entry and proof test if diff := pretty.Compare(&resp, test.wantRsp); diff != "" { t.Errorf("getEntryAndProof(%q) diff:\n%v", test.req, diff) } } } func createJSONChain(t *testing.T, p x509util.PEMCertPool) io.Reader { t.Helper() var req ct.AddChainRequest for _, rawCert := range p.RawCertificates() { req.Chain = append(req.Chain, rawCert.Raw) } var buffer bytes.Buffer // It's tempting to avoid creating and flushing the intermediate writer but it doesn't work writer := bufio.NewWriter(&buffer) err := json.NewEncoder(writer).Encode(&req) if err := writer.Flush(); err != nil { t.Error(err) } if err != nil { t.Fatalf("Failed to create test json: %v", err) } return bufio.NewReader(&buffer) } func logLeafForCert(t *testing.T, certs []*x509.Certificate, merkleLeaf *ct.MerkleTreeLeaf, isPrecert bool) *trillian.LogLeaf { t.Helper() leafData, err := tls.Marshal(*merkleLeaf) if err != nil { t.Fatalf("failed to serialize leaf: %v", err) } raw := extractRawCerts(certs) leafIDHash := sha256.Sum256(raw[0].Data) extraData, err := util.ExtraDataForChain(raw[0], raw[1:], isPrecert) if err != nil { t.Fatalf("failed to serialize extra data: %v", err) } return &trillian.LogLeaf{LeafIdentityHash: leafIDHash[:], LeafValue: leafData, ExtraData: extraData} } type dlMatcher struct { } func deadlineMatcher() gomock.Matcher { return dlMatcher{} } func (d dlMatcher) Matches(x interface{}) bool { ctx, ok := x.(context.Context) if !ok { return false } deadlineTime, ok := ctx.Deadline() if !ok { return false // we never make RPC calls without a deadline set } return deadlineTime == fakeDeadlineTime } func (d dlMatcher) String() string { return fmt.Sprintf("deadline is %v", fakeDeadlineTime) } func makeAddPrechainRequest(t *testing.T, li *logInfo, body io.Reader) *httptest.ResponseRecorder { t.Helper() handler := AppHandler{Info: li, Handler: addPreChain, Name: "AddPreChain", Method: http.MethodPost} return makeAddChainRequestInternal(t, handler, "add-pre-chain", body) } func makeAddChainRequest(t *testing.T, li *logInfo, body io.Reader) *httptest.ResponseRecorder { t.Helper() handler := AppHandler{Info: li, Handler: addChain, Name: "AddChain", Method: http.MethodPost} return makeAddChainRequestInternal(t, handler, "add-chain", body) } func makeAddChainRequestInternal(t *testing.T, handler AppHandler, path string, body io.Reader) *httptest.ResponseRecorder { t.Helper() req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://example.com/ct/v1/%s", path), body) if err != nil { t.Fatalf("Failed to create POST request: %v", err) } w := httptest.NewRecorder() handler.ServeHTTP(w, req) return w } func makeGetRootResponseForTest(t *testing.T, stamp, treeSize int64, hash []byte) *trillian.GetLatestSignedLogRootResponse { t.Helper() return &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ TimestampNanos: uint64(stamp), TreeSize: uint64(treeSize), RootHash: hash, }), } } func loadCertsIntoPoolOrDie(t *testing.T, certs []string) *x509util.PEMCertPool { t.Helper() pool := x509util.NewPEMCertPool() for _, cert := range certs { if !pool.AppendCertsFromPEM([]byte(cert)) { t.Fatalf("couldn't parse test certs: %v", certs) } } return pool } func mustMarshalRoot(t *testing.T, lr *types.LogRootV1) *trillian.SignedLogRoot { t.Helper() rootBytes, err := lr.MarshalBinary() if err != nil { t.Fatalf("Failed to marshal root in test: %v", err) } return &trillian.SignedLogRoot{ LogRoot: rootBytes, } } // cmpMatcher is a custom gomock.Matcher that uses cmp.Equal combined with a // cmp.Comparer that knows how to properly compare proto.Message types. type cmpMatcher struct{ want interface{} } func (m cmpMatcher) Matches(got interface{}) bool { return cmp.Equal(got, m.want, cmp.Comparer(proto.Equal)) } func (m cmpMatcher) String() string { return fmt.Sprintf("equals %v", m.want) } google-certificate-transparency-go-2308f62/trillian/ctfe/instance.go000066400000000000000000000171361462611535200255430ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "errors" "fmt" "net/http" "strconv" "strings" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/schedule" "github.com/google/certificate-transparency-go/trillian/ctfe/cache" "github.com/google/certificate-transparency-go/trillian/ctfe/storage" "github.com/google/certificate-transparency-go/trillian/util" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian" "github.com/google/trillian/crypto/keys" "github.com/google/trillian/monitoring" "k8s.io/klog/v2" ) // InstanceOptions describes the options for a log instance. type InstanceOptions struct { // Validated holds the original configuration options for the log, and some // of its fields parsed as a result of validating it. Validated *ValidatedLogConfig // Client is a corresponding Trillian log client. Client trillian.TrillianLogClient // Deadline is a timeout for Trillian RPC requests. Deadline time.Duration // MetricFactory allows creating metrics. MetricFactory monitoring.MetricFactory // ErrorMapper converts an error from an RPC request to an HTTP status, plus // a boolean to indicate whether the conversion succeeded. ErrorMapper func(error) (int, bool) // RequestLog provides structured logging of CTFE requests. RequestLog RequestLog // RemoteUser returns a string representing the originating host for the // given request. This string will be used as a User quota key. // If unset, no quota will be requested for remote users. RemoteQuotaUser func(*http.Request) string // CertificateQuotaUser returns a string representing the passed in // intermediate certificate. This string will be user as a User quota key for // the cert. Quota will be requested for each intermediate in an // add-[pre]-chain request so as to allow individual issuers to be rate // limited. If unset, no quota will be requested for intermediate // certificates. CertificateQuotaUser func(*x509.Certificate) string // STHStorage provides STHs of a source log for the mirror. Only mirror // instances will use it, i.e. when IsMirror == true in the config. If it is // empty then the DefaultMirrorSTHStorage will be used. STHStorage MirrorSTHStorage // MaskInternalErrors indicates if internal server errors should be masked // or returned to the user containing the full error message. MaskInternalErrors bool // CacheType is the CTFE cache type. CacheType cache.Type // CacheOption includes the cache size and time-to-live (TTL). CacheOption cache.Option } // Instance is a set up log/mirror instance. It must be created with the // SetUpInstance call. type Instance struct { Handlers PathHandlers STHGetter STHGetter li *logInfo } // RunUpdateSTH regularly updates the Instance STH so our metrics stay // up-to-date with any tree head changes that are not triggered by us. func (i *Instance) RunUpdateSTH(ctx context.Context, period time.Duration) { c := i.li.instanceOpts.Validated.Config klog.Infof("Start internal get-sth operations on %v (%d)", c.Prefix, c.LogId) schedule.Every(ctx, period, func(ctx context.Context) { klog.V(1).Infof("Force internal get-sth for %v (%d)", c.Prefix, c.LogId) if _, err := i.li.getSTH(ctx); err != nil { klog.Warningf("Failed to retrieve STH for %v (%d): %v", c.Prefix, c.LogId, err) } }) } // GetPublicKey returns the public key from the instance's signer. func (i *Instance) GetPublicKey() crypto.PublicKey { if i.li != nil && i.li.signer != nil { return i.li.signer.Public() } return nil } // SetUpInstance sets up a log (or log mirror) instance using the provided // configuration, and returns an object containing a set of handlers for this // log, and an STH getter. func SetUpInstance(ctx context.Context, opts InstanceOptions) (*Instance, error) { logInfo, err := setUpLogInfo(ctx, opts) if err != nil { return nil, err } handlers := logInfo.Handlers(opts.Validated.Config.Prefix) return &Instance{Handlers: handlers, STHGetter: logInfo.sthGetter, li: logInfo}, nil } func setUpLogInfo(ctx context.Context, opts InstanceOptions) (*logInfo, error) { vCfg := opts.Validated cfg := vCfg.Config // Check config validity. if !cfg.IsMirror && len(cfg.RootsPemFile) == 0 { return nil, errors.New("need to specify RootsPemFile") } // Load the trusted roots. roots := x509util.NewPEMCertPool() for _, pemFile := range cfg.RootsPemFile { if err := roots.AppendCertsFromPEMFile(pemFile); err != nil { return nil, fmt.Errorf("failed to read trusted roots: %v", err) } } var signer crypto.Signer if !cfg.IsMirror { var err error if signer, err = keys.NewSigner(ctx, vCfg.PrivKey); err != nil { return nil, fmt.Errorf("failed to load private key: %v", err) } // If a public key has been configured for a log, check that it is consistent with the private key. if vCfg.PubKey != nil { switch pub := vCfg.PubKey.(type) { case *ecdsa.PublicKey: if !pub.Equal(signer.Public()) { return nil, errors.New("public key is not consistent with private key") } case ed25519.PublicKey: if !pub.Equal(signer.Public()) { return nil, errors.New("public key is not consistent with private key") } case *rsa.PublicKey: if !pub.Equal(signer.Public()) { return nil, errors.New("public key is not consistent with private key") } default: return nil, errors.New("failed to verify consistency of public key with private key") } } } validationOpts := CertValidationOpts{ trustedRoots: roots, rejectExpired: cfg.RejectExpired, rejectUnexpired: cfg.RejectUnexpired, notAfterStart: vCfg.NotAfterStart, notAfterLimit: vCfg.NotAfterLimit, acceptOnlyCA: cfg.AcceptOnlyCa, extKeyUsages: vCfg.KeyUsages, } var err error validationOpts.rejectExtIds, err = parseOIDs(cfg.RejectExtensions) if err != nil { return nil, fmt.Errorf("failed to parse RejectExtensions: %v", err) } // Initialise IssuanceChainService with IssuanceChainStorage and IssuanceChainCache. issuanceChainStorage, err := storage.NewIssuanceChainStorage(ctx, vCfg.ExtraDataIssuanceChainStorageBackend, vCfg.CTFEStorageConnectionString) if err != nil { return nil, err } // issuanceChainCache is nil if the cache related flags are not defined. issuanceChainCache, err := cache.NewIssuanceChainCache(ctx, opts.CacheType, opts.CacheOption) if err != nil { return nil, err } issuanceChainService := newIssuanceChainService(issuanceChainStorage, issuanceChainCache) logInfo := newLogInfo(opts, validationOpts, signer, new(util.SystemTimeSource), issuanceChainService) return logInfo, nil } func parseOIDs(oids []string) ([]asn1.ObjectIdentifier, error) { ret := make([]asn1.ObjectIdentifier, 0, len(oids)) for _, s := range oids { bits := strings.Split(s, ".") var oid asn1.ObjectIdentifier for _, n := range bits { p, err := strconv.Atoi(n) if err != nil { return nil, err } oid = append(oid, p) } ret = append(ret, oid) } return ret, nil } google-certificate-transparency-go-2308f62/trillian/ctfe/instance_test.go000066400000000000000000000206261462611535200266000ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "errors" "fmt" "net/http/httptest" "strings" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/trillian/ctfe/cache" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/trillian/crypto/keys" "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/monitoring" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/timestamppb" ) func init() { keys.RegisterHandler(&keyspb.PEMKeyFile{}, pem.FromProto) } func TestSetUpInstance(t *testing.T) { ctx := context.Background() privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"}) missingPrivKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/bogus.privkey.pem", Password: "dirk"}) wrongPassPrivKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirkly"}) pubKey := mustReadPublicKey("../testdata/ct-http-server.pubkey.pem") var tests = []struct { desc string cfg *configpb.LogConfig wantErr string }{ { desc: "valid", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, }, }, { desc: "valid-mirror", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PublicKey: pubKey, IsMirror: true, }, }, { desc: "no-roots", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", PrivateKey: privKey, }, wantErr: "specify RootsPemFile", }, { desc: "no-roots-mirror", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", PublicKey: pubKey, IsMirror: true, }, }, { desc: "missing-root-cert", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/bogus.cert"}, PrivateKey: privKey, }, wantErr: "failed to read trusted roots", }, { desc: "missing-privkey", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: missingPrivKey, }, wantErr: "failed to load private key", }, { desc: "privkey-wrong-password", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: wrongPassPrivKey, }, wantErr: "failed to load private key", }, { desc: "valid-ekus-1", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, ExtKeyUsages: []string{"Any"}, }, }, { desc: "valid-ekus-2", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, ExtKeyUsages: []string{"Any", "ServerAuth", "TimeStamping"}, }, }, { desc: "valid-reject-ext", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, RejectExtensions: []string{"1.2.3.4", "5.6.7.8"}, }, }, { desc: "invalid-reject-ext", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, RejectExtensions: []string{"1.2.3.4", "one.banana.two.bananas"}, }, wantErr: "one", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { vCfg, err := ValidateLogConfig(test.cfg) if err != nil { t.Fatalf("ValidateLogConfig(): %v", err) } opts := InstanceOptions{Validated: vCfg, Deadline: time.Second, MetricFactory: monitoring.InertMetricFactory{}} if _, err := SetUpInstance(ctx, opts); err != nil { if test.wantErr == "" { t.Errorf("SetUpInstance()=_,%v; want _,nil", err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("SetUpInstance()=_,%v; want err containing %q", err, test.wantErr) } return } if test.wantErr != "" { t.Errorf("SetUpInstance()=_,nil; want err containing %q", test.wantErr) } }) } } func equivalentTimes(a *time.Time, b *timestamppb.Timestamp) bool { if a == nil && b == nil { return true } if a == nil { // b can't be nil as it would have returned above. return false } tsA := timestamppb.New(*a) return tsA.AsTime().Format(time.RFC3339Nano) == b.AsTime().Format(time.RFC3339Nano) } func TestSetUpInstanceSetsValidationOpts(t *testing.T) { ctx := context.Background() start := timestamppb.New(time.Unix(10000, 0)) limit := timestamppb.New(time.Unix(12000, 0)) privKey, err := anypb.New(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"}) if err != nil { t.Fatalf("Could not marshal private key proto: %v", err) } var tests = []struct { desc string cfg *configpb.LogConfig }{ { desc: "no validation opts", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "/log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, }, }, { desc: "notAfterStart only", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "/log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, NotAfterStart: start, }, }, { desc: "notAfter range", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "/log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, NotAfterStart: start, NotAfterLimit: limit, }, }, { desc: "caOnly", cfg: &configpb.LogConfig{ LogId: 1, Prefix: "/log", RootsPemFile: []string{"../testdata/fake-ca.cert"}, PrivateKey: privKey, AcceptOnlyCa: true, }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { vCfg, err := ValidateLogConfig(test.cfg) if err != nil { t.Fatalf("ValidateLogConfig(): %v", err) } opts := InstanceOptions{Validated: vCfg, Deadline: time.Second, MetricFactory: monitoring.InertMetricFactory{}, CacheType: cache.NOOP, CacheOption: cache.Option{}} inst, err := SetUpInstance(ctx, opts) if err != nil { t.Fatalf("%v: SetUpInstance() = %v, want no error", test.desc, err) } addChainHandler, ok := inst.Handlers[test.cfg.Prefix+ct.AddChainPath] if !ok { t.Fatal("Couldn't find AddChain handler") } gotOpts := addChainHandler.Info.validationOpts if got, want := gotOpts.notAfterStart, test.cfg.NotAfterStart; want != nil && !equivalentTimes(got, want) { t.Errorf("%v: handler notAfterStart %v, want %v", test.desc, got, want) } if got, want := gotOpts.notAfterLimit, test.cfg.NotAfterLimit; want != nil && !equivalentTimes(got, want) { t.Errorf("%v: handler notAfterLimit %v, want %v", test.desc, got, want) } if got, want := gotOpts.acceptOnlyCA, test.cfg.AcceptOnlyCa; got != want { t.Errorf("%v: handler acceptOnlyCA %v, want %v", test.desc, got, want) } }) } } func TestErrorMasking(t *testing.T) { info := logInfo{} w := httptest.NewRecorder() prefix := "Internal Server Error" err := errors.New("well that's bad") info.SendHTTPError(w, 500, err) if got, want := w.Body.String(), fmt.Sprintf("%s\n%v\n", prefix, err); got != want { t.Errorf("SendHTTPError: got %s, want %s", got, want) } info.instanceOpts.MaskInternalErrors = true w = httptest.NewRecorder() info.SendHTTPError(w, 500, err) if got, want := w.Body.String(), prefix+"\n"; got != want { t.Errorf("SendHTTPError: got %s, want %s", got, want) } } google-certificate-transparency-go-2308f62/trillian/ctfe/requestlog.go000066400000000000000000000140611462611535200261230ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "encoding/hex" "time" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) const vLevel = 9 // RequestLog allows implementations to do structured logging of CTFE // request parameters, submitted chains and other internal details that // are useful for log operators when debugging issues. CTFE handlers will // call the appropriate methods during request processing. The implementation // is responsible for collating and storing the resulting logging information. type RequestLog interface { // Start will be called once at the beginning of handling each request. // The supplied context will be the one used for request processing and // can be used by the logger to set values on the returned context. // The returned context should be used in all the following calls to // this API. This is normally arranged by the request handler code. Start(context.Context) context.Context // LogPrefix will be called once per request to set the log prefix. LogPrefix(context.Context, string) // AddDERToChain will be called once for each certificate in a submitted // chain. It's called early in request processing so the supplied bytes // have not been checked for validity. Calls will be in order of the // certificates as presented in the request with the root last. AddDERToChain(context.Context, []byte) // AddCertToChain will be called once for each certificate in the chain // after it has been parsed and verified. Calls will be in order of the // certificates as presented in the request with the root last. AddCertToChain(context.Context, *x509.Certificate) // FirstAndSecond will be called once for a consistency proof request with // the first and second tree sizes involved (if they parse correctly). FirstAndSecond(context.Context, int64, int64) // StartAndEnd will be called once for a get entries request with the // endpoints of the range requested (if they parse correctly). StartAndEnd(context.Context, int64, int64) // LeafIndex will be called once with the index of a leaf being requested // by get entry and proof (if the request params parse correctly). LeafIndex(context.Context, int64) // TreeSize will be called once with the requested tree size for get entry // and proof requests (if the request params parse correctly). TreeSize(context.Context, int64) // LeafHash will be called once for get proof by hash requests with the // requested hash value (if the parameters parse correctly). LeafHash(context.Context, []byte) // IssueSCT will be called once when the server is about to issue an SCT to a // client. This should not be called if the submission process fails before an // SCT could be presented to a client, even if this is unrelated to // the validity of the submitted chain. The SCT bytes will be in TLS // serialized format. IssueSCT(context.Context, []byte) // Status will be called once to set the HTTP status code that was the // the result after the request has been handled. Status(context.Context, int) } // DefaultRequestLog is an implementation of RequestLog that does nothing // except log the calls at a high level of verbosity. type DefaultRequestLog struct { } // Start logs the start of request processing. func (dlr *DefaultRequestLog) Start(ctx context.Context) context.Context { klog.V(vLevel).Info("RL: Start") return ctx } // LogPrefix logs the prefix of the CT log that this request is for. func (dlr *DefaultRequestLog) LogPrefix(_ context.Context, p string) { klog.V(vLevel).Infof("RL: LogPrefix: %s", p) } // AddDERToChain logs the raw bytes of a submitted certificate. func (dlr *DefaultRequestLog) AddDERToChain(_ context.Context, d []byte) { // Explicit hex encoding below to satisfy CodeQL: klog.V(vLevel).Infof("RL: Cert DER: %s", hex.EncodeToString(d)) } // AddCertToChain logs some issuer / subject / timing fields from a // certificate that is part of a submitted chain. func (dlr *DefaultRequestLog) AddCertToChain(_ context.Context, cert *x509.Certificate) { klog.V(vLevel).Infof("RL: Cert: Sub: %s Iss: %s notBef: %s notAft: %s", x509util.NameToString(cert.Subject), x509util.NameToString(cert.Issuer), cert.NotBefore.Format(time.RFC1123Z), cert.NotAfter.Format(time.RFC1123Z)) } // FirstAndSecond logs request parameters. func (dlr *DefaultRequestLog) FirstAndSecond(_ context.Context, f, s int64) { klog.V(vLevel).Infof("RL: First: %d Second: %d", f, s) } // StartAndEnd logs request parameters. func (dlr *DefaultRequestLog) StartAndEnd(_ context.Context, s, e int64) { klog.V(vLevel).Infof("RL: Start: %d End: %d", s, e) } // LeafIndex logs request parameters. func (dlr *DefaultRequestLog) LeafIndex(_ context.Context, li int64) { klog.V(vLevel).Infof("RL: LeafIndex: %d", li) } // TreeSize logs request parameters. func (dlr *DefaultRequestLog) TreeSize(_ context.Context, ts int64) { klog.V(vLevel).Infof("RL: TreeSize: %d", ts) } // LeafHash logs request parameters. func (dlr *DefaultRequestLog) LeafHash(_ context.Context, lh []byte) { // Explicit hex encoding below to satisfy CodeQL: klog.V(vLevel).Infof("RL: LeafHash: %s", hex.EncodeToString(lh)) } // IssueSCT logs an SCT that will be issued to a client. func (dlr *DefaultRequestLog) IssueSCT(_ context.Context, sct []byte) { klog.V(vLevel).Infof("RL: Issuing SCT: %x", sct) } // Status logs the response HTTP status code after processing completes. func (dlr *DefaultRequestLog) Status(_ context.Context, s int) { klog.V(vLevel).Infof("RL: Status: %d", s) } google-certificate-transparency-go-2308f62/trillian/ctfe/serialize.go000066400000000000000000000073641462611535200257300ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bytes" "crypto" "crypto/rand" "crypto/sha256" "fmt" "sync" "github.com/google/certificate-transparency-go/tls" ct "github.com/google/certificate-transparency-go" ) // SignatureCache is a one-entry cache that stores the last generated signature // for a given bytes input. It helps to reduce the number of signing // operations, and the number of distinct signatures produced for the same // input (some signing methods are non-deterministic). type SignatureCache struct { mu sync.RWMutex input []byte sig ct.DigitallySigned } // GetSignature returns the latest signature for the given bytes input. If the // input is not in the cache, it returns (_, false). func (sc *SignatureCache) GetSignature(input []byte) (ct.DigitallySigned, bool) { sc.mu.RLock() defer sc.mu.RUnlock() if !bytes.Equal(input, sc.input) { return ct.DigitallySigned{}, false } return sc.sig, true } // SetSignature associates the signature with the given bytes input. func (sc *SignatureCache) SetSignature(input []byte, sig ct.DigitallySigned) { sc.mu.Lock() defer sc.mu.Unlock() sc.input, sc.sig = input, sig } // signV1TreeHead signs a tree head for CT. The input STH should have been // built from a backend response and already checked for validity. func signV1TreeHead(signer crypto.Signer, sth *ct.SignedTreeHead, cache *SignatureCache) error { sthBytes, err := ct.SerializeSTHSignatureInput(*sth) if err != nil { return err } if sig, ok := cache.GetSignature(sthBytes); ok { sth.TreeHeadSignature = sig return nil } h := sha256.New() h.Write(sthBytes) signature, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256) if err != nil { return err } sth.TreeHeadSignature = ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.SignatureAlgorithmFromPubKey(signer.Public()), }, Signature: signature, } cache.SetSignature(sthBytes, sth.TreeHeadSignature) return nil } func buildV1SCT(signer crypto.Signer, leaf *ct.MerkleTreeLeaf) (*ct.SignedCertificateTimestamp, error) { // Serialize SCT signature input to get the bytes that need to be signed sctInput := ct.SignedCertificateTimestamp{ SCTVersion: ct.V1, Timestamp: leaf.TimestampedEntry.Timestamp, Extensions: leaf.TimestampedEntry.Extensions, } data, err := ct.SerializeSCTSignatureInput(sctInput, ct.LogEntry{Leaf: *leaf}) if err != nil { return nil, fmt.Errorf("failed to serialize SCT data: %v", err) } h := sha256.Sum256(data) signature, err := signer.Sign(rand.Reader, h[:], crypto.SHA256) if err != nil { return nil, fmt.Errorf("failed to sign SCT data: %v", err) } digitallySigned := ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.SignatureAlgorithmFromPubKey(signer.Public()), }, Signature: signature, } logID, err := GetCTLogID(signer.Public()) if err != nil { return nil, fmt.Errorf("failed to get logID for signing: %v", err) } return &ct.SignedCertificateTimestamp{ SCTVersion: ct.V1, LogID: ct.LogID{KeyID: logID}, Timestamp: sctInput.Timestamp, Extensions: sctInput.Extensions, Signature: digitallySigned, }, nil } google-certificate-transparency-go-2308f62/trillian/ctfe/serialize_test.go000066400000000000000000000201531462611535200267560ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bytes" "crypto/sha256" "testing" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian/crypto/keys/pem" "github.com/kylelemons/godebug/pretty" ct "github.com/google/certificate-transparency-go" ) func TestBuildV1MerkleTreeLeafForCert(t *testing.T) { cert, err := x509util.CertificateFromPEM([]byte(testonly.LeafSignedByFakeIntermediateCertPEM)) if x509.IsFatal(err) { t.Fatalf("failed to set up test cert: %v", err) } signer, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("could not create signer: %v", err) } leaf, err := ct.MerkleTreeLeafFromChain([]*x509.Certificate{cert}, ct.X509LogEntryType, fixedTimeMillis) if err != nil { t.Fatalf("buildV1MerkleTreeLeafForCert()=nil,%v; want _,nil", err) } got, err := buildV1SCT(signer, leaf) if err != nil { t.Fatalf("buildV1SCT()=nil,%v; want _,nil", err) } expected := ct.SignedCertificateTimestamp{ SCTVersion: 0, LogID: ct.LogID{KeyID: demoLogID}, Timestamp: fixedTimeMillis, Extensions: ct.CTExtensions{}, Signature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.ECDSA}, Signature: fakeSignature, }, } if diff := pretty.Compare(*got, expected); diff != "" { t.Fatalf("Mismatched SCT (cert), diff:\n%v", diff) } // Additional checks that the MerkleTreeLeaf we built is correct if got, want := leaf.Version, ct.V1; got != want { t.Fatalf("Got a %v leaf, expected a %v leaf", got, want) } if got, want := leaf.LeafType, ct.TimestampedEntryLeafType; got != want { t.Fatalf("Got leaf type %v, expected %v", got, want) } if got, want := leaf.TimestampedEntry.EntryType, ct.X509LogEntryType; got != want { t.Fatalf("Got entry type %v, expected %v", got, want) } if got, want := leaf.TimestampedEntry.Timestamp, got.Timestamp; got != want { t.Fatalf("Entry / sct timestamp mismatch; got %v, expected %v", got, want) } if got, want := leaf.TimestampedEntry.X509Entry.Data, cert.Raw; !bytes.Equal(got, want) { t.Fatalf("Cert bytes mismatch, got %x, expected %x", got, want) } } func TestSignV1SCTForPrecertificate(t *testing.T) { cert, err := x509util.CertificateFromPEM([]byte(testonly.PrecertPEMValid)) if x509.IsFatal(err) { t.Fatalf("failed to set up test precert: %v", err) } signer, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("could not create signer: %v", err) } // Use the same cert as the issuer for convenience. leaf, err := ct.MerkleTreeLeafFromChain([]*x509.Certificate{cert, cert}, ct.PrecertLogEntryType, fixedTimeMillis) if err != nil { t.Fatalf("buildV1MerkleTreeLeafForCert()=nil,%v; want _,nil", err) } got, err := buildV1SCT(signer, leaf) if err != nil { t.Fatalf("buildV1SCT()=nil,%v; want _,nil", err) } expected := ct.SignedCertificateTimestamp{ SCTVersion: 0, LogID: ct.LogID{KeyID: demoLogID}, Timestamp: fixedTimeMillis, Extensions: ct.CTExtensions{}, Signature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.ECDSA}, Signature: fakeSignature}} if diff := pretty.Compare(*got, expected); diff != "" { t.Fatalf("Mismatched SCT (precert), diff:\n%v", diff) } // Additional checks that the MerkleTreeLeaf we built is correct if got, want := leaf.Version, ct.V1; got != want { t.Fatalf("Got a %v leaf, expected a %v leaf", got, want) } if got, want := leaf.LeafType, ct.TimestampedEntryLeafType; got != want { t.Fatalf("Got leaf type %v, expected %v", got, want) } if got, want := leaf.TimestampedEntry.EntryType, ct.PrecertLogEntryType; got != want { t.Fatalf("Got entry type %v, expected %v", got, want) } if got, want := got.Timestamp, leaf.TimestampedEntry.Timestamp; got != want { t.Fatalf("Entry / sct timestamp mismatch; got %v, expected %v", got, want) } keyHash := sha256.Sum256(cert.RawSubjectPublicKeyInfo) if got, want := keyHash[:], leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash[:]; !bytes.Equal(got, want) { t.Fatalf("Issuer key hash bytes mismatch, got %v, expected %v", got, want) } defangedTBS, _ := x509.RemoveCTPoison(cert.RawTBSCertificate) if got, want := leaf.TimestampedEntry.PrecertEntry.TBSCertificate, defangedTBS; !bytes.Equal(got, want) { t.Fatalf("TBS cert mismatch, got %v, expected %v", got, want) } } func TestSignV1TreeHead(t *testing.T) { signer, err := pem.UnmarshalPrivateKey(testdata.DemoPrivateKey, testdata.DemoPrivateKeyPass) if err != nil { t.Fatalf("could not create signer: %v", err) } var cache SignatureCache sth := ct.SignedTreeHead{ Version: ct.V1, TreeSize: 10, Timestamp: 1512993312000, } if err := signV1TreeHead(signer, &sth, &cache); err != nil { t.Fatalf("signV1TreeHead()=%v; want nil", err) } prevSig := make([]byte, len(sth.TreeHeadSignature.Signature)) copy(prevSig, sth.TreeHeadSignature.Signature) // Signing the same contents should get the same cached signature regardless. for i := 0; i < 5; i++ { if err := signV1TreeHead(signer, &sth, &cache); err != nil { t.Fatalf("signV1TreeHead()=%v; want nil", err) } sig := make([]byte, len(sth.TreeHeadSignature.Signature)) copy(sig, sth.TreeHeadSignature.Signature) if diff := pretty.Compare(prevSig, sig); diff != "" { t.Fatalf("signV1TreeHead().TreeHeadSignature mismatched, diff:\n%v", diff) } } // But changing the contents does change the signature. for i := 0; i < 5; i++ { sth.TreeSize = uint64(11 + i) if err := signV1TreeHead(signer, &sth, &cache); err != nil { t.Errorf("signV1TreeHead()=%v; want nil", err) } sig := make([]byte, len(sth.TreeHeadSignature.Signature)) copy(sig, sth.TreeHeadSignature.Signature) if bytes.Equal(prevSig, sig) { t.Fatalf("signV1TreeHead(size=%d).TreeHeadSignature unexpectedly matched", sth.TreeSize) } prevSig := sig // Repeating should again return the cached signature. if err := signV1TreeHead(signer, &sth, &cache); err != nil { t.Errorf("signV1TreeHead(size=%d)=%v; want nil", sth.TreeSize, err) } sig = make([]byte, len(sth.TreeHeadSignature.Signature)) copy(sig, sth.TreeHeadSignature.Signature) if diff := pretty.Compare(prevSig, sig); diff != "" { t.Fatalf("signV1TreeHead(size=%d).TreeHeadSignature mismatched, diff:\n%v", sth.TreeSize, diff) } } } func TestSignV1TreeHeadDifferentSigners(t *testing.T) { signer1, err := pem.UnmarshalPrivateKey(testdata.DemoPrivateKey, testdata.DemoPrivateKeyPass) if err != nil { t.Fatalf("could not create signer1: %v", err) } signer2, err := setupSigner(fakeSignature) if err != nil { t.Fatalf("could not create signer2: %v", err) } var cache1, cache2 SignatureCache sth := ct.SignedTreeHead{ Version: ct.V1, TreeSize: 10, Timestamp: 1512993312000, } if err := signV1TreeHead(signer1, &sth, &cache1); err != nil { t.Fatalf("signV1TreeHead(signer1)=%v; want nil", err) } sig1 := make([]byte, len(sth.TreeHeadSignature.Signature)) copy(sig1, sth.TreeHeadSignature.Signature) if err := signV1TreeHead(signer2, &sth, &cache2); err != nil { t.Fatalf("signV1TreeHead(signer2)=%v; want nil", err) } sig2 := make([]byte, len(sth.TreeHeadSignature.Signature)) copy(sig2, sth.TreeHeadSignature.Signature) // Check that different signers on the same contents give different results. if bytes.Equal(sig1, sig2) { t.Fatal("signV1TreeHead().TreeHeadSignature unexpectedly matched") } } google-certificate-transparency-go-2308f62/trillian/ctfe/services.go000066400000000000000000000160641462611535200255610ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto/sha256" "errors" "fmt" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/ctfe/cache" "github.com/google/certificate-transparency-go/trillian/ctfe/storage" "github.com/google/certificate-transparency-go/trillian/util" "github.com/google/certificate-transparency-go/x509" "github.com/google/trillian" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) type issuanceChainService struct { storage storage.IssuanceChainStorage cache cache.IssuanceChainCache } func newIssuanceChainService(s storage.IssuanceChainStorage, c cache.IssuanceChainCache) *issuanceChainService { service := &issuanceChainService{ storage: s, cache: c, } return service } func (s *issuanceChainService) isCTFEStorageEnabled() bool { return s.storage != nil } // GetByHash returns the issuance chain with hash as the input. func (s *issuanceChainService) GetByHash(ctx context.Context, hash []byte) ([]byte, error) { // Return err if CTFE storage backend is not enabled. if !s.isCTFEStorageEnabled() { return nil, errors.New("failed to GetByHash when storage is nil") } // Return if found in cache. chain, err := s.cache.Get(ctx, hash) if chain != nil || err != nil { return chain, err } // Find in storage if cache miss. chain, err = s.storage.FindByKey(ctx, hash) if err != nil { return nil, err } // If there is any error from cache set, do not return the error because // the chain is still available for read. go func(ctx context.Context, hash, chain []byte) { if err := s.cache.Set(ctx, hash, chain); err != nil { klog.Errorf("failed to set hash and chain into cache: %v", err) } }(ctx, hash, chain) return chain, nil } // add adds the issuance chain into the storage and cache and returns the hash // of the chain. func (s *issuanceChainService) add(ctx context.Context, chain []byte) ([]byte, error) { // Return err if CTFE storage backend is not enabled. if !s.isCTFEStorageEnabled() { return nil, errors.New("failed to Add when storage is nil") } hash := issuanceChainHash(chain) if err := s.storage.Add(ctx, hash, chain); err != nil { return nil, err } // If there is any error from cache set, do not return the error because // the chain is already stored. go func(ctx context.Context, hash, chain []byte) { if err := s.cache.Set(ctx, hash, chain); err != nil { klog.Errorf("failed to set hash and chain into cache: %v", err) } }(ctx, hash, chain) return hash, nil } // BuildLogLeaf builds the MerkleTreeLeaf that gets sent to the backend, and make a trillian.LogLeaf for it. func (s *issuanceChainService) BuildLogLeaf(ctx context.Context, chain []*x509.Certificate, logPrefix string, merkleLeaf *ct.MerkleTreeLeaf, isPrecert bool) (*trillian.LogLeaf, error) { raw := extractRawCerts(chain) // If CTFE storage is enabled for issuance chain, add the chain to storage // and cache, and then build log leaf. If Trillian gRPC is enabled for // issuance chain, build the log leaf. if s.isCTFEStorageEnabled() { issuanceChain, err := asn1.Marshal(raw[1:]) if err != nil { return nil, fmt.Errorf("failed to marshal issuance chain: %s", err) } hash, err := s.add(ctx, issuanceChain) if err != nil { return nil, fmt.Errorf("failed to add issuance chain into CTFE storage: %s", err) } leaf, err := util.BuildLogLeafWithChainHash(logPrefix, *merkleLeaf, 0, raw[0], hash, isPrecert) if err != nil { return nil, fmt.Errorf("failed to build LogLeaf: %s", err) } return leaf, nil } // Trillian gRPC leaf, err := util.BuildLogLeaf(logPrefix, *merkleLeaf, 0, raw[0], raw[1:], isPrecert) if err != nil { return nil, fmt.Errorf("failed to build LogLeaf: %s", err) } return leaf, nil } // FixLogLeaf recreates and populates the LogLeaf.ExtraData if CTFE storage // backend is enabled and the type of LogLeaf.ExtraData contains any hash // (e.g. PrecertChainEntryHash, CertificateChainHash). func (s *issuanceChainService) FixLogLeaf(ctx context.Context, leaf *trillian.LogLeaf) error { // Skip if CTFE storage backend is not enabled. if !s.isCTFEStorageEnabled() { return nil } // As the struct stored in leaf.ExtraData is unknown, the only way is to try to unmarshal with each possible struct. // Try to unmarshal with ct.PrecertChainEntryHash struct. var precertChainHash ct.PrecertChainEntryHash if rest, err := tls.Unmarshal(leaf.ExtraData, &precertChainHash); err == nil && len(rest) == 0 { var chain []ct.ASN1Cert if len(precertChainHash.IssuanceChainHash) > 0 { chainBytes, err := s.GetByHash(ctx, precertChainHash.IssuanceChainHash) if err != nil { return err } if rest, err := asn1.Unmarshal(chainBytes, &chain); err != nil { return err } else if len(rest) > 0 { return fmt.Errorf("IssuanceChain: trailing data %d bytes", len(rest)) } } precertChain := ct.PrecertChainEntry{ PreCertificate: precertChainHash.PreCertificate, CertificateChain: chain, } extraData, err := tls.Marshal(precertChain) if err != nil { return err } leaf.ExtraData = extraData return nil } // Try to unmarshal with ct.CertificateChainHash struct. var certChainHash ct.CertificateChainHash if rest, err := tls.Unmarshal(leaf.ExtraData, &certChainHash); err == nil && len(rest) == 0 { var entries []ct.ASN1Cert if len(certChainHash.IssuanceChainHash) > 0 { chainBytes, err := s.GetByHash(ctx, certChainHash.IssuanceChainHash) if err != nil { return err } if rest, err := asn1.Unmarshal(chainBytes, &entries); err != nil { return err } else if len(rest) > 0 { return fmt.Errorf("IssuanceChain: trailing data %d bytes", len(rest)) } } certChain := ct.CertificateChain{ Entries: entries, } extraData, err := tls.Marshal(certChain) if err != nil { return err } leaf.ExtraData = extraData return nil } // Skip if the types are ct.PrecertChainEntry or ct.CertificateChain as there is no hash. var precertChain ct.PrecertChainEntry if rest, err := tls.Unmarshal(leaf.ExtraData, &precertChain); err == nil && len(rest) == 0 { return nil } var certChain ct.CertificateChain if rest, err := tls.Unmarshal(leaf.ExtraData, &certChain); err == nil && len(rest) == 0 { return nil } return fmt.Errorf("unknown extra data type in log leaf: %s", string(leaf.MerkleLeafHash)) } // issuanceChainHash returns the SHA-256 hash of the chain. func issuanceChainHash(chain []byte) []byte { checksum := sha256.Sum256(chain) return checksum[:] } google-certificate-transparency-go-2308f62/trillian/ctfe/services_test.go000066400000000000000000000053661462611535200266230ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bytes" "context" "crypto/sha256" "os" "sync" "testing" ) func TestIssuanceChainServiceAddAndGet(t *testing.T) { tests := []struct { chain []byte }{ {readTestData(t, "leaf00.chain")}, {readTestData(t, "leaf01.chain")}, {readTestData(t, "leaf02.chain")}, {nil}, } ctx := context.Background() storage := &fakeIssuanceChainStorage{} cache := &fakeIssuanceChainCache{} issuanceChainService := newIssuanceChainService(storage, cache) for _, test := range tests { hash, err := issuanceChainService.add(ctx, test.chain) if err != nil { t.Errorf("IssuanceChainService.Add(): %v", err) } got, err := issuanceChainService.GetByHash(ctx, hash) if err != nil { t.Errorf("IssuanceChainService.GetByHash(): %v", err) } if !bytes.Equal(got, test.chain) { t.Errorf("GetByHash = %v, want %v", got, test.chain) } } } func TestIssuanceChainHashLen(t *testing.T) { want := sha256.Size tests := []struct { chain []byte }{ {readTestData(t, "leaf00.chain")}, {readTestData(t, "leaf01.chain")}, {readTestData(t, "leaf02.chain")}, {nil}, } for _, test := range tests { got := len(issuanceChainHash(test.chain)) if got != want { t.Errorf("len(issuanceChainHash(%v)) = %d, want %d", test.chain, got, want) } } } func readTestData(t *testing.T, filename string) []byte { t.Helper() data, err := os.ReadFile("../testdata/" + filename) if err != nil { t.Fatal(err) } return data } type fakeIssuanceChainStorage struct { chains sync.Map } func (s *fakeIssuanceChainStorage) FindByKey(_ context.Context, key []byte) ([]byte, error) { val, _ := s.chains.Load(string(key)) chain, _ := val.([]byte) return chain, nil } func (s *fakeIssuanceChainStorage) Add(_ context.Context, key []byte, chain []byte) error { s.chains.Store(string(key), chain) return nil } type fakeIssuanceChainCache struct { chains sync.Map } func (c *fakeIssuanceChainCache) Get(_ context.Context, key []byte) ([]byte, error) { val, _ := c.chains.Load(string(key)) chain, _ := val.([]byte) return chain, nil } func (c *fakeIssuanceChainCache) Set(_ context.Context, key []byte, chain []byte) error { c.chains.Store(string(key), chain) return nil } google-certificate-transparency-go-2308f62/trillian/ctfe/sth.go000066400000000000000000000135271462611535200245350ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto/sha256" "errors" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/trillian" "github.com/google/trillian/types" "google.golang.org/protobuf/encoding/prototext" "k8s.io/klog/v2" ) type contextKey string // remoteQuotaCtxKey is the key used to attach a Trillian quota user to // context.Context passed in to STH getters. var remoteQuotaCtxKey = contextKey("quotaUser") // MirrorSTHStorage provides STHs of a source log to be served from a mirror. type MirrorSTHStorage interface { // GetMirrorSTH returns an STH of TreeSize <= maxTreeSize. It does best // effort to maximize the returned STH's TreeSize and/or Timestamp. GetMirrorSTH(ctx context.Context, maxTreeSize int64) (*ct.SignedTreeHead, error) } // STHGetter provides latest STHs for a log. type STHGetter interface { // GetSTH returns the latest STH for the log, as required by the RFC-6962 // get-sth endpoint: https://tools.ietf.org/html/rfc6962#section-4.3. GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) } // FrozenSTHGetter is an STHGetter implementation returning a constant STH. type FrozenSTHGetter struct { sth *ct.SignedTreeHead } // GetSTH returns the frozen STH. func (sg *FrozenSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { return sg.sth, nil } // LogSTHGetter is an STHGetter implementation for regular (non-mirror) logs, // i.e. logs that have their own key and actively sign STHs. type LogSTHGetter struct { li *logInfo cache SignatureCache } // GetSTH retrieves and builds a tree head structure for the given log. // nolint:staticcheck func (sg *LogSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { currentRoot, err := getSignedLogRoot(ctx, sg.li.rpcClient, sg.li.logID, sg.li.LogPrefix) if err != nil { return nil, err } // Build the CT STH object, except the signature. sth := &ct.SignedTreeHead{ Version: ct.V1, TreeSize: uint64(currentRoot.TreeSize), Timestamp: uint64(currentRoot.TimestampNanos / 1000 / 1000), } // Note: The size was checked in getSignedLogRoot. copy(sth.SHA256RootHash[:], currentRoot.RootHash) // Add the signature over the STH contents. err = signV1TreeHead(sg.li.signer, sth, &sg.cache) if err != nil || len(sth.TreeHeadSignature.Signature) == 0 { return nil, fmt.Errorf("failed to sign tree head: %v", err) } return sth, nil } // MirrorSTHGetter is an STHGetter implementation for mirror logs. It assumes // no knowledge of the key, and returns STHs obtained from an external source // represented by the MirrorSTHStorage interface. type MirrorSTHGetter struct { li *logInfo st MirrorSTHStorage } // GetSTH returns a known source log's STH with as large TreeSize and/or // timestamp as possible, but such that TreeSize <= Trillian log size. This is // to ensure that the mirror doesn't expose a "future" state of the log before // it is properly stored in Trillian. func (sg *MirrorSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { currentRoot, err := getSignedLogRoot(ctx, sg.li.rpcClient, sg.li.logID, sg.li.LogPrefix) if err != nil { return nil, err } sth, err := sg.st.GetMirrorSTH(ctx, int64(currentRoot.TreeSize)) // nolint:staticcheck if err != nil { return nil, err } // TODO(pavelkalinnikov): Check sth signature. // TODO(pavelkalinnikov): Check consistency between slr and sth. return sth, nil } // getSignedLogRoot obtains the latest LogRootV1 from Trillian log. // nolint:staticcheck func getSignedLogRoot(ctx context.Context, client trillian.TrillianLogClient, logID int64, prefix string) (*types.LogRootV1, error) { req := trillian.GetLatestSignedLogRootRequest{LogId: logID} if q := ctx.Value(remoteQuotaCtxKey); q != nil { quotaUser, ok := q.(string) if !ok { return nil, fmt.Errorf("incorrect quota value: %v, type %T", q, q) } req.ChargeTo = appendUserCharge(req.ChargeTo, quotaUser) } klog.V(2).Infof("%s: GetSTH => grpc.GetLatestSignedLogRoot %+v", prefix, prototext.Format(&req)) rsp, err := client.GetLatestSignedLogRoot(ctx, &req) klog.V(2).Infof("%s: GetSTH <= grpc.GetLatestSignedLogRoot err=%v", prefix, err) if err != nil { return nil, err } // Check over the response. slr := rsp.SignedLogRoot if slr == nil { return nil, errors.New("no log root returned") } klog.V(3).Infof("%s: GetSTH <= slr=%+v", prefix, slr) var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(slr.GetLogRoot()); err != nil { return nil, fmt.Errorf("failed to unmarshal root: %v", slr) } if hashSize := len(currentRoot.RootHash); hashSize != sha256.Size { return nil, fmt.Errorf("bad hash size from backend expecting: %d got %d", sha256.Size, hashSize) } return ¤tRoot, nil } // DefaultMirrorSTHFactory creates DefaultMirrorSTHStorage instances. type DefaultMirrorSTHFactory struct{} // NewStorage creates a dummy STH storage. func (f DefaultMirrorSTHFactory) NewStorage(logID [sha256.Size]byte) (MirrorSTHStorage, error) { return DefaultMirrorSTHStorage{}, nil } // DefaultMirrorSTHStorage is a dummy STH storage that always returns an error. type DefaultMirrorSTHStorage struct{} // GetMirrorSTH returns an error. func (st DefaultMirrorSTHStorage) GetMirrorSTH(ctx context.Context, maxTreeSize int64) (*ct.SignedTreeHead, error) { return nil, errors.New("not implemented") } google-certificate-transparency-go-2308f62/trillian/ctfe/sth_test.go000066400000000000000000000224371462611535200255740ustar00rootroot00000000000000// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto" "errors" "io" "reflect" "strings" "testing" "github.com/golang/mock/gomock" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/mockclient" "github.com/google/trillian" "github.com/google/trillian/types" ) type testCase struct { desc string ctxSetup func(ctx context.Context) context.Context ms MirrorSTHStorage // Only set for mirror getter tests. slr *trillian.GetLatestSignedLogRootResponse slrErr error sig []byte // Only set (and sigErr) for log getter tests. sigErr error wantSTH *ct.SignedTreeHead errStr string } // commonTests are valid for both cases, mostly basic parameter checks // and type / error handling. func commonTests(t *testing.T) []testCase { t.Helper() return []testCase{ { desc: "bad quota value", ctxSetup: func(ctx context.Context) context.Context { return context.WithValue(ctx, remoteQuotaCtxKey, []byte("not a string value")) }, errStr: "incorrect quota", }, { desc: "latest root RPC fails", slrErr: errors.New("slr failed"), errStr: "slr failed", }, { desc: "nil slr", slr: &trillian.GetLatestSignedLogRootResponse{}, errStr: "no log root", }, { desc: "bad slr", slr: &trillian.GetLatestSignedLogRootResponse{SignedLogRoot: &trillian.SignedLogRoot{LogRoot: []byte("not tls encoded")}}, errStr: "unmarshal root: log_root:\"not tls", }, { desc: "bad hash", slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{RootHash: []byte("not a 32 byte hash")}), }, errStr: "bad hash size", }, } } // logTests apply only to the LogSTHGetter where things are signed. func logTests(t *testing.T) []testCase { t.Helper() return []testCase{ { desc: "signer error", slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{RootHash: []byte("12345678123456781234567812345678")}), }, sigErr: errors.New("not signing that"), errStr: "sign tree head: not signing", }, { desc: "empty sig", slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{RootHash: []byte("12345678123456781234567812345678")}), }, sig: []byte{}, errStr: "sign tree head: ", }, { desc: "ok", slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Ensure response contains all fields needed for the CT STH. TreeSize: 12345, TimestampNanos: 987654321, RootHash: []byte("12345678123456781234567812345678")}), }, sig: []byte("signedit"), wantSTH: &ct.SignedTreeHead{ Timestamp: 987, SHA256RootHash: hashFromString("12345678123456781234567812345678"), TreeHeadSignature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.SignatureAlgorithmFromPubKey(tls.Anonymous), }, Signature: []byte("signedit"), }, TreeSize: 12345, }, }, } } // mirrorTests apply only to the MirrorSTHGetter where sth is read from a store. func mirrorTests(t *testing.T) []testCase { t.Helper() return []testCase{ { desc: "bad mirror storage", ms: &fakeMirrorSTHStorage{ err: errors.New("mirror store failed"), }, slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Ensure response contains all fields needed for the CT STH. TreeSize: 12345, TimestampNanos: 987654321, RootHash: []byte("12345678123456781234567812345678")}), }, errStr: "mirror store failed", }, { desc: "ok", ms: &fakeMirrorSTHStorage{ sth: &ct.SignedTreeHead{ Timestamp: 987, SHA256RootHash: hashFromString("12345678123456781234567812345678"), TreeHeadSignature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.SignatureAlgorithmFromPubKey(tls.Anonymous), }, Signature: []byte("signedit"), }, TreeSize: 12345, }, }, slr: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{ // Ensure response contains all fields needed for the CT STH. TreeSize: 12345, TimestampNanos: 987654321, RootHash: []byte("12345678123456781234567812345678")}), }, wantSTH: &ct.SignedTreeHead{ Timestamp: 987, SHA256RootHash: hashFromString("12345678123456781234567812345678"), TreeHeadSignature: ct.DigitallySigned{ Algorithm: tls.SignatureAndHashAlgorithm{ Hash: tls.SHA256, Signature: tls.SignatureAlgorithmFromPubKey(tls.Anonymous), }, Signature: []byte("signedit"), }, TreeSize: 12345, }, }, } } func TestLogSTHGetter(t *testing.T) { // Note: Does not test signature cache interaction as this is inside // signV1TreeHead and covered by other tests. tests := make([]testCase, 0, 30) tests = append(tests, commonTests(t)...) tests = append(tests, logTests(t)...) for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { ctrl := gomock.NewController(t) rpcCl := mockclient.NewMockTrillianLogClient(ctrl) if tc.slr != nil || tc.slrErr != nil { rpcCl.EXPECT().GetLatestSignedLogRoot(gomock.Any(), cmpMatcher{&trillian.GetLatestSignedLogRootRequest{LogId: 99}}).Return(tc.slr, tc.slrErr) } sthg := LogSTHGetter{li: &logInfo{rpcClient: rpcCl, logID: 99, signer: &fakeSigner{sig: tc.sig, err: tc.sigErr}}} ctx := context.Background() if tc.ctxSetup != nil { ctx = tc.ctxSetup(ctx) } sth, err := sthg.GetSTH(ctx) if len(tc.errStr) > 0 { if err == nil || !strings.Contains(err.Error(), tc.errStr) { t.Errorf("GetSTH()=%v, %v want: nil, err containing %s", sth, err, tc.errStr) } } else { if err != nil || !reflect.DeepEqual(sth, tc.wantSTH) { t.Errorf("GetSTH()=%v, %v, want: %v, nil", sth, err, tc.wantSTH) } } ctrl.Finish() }) } } func TestMirrorSTHGetter(t *testing.T) { // Note: This does not test the operation of MirrorSTHStorage. Implementations // of this need their own tests. tests := make([]testCase, 0, 30) tests = append(tests, commonTests(t)...) tests = append(tests, mirrorTests(t)...) for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { ctrl := gomock.NewController(t) rpcCl := mockclient.NewMockTrillianLogClient(ctrl) if tc.slr != nil || tc.slrErr != nil { rpcCl.EXPECT().GetLatestSignedLogRoot(gomock.Any(), cmpMatcher{&trillian.GetLatestSignedLogRootRequest{LogId: 99}}).Return(tc.slr, tc.slrErr) } sthg := MirrorSTHGetter{li: &logInfo{rpcClient: rpcCl, logID: 99}, st: tc.ms} ctx := context.Background() if tc.ctxSetup != nil { ctx = tc.ctxSetup(ctx) } sth, err := sthg.GetSTH(ctx) if len(tc.errStr) > 0 { if err == nil || !strings.Contains(err.Error(), tc.errStr) { t.Errorf("GetSTH()=%v, %v want: nil, err containing %s", sth, err, tc.errStr) } } else { if err != nil || !reflect.DeepEqual(sth, tc.wantSTH) { t.Errorf("GetSTH()=%v, %v, want: %v, nil", sth, err, tc.wantSTH) } } ctrl.Finish() }) } } func TestFrozenSTHGetter(t *testing.T) { sth := &ct.SignedTreeHead{TreeSize: 123, Version: 1} f := FrozenSTHGetter{sth: sth} // This should always return its canned value and never an error. if sth2, err := f.GetSTH(context.Background()); sth2 != sth || err != nil { t.Fatalf("FrozenSTHGetter.GetSTH()=%v, %v, want: %v, nil", sth2, err, sth) } } func TestDefaultMirrorSTHStorage(t *testing.T) { s, err := DefaultMirrorSTHFactory{}.NewStorage([32]byte{}) if err != nil { t.Fatalf("NewStorage()=%v, %v, want: no err", s, err) } // We expect a "not implemented" error from this and nil sth. sth, err := s.GetMirrorSTH(context.Background(), 9999) if sth != nil || err == nil || !strings.Contains(err.Error(), "not impl") { t.Fatalf("MirrorSTHStorage.GetMirrorSTH(): got: %v, %v, want: nil, err containing 'not impl'", sth, err) } } type fakeSigner struct { sig []byte err error } func (f *fakeSigner) Public() crypto.PublicKey { return []byte("this key is public") // This will map to tls.Anonymous. } func (f *fakeSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { return f.sig, f.err } type fakeMirrorSTHStorage struct { sth *ct.SignedTreeHead err error } func (f *fakeMirrorSTHStorage) GetMirrorSTH(ctx context.Context, maxTreeSize int64) (*ct.SignedTreeHead, error) { return f.sth, f.err } func hashFromString(str string) [32]byte { var hash = [32]byte{} copy(hash[:], []byte(str)) return hash } google-certificate-transparency-go-2308f62/trillian/ctfe/storage/000077500000000000000000000000001462611535200250445ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/storage/mysql/000077500000000000000000000000001462611535200262115ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/storage/mysql/mysql.go000066400000000000000000000064221462611535200277110ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package mysql defines the IssuanceChainStorage type, which implements IssuanceChainStorage interface with FindByKey and Add methods. package mysql import ( "context" "database/sql" "errors" "fmt" "strings" "k8s.io/klog/v2" "github.com/go-sql-driver/mysql" ) const ( selectIssuanceChainByKeySQL = "SELECT c.ChainValue FROM IssuanceChain AS c WHERE c.IdentityHash = ?" insertIssuanceChainSQL = "INSERT INTO IssuanceChain(IdentityHash, ChainValue) VALUES (?, ?)" ) // IssuanceChainStorage is a MySQL implementation of the IssuanceChainStorage interface. type IssuanceChainStorage struct { db *sql.DB } // NewIssuanceChainStorage takes the database connection string as the input and return the IssuanceChainStorage. func NewIssuanceChainStorage(ctx context.Context, dbConn string) *IssuanceChainStorage { db, err := open(ctx, dbConn) if err != nil { klog.Exitf(fmt.Sprintf("failed to open database: %v", err)) } return &IssuanceChainStorage{ db: db, } } // FindByKey returns the key-value pair of issuance chain by the key. func (s *IssuanceChainStorage) FindByKey(ctx context.Context, key []byte) ([]byte, error) { row := s.db.QueryRowContext(ctx, selectIssuanceChainByKeySQL, key) if err := row.Err(); err != nil { return nil, err } var chain []byte if err := row.Scan(&chain); err != nil { return nil, err } return chain, nil } // Add inserts the key-value pair of issuance chain. func (s *IssuanceChainStorage) Add(ctx context.Context, key []byte, chain []byte) error { _, err := s.db.ExecContext(ctx, insertIssuanceChainSQL, key, chain) if err != nil { // Ignore duplicated key error. var mysqlErr *mysql.MySQLError if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 { return nil } return err } return nil } // open takes the data source name and returns the sql.DB object. func open(ctx context.Context, dataSourceName string) (*sql.DB, error) { // Verify data source name format. conn := strings.Split(dataSourceName, "://") if len(conn) != 2 { return nil, errors.New("could not parse MySQL data source name") } if conn[0] != "mysql" { return nil, errors.New("expect data source name to start with mysql") } db, err := sql.Open("mysql", conn[1]) if err != nil { // Don't log data source name as it could contain credentials. klog.Errorf("could not open MySQL database, check config: %s", err) return nil, err } // Enable strict SQL mode to ensure consistent behaviour among different storage engines when handling invalid or missing values in data-change statements. if _, err := db.ExecContext(ctx, "SET sql_mode = 'STRICT_ALL_TABLES'"); err != nil { klog.Warningf("failed to set strict mode on mysql db: %s", err) return nil, err } return db, nil } google-certificate-transparency-go-2308f62/trillian/ctfe/storage/mysql/mysql_test.go000066400000000000000000000057261462611535200307560ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mysql import ( "bytes" "context" "crypto/sha256" "database/sql" "os" "testing" "github.com/DATA-DOG/go-sqlmock" ) func TestIssuanceChainFindByKeySuccess(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer func() { mock.ExpectClose() if err := db.Close(); err != nil { t.Error(err) } }() testVal := readTestData(t, "leaf00.chain") testKey := sha256.Sum256(testVal) issuanceChainMockRows := sqlmock.NewRows([]string{"ChainValue"}).AddRow(testVal) mock.ExpectQuery(selectIssuanceChainByKeySQL).WillReturnRows(issuanceChainMockRows) storage := mockIssuanceChainStorage(db) got, err := storage.FindByKey(context.Background(), testKey[:]) if err != nil { t.Errorf("issuanceChainStorage.FindByKey: %v", err) } if !bytes.Equal(got, testVal) { t.Errorf("got: %v, want: %v", got, testVal) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } func TestIssuanceChainAddSuccess(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer func() { mock.ExpectClose() if err := db.Close(); err != nil { t.Error(err) } }() tests := setupTestData(t, "leaf00.chain", "leaf01.chain", "leaf02.chain", ) storage := mockIssuanceChainStorage(db) for k, v := range tests { mock.ExpectExec("INSERT INTO IssuanceChain").WithArgs([]byte(k), v).WillReturnResult(sqlmock.NewResult(1, 1)) if err := storage.Add(context.Background(), []byte(k), v); err != nil { t.Errorf("issuanceChainStorage.Add: %v", err) } } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } func readTestData(t *testing.T, filename string) []byte { t.Helper() data, err := os.ReadFile("../../../testdata/" + filename) if err != nil { t.Fatal(err) } return data } func setupTestData(t *testing.T, filenames ...string) map[string][]byte { t.Helper() data := make(map[string][]byte, len(filenames)) for _, filename := range filenames { val := readTestData(t, filename) key := sha256.Sum256(val) data[string(key[:])] = val } return data } func mockIssuanceChainStorage(db *sql.DB) *IssuanceChainStorage { return &IssuanceChainStorage{ db: db, } } google-certificate-transparency-go-2308f62/trillian/ctfe/storage/mysql/schema.sql000066400000000000000000000020021462611535200301640ustar00rootroot00000000000000-- Copyright 2024 Google LLC -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- MySQL / MariaDB version of the CTFE database schema -- "IssuanceChain" table contains the hash and value pairs of the issuance chain. CREATE TABLE IF NOT EXISTS `IssuanceChain` ( -- Hash of the chain of intermediate certificates and root certificates. `IdentityHash` VARBINARY(255) NOT NULL, -- Chain data of intermediate certificates and root certificates. `ChainValue` LONGBLOB NOT NULL, PRIMARY KEY (`IdentityHash`) ); google-certificate-transparency-go-2308f62/trillian/ctfe/storage/storage.go000066400000000000000000000041351462611535200270420ustar00rootroot00000000000000// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package storage defines the IssuanceChainStorage type, which allows different storage implementation for the key-value pairs of issuance chains. package storage import ( "context" "errors" "strings" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/trillian/ctfe/storage/mysql" ) // IssuanceChainStorage is an interface which allows CTFE binaries to use different storage implementations for issuance chains. type IssuanceChainStorage interface { // FindByKey returns the issuance chain associated with the provided key. FindByKey(ctx context.Context, key []byte) ([]byte, error) // Add inserts the key-value pair of issuance chain. Add(ctx context.Context, key []byte, chain []byte) error } // NewIssuanceChainStorage returns nil for Trillian gRPC or mysql.IssuanceChainStorage when MySQL is the prefix in database connection string. func NewIssuanceChainStorage(ctx context.Context, backend configpb.LogConfig_IssuanceChainStorageBackend, dbConn string) (IssuanceChainStorage, error) { switch backend { case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC: return nil, nil case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE: if strings.HasPrefix(dbConn, "mysql") { return mysql.NewIssuanceChainStorage(ctx, dbConn), nil } return nil, errors.New("failed to initialise IssuanceChainService due to unsupported driver in CTFE storage connection string") } return nil, errors.New("unsupported issuance chain storage backend") } google-certificate-transparency-go-2308f62/trillian/ctfe/structures.go000066400000000000000000000023251462611535200261540ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe // Code to handle encoding / decoding various data structures used in RFC 6962. Does not // contain the low level serialization. import ( "crypto" "crypto/sha256" "github.com/google/certificate-transparency-go/x509" ) const millisPerNano int64 = 1000 * 1000 // GetCTLogID takes the key manager for a log and returns the LogID. (see RFC 6962 S3.2) // In CT V1 the log id is a hash of the public key. func GetCTLogID(pk crypto.PublicKey) ([sha256.Size]byte, error) { pubBytes, err := x509.MarshalPKIXPublicKey(pk) if err != nil { return [sha256.Size]byte{}, err } return sha256.Sum256(pubBytes), nil } google-certificate-transparency-go-2308f62/trillian/ctfe/structures_test.go000066400000000000000000000035451462611535200272200ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "crypto" "crypto/x509" "encoding/pem" "testing" "time" "github.com/google/certificate-transparency-go/trillian/testdata" ) var ( fixedTime = time.Date(2017, 9, 7, 12, 15, 23, 0, time.UTC) fixedTimeMillis = uint64(fixedTime.UnixNano() / millisPerNano) demoLogID = [32]byte{19, 56, 222, 93, 229, 36, 102, 128, 227, 214, 3, 121, 93, 175, 126, 236, 97, 217, 34, 32, 40, 233, 98, 27, 46, 179, 164, 251, 84, 10, 60, 57} fakeSignature = []byte("signed") ) func TestGetCTLogID(t *testing.T) { block, _ := pem.Decode([]byte(testdata.DemoPublicKey)) pk, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatalf("unexpected error loading public key: %v", err) } got, err := GetCTLogID(pk) if err != nil { t.Fatalf("error getting logid: %v", err) } if want := demoLogID; got != want { t.Errorf("logID: \n%v want \n%v", got, want) } } // Creates a fake signer for use in interaction tests. // It will always return fakeSig when asked to sign something. func setupSigner(fakeSig []byte) (crypto.Signer, error) { block, _ := pem.Decode([]byte(testdata.DemoPublicKey)) key, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, err } return testdata.NewSignerWithFixedSig(key, fakeSig), nil } google-certificate-transparency-go-2308f62/trillian/ctfe/testonly/000077500000000000000000000000001462611535200252615ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/ctfe/testonly/certificates.go000066400000000000000000001434061462611535200302650ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testonly // CACertPEM is a valid test CA certificate. // // Data: // Version: 3 (0x2) // Serial Number: 0 (0x0) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:d5:8a:68:53:62:10:a2:71:19:93:6e:77:83:21: // 18:1c:2a:40:13:c6:d0:7b:8c:76:eb:91:57:d3:d0: // fb:4b:3b:51:6e:ce:cb:d1:c9:8d:91:c5:2f:74:3f: // ab:63:5d:55:09:9c:d1:3a:ba:f3:1a:e5:41:44:24: // 51:a7:4c:78:16:f2:24:3c:f8:48:cf:28:31:cc:e6: // 7b:a0:4a:5a:23:81:9f:3c:ba:37:e6:24:d9:c3:bd: // b2:99:b8:39:dd:fe:26:31:d2:cb:3a:84:fc:7b:b2: // b5:c5:2f:cf:c1:4f:ff:40:6f:5c:d4:46:69:cb:b2: // f7:cf:df:86:fb:6a:b9:d1:b1 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:TRUE // Signature Algorithm: sha1WithRSAEncryption // 06:08:cc:4a:6d:64:f2:20:5e:14:6c:04:b2:76:f9:2b:0e:fa: // 94:a5:da:f2:3a:fc:38:06:60:6d:39:90:d0:a1:ea:23:3d:40: // 29:57:69:46:3b:04:66:61:e7:fa:1d:17:99:15:20:9a:ea:2e: // 0a:77:51:76:41:12:27:d7:c0:03:07:c7:47:0e:61:58:4f:d7: // 33:42:24:72:7f:51:d6:90:bc:47:a9:df:35:4d:b0:f6:eb:25: // 95:5d:e1:89:3c:4d:d5:20:2b:24:a2:f3:e4:40:d2:74:b5:4e: // 1b:d3:76:26:9c:a9:62:89:b7:6e:ca:a4:10:90:e1:4f:3b:0a: // 94:2e const CACertPEM = ` -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE-----` // FakeCACertPEM is a test CA cert for testing. // // Data: // Version: 3 (0x2) // Serial Number: // b6:31:d2:ac:21:ab:65:20 // Signature Algorithm: sha256WithRSAEncryption // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Jul 11 12:23:26 2016 GMT // Not After : Jul 11 12:23:26 2017 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (2048 bit) // Modulus: // 00:a5:41:9a:7a:2d:98:a3:b5:78:6f:15:21:db:0c: // c1:0e:a1:f8:26:f5:b3:b2:67:85:dc:a1:e6:b7:83: // 6d:da:63:da:d0:f6:a3:ff:bc:43:f5:2b:9f:00:19: // 6e:6b:60:4b:43:20:6e:e2:cb:2e:b6:65:ed:9b:dc: // 80:c3:e1:5a:96:af:60:78:0e:0e:fb:8f:ea:3e:3d: // c9:67:8f:a4:57:1c:ba:e4:f3:37:a9:2f:dd:11:9d: // 10:5d:e5:d6:ef:d4:3b:06:d9:34:43:42:bb:bb:be: // 43:40:2b:e3:b6:d1:b5:6c:58:12:34:96:14:d4:fc: // 49:79:c5:26:8c:24:7d:b3:12:f5:f6:3e:b7:41:46: // 6b:6d:3a:41:fd:7c:e3:b5:fc:96:6c:c6:cc:ad:8d: // 48:09:73:44:64:ea:4f:17:1d:0a:4b:14:5a:19:07: // 4a:32:0f:41:2e:e4:85:bd:a1:e1:9b:de:63:7c:3b: // bc:ec:aa:93:2a:0b:a8:c7:24:34:54:42:38:a5:d1: // 0c:c4:f9:9e:7c:69:42:71:77:d7:95:aa:bb:13:3d: // f3:cc:c7:5d:b3:fd:76:25:25:e3:da:14:0e:59:81: // e8:2c:58:e8:09:29:7d:22:02:91:95:81:eb:55:6f: // 2f:17:b9:af:4a:f3:84:8b:24:6e:ea:14:6b:bb:90: // 84:35 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // Signature Algorithm: sha256WithRSAEncryption // 92:be:33:eb:d5:d4:32:e7:9e:4e:65:2a:e8:3f:67:b8:f4:d7: // 34:ab:95:11:6a:5d:ba:fd:57:9b:94:6e:8d:20:be:fb:7a:e1: // 49:ca:39:ea:92:d3:81:5a:b1:87:a3:9f:50:a4:e0:1e:11:de: // c4:d1:07:a1:ca:d1:97:1a:92:bd:73:9a:11:ec:6a:9a:52:11: // 2d:40:e1:3b:4f:3c:1f:81:3f:4c:ab:6a:02:84:4f:8b:18:36: // 7a:cc:5c:a9:0e:25:2b:cd:57:53:88:d9:eb:82:b1:ce:62:76: // 56:d4:23:9e:01:b3:6d:2b:49:ea:d4:3a:c2:f5:76:a7:b3:2d: // 24:97:6f:b4:1c:74:6b:95:85:f6:b5:41:56:82:3c:ed:be:96: // 1e:5e:6a:2d:7b:f7:fd:7d:6e:3f:fb:c2:ec:61:b3:7c:7f:3b: // f5:9c:64:61:5f:02:93:87:cd:81:f9:7e:53:3e:c1:f5:79:85: // f4:41:87:c7:ca:bd:af:ab:2b:a4:aa:a8:1d:2c:50:ad:23:8f: // db:13:1d:71:8a:85:bd:ac:59:6c:c4:53:c5:71:0c:90:91:f3: // 0b:41:ef:da:6e:27:bb:09:57:9c:97:b9:d7:fc:20:96:c5:75: // 96:ce:2e:6c:a8:b6:6e:b0:4d:0f:3e:01:95:ea:8b:cd:ae:47: // d0:d9:01:b7 const FakeCACertPEM = ` -----BEGIN CERTIFICATE----- MIIDrDCCApSgAwIBAgIJALYx0qwhq2UgMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0GA1UE CgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZpY2F0 ZUF1dGhvcml0eTAeFw0xNjA3MTExMjIzMjZaFw0xNzA3MTExMjIzMjZaMHExCzAJ BgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0G A1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZp Y2F0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKVB mnotmKO1eG8VIdsMwQ6h+Cb1s7Jnhdyh5reDbdpj2tD2o/+8Q/UrnwAZbmtgS0Mg buLLLrZl7ZvcgMPhWpavYHgODvuP6j49yWePpFccuuTzN6kv3RGdEF3l1u/UOwbZ NENCu7u+Q0Ar47bRtWxYEjSWFNT8SXnFJowkfbMS9fY+t0FGa206Qf1847X8lmzG zK2NSAlzRGTqTxcdCksUWhkHSjIPQS7khb2h4ZveY3w7vOyqkyoLqMckNFRCOKXR DMT5nnxpQnF315WquxM988zHXbP9diUl49oUDlmB6CxY6AkpfSICkZWB61VvLxe5 r0rzhIskbuoUa7uQhDUCAwEAAaNHMEUwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgw BoAEAQIDBDASBgNVHRMBAf8ECDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwDQYJ KoZIhvcNAQELBQADggEBAJK+M+vV1DLnnk5lKug/Z7j01zSrlRFqXbr9V5uUbo0g vvt64UnKOeqS04FasYejn1Ck4B4R3sTRB6HK0Zcakr1zmhHsappSES1A4TtPPB+B P0yragKET4sYNnrMXKkOJSvNV1OI2euCsc5idlbUI54Bs20rSerUOsL1dqezLSSX b7QcdGuVhfa1QVaCPO2+lh5eai179/19bj/7wuxhs3x/O/WcZGFfApOHzYH5flM+ wfV5hfRBh8fKva+rK6SqqB0sUK0jj9sTHXGKhb2sWWzEU8VxDJCR8wtB79puJ7sJ V5yXudf8IJbFdZbOLmyotm6wTQ8+AZXqi82uR9DZAbc= -----END CERTIFICATE-----` // PrecertPEMValid is a test certificate containing a valid CT precertificate // extension. // // Data: // Version: 3 (0x2) // Serial Number: 7 (0x7) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c: // 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1: // ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b: // 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4: // dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10: // cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87: // 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d: // c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a: // 05:b0:14:09:ff:5d:d8:7e:b5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // CT Precertificate Poison: critical // .. // Signature Algorithm: sha1WithRSAEncryption // 02:a1:c3:9e:01:5a:f5:4d:ff:02:3c:33:60:87:5f:ff:34:37: // 55:2f:1f:09:01:bd:c2:54:31:5f:33:72:b7:23:fb:15:fb:ce: // cc:4d:f4:71:a0:ce:4d:8c:54:65:5d:84:87:97:fb:28:1e:3d: // fa:bb:46:2d:2c:68:4b:05:6f:ea:7b:63:b4:70:ff:16:6e:32: // d4:46:06:35:b3:d2:bc:6d:a8:24:9b:26:30:e7:1f:c3:4f:08: // f2:3d:d4:ee:22:8f:8f:74:f6:3d:78:63:11:dd:0a:58:11:40: // 5f:90:6c:ca:2c:2d:3e:eb:fc:81:99:64:eb:d8:cf:7c:08:86: // 3f:be const PrecertPEMValid = ` -----BEGIN CERTIFICATE----- MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/ BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3 tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf kGzKLC0+6/yBmWTr2M98CIY/vg== -----END CERTIFICATE-----` // TestCertPEM is a certificate issued by CACertPEM, no CT extensions. // // Data: // Version: 3 (0x2) // Serial Number: 6 (0x6) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4: // 19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04: // 16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34: // 1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56: // 46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc: // a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02: // ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d: // d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65: // b1:a3:7a:8c:67:62:53:dc:c1 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:FALSE // Signature Algorithm: sha1WithRSAEncryption // 17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2: // 70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae: // fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f: // 71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5: // 06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58: // 2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71: // 90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da: // c0:e6 const TestCertPEM = ` -----BEGIN CERTIFICATE----- MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ 4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx /KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY 4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6 n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g== -----END CERTIFICATE-----` // FakeIntermediateCertPEM is a test intermediate CA cert. // // Data: // Version: 3 (0x2) // Serial Number: 4792439526061490155 (0x42822a5b866fbfeb) // Signature Algorithm: sha256WithRSAEncryption // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: May 13 14:26:44 2016 GMT // Not After : Jul 12 14:26:44 2019 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (2048 bit) // Modulus: // 00:ca:a4:0c:7a:6d:e9:26:22:d4:67:19:c8:29:40: // c6:bd:cb:44:39:e7:fa:84:01:1d:b3:04:15:48:37: // fa:55:d5:98:4b:2a:ff:14:0e:d6:ce:27:6b:29:d5: // e8:8d:39:eb:be:97:be:53:21:d2:a3:f2:27:ef:46: // 68:1c:6f:84:77:85:b4:68:78:7a:d4:3d:50:49:89: // 8f:9e:6b:4a:ce:74:c0:0f:c8:68:38:7e:ae:82:ae: // 91:0c:6d:87:24:c4:48:f3:e0:8e:a8:3e:0c:f8:e1: // e8:7f:a1:dd:29:f4:d0:eb:3a:b2:38:77:0f:1a:4e: // a6:14:c4:b1:db:5b:ed:f9:a4:f0:9d:1e:d8:a8:d0: // 40:28:d6:fc:69:44:0b:37:37:e7:d6:fd:29:b0:70: // 36:47:00:89:81:5a:c9:51:cf:2d:a0:80:76:fc:d8: // 57:28:87:81:71:e4:10:4b:39:16:51:f2:85:ed:a0: // 34:41:bf:f3:52:28:f1:cd:c4:dc:31:f9:26:14:fd: // b6:65:51:2f:76:e9:82:94:fc:2a:be:1a:a0:58:54: // d8:b5:de:e3:96:08:07:50:3d:0e:35:26:e5:3a:c7: // 67:e8:8d:b6:f1:34:61:f6:0c:47:d2:fd:0b:51:cf: // a6:99:97:d4:26:a1:12:14:dd:a2:0e:e5:68:4d:75: // f7:c5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:0 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // Signature Algorithm: sha256WithRSAEncryption // 01:e2:3a:0c:00:bc:4c:e1:ac:d3:10:54:0c:fc:6b:e4:ac:c8: // c2:00:05:74:39:3f:c5:9b:25:e1:e3:90:88:a9:13:8f:b9:66: // 99:2b:65:55:ea:f6:9f:30:39:d9:18:9c:e1:f1:e1:63:62:f4: // f5:46:41:b2:c6:f4:8b:9f:87:d7:e9:93:c7:32:c9:15:83:8b: // e5:76:d3:f0:8d:36:d6:b0:32:ad:c2:95:5d:dd:58:2f:7c:4e: // 3e:16:5f:f0:57:0c:27:98:da:32:b8:8d:81:95:f9:db:38:dc: // 76:15:d1:3a:01:9a:fb:eb:71:ca:bf:53:bc:d8:30:61:5c:42: // 22:81:0a:5c:f9:6d:31:3e:18:cb:eb:65:67:0e:e4:0f:cb:87: // 7f:22:d9:84:85:d6:2f:12:7c:35:67:00:e0:65:02:06:66:96: // 57:21:78:7a:46:b1:67:d2:9d:db:88:96:55:2f:4e:c4:6f:10: // 8b:1a:6a:a7:d5:2e:5e:50:a5:15:c1:3a:af:2d:6e:32:bc:e7: // fd:a0:e9:e6:ab:d6:8c:4f:84:9d:70:f6:17:6c:f9:64:c5:5e: // 49:87:91:6b:ca:25:e6:d8:d7:7b:77:39:f4:a3:03:28:5a:45: // 2b:7c:85:dc:c3:cc:74:c5:c2:33:e3:1d:3f:21:e9:d5:3b:fe: // 13:1d:91:48 const FakeIntermediateCertPEM = ` -----BEGIN CERTIFICATE----- MIIDnTCCAoWgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQK DAZHb29nbGUxDDAKBgNVBAsMA0VuZzEhMB8GA1UEAwwYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE2MDUxMzE0MjY0NFoXDTE5MDcxMjE0MjY0NFowcjELMAkG A1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYD VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVk aWF0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqk DHpt6SYi1GcZyClAxr3LRDnn+oQBHbMEFUg3+lXVmEsq/xQO1s4naynV6I05676X vlMh0qPyJ+9GaBxvhHeFtGh4etQ9UEmJj55rSs50wA/IaDh+roKukQxthyTESPPg jqg+DPjh6H+h3Sn00Os6sjh3DxpOphTEsdtb7fmk8J0e2KjQQCjW/GlECzc359b9 KbBwNkcAiYFayVHPLaCAdvzYVyiHgXHkEEs5FlHyhe2gNEG/81Io8c3E3DH5JhT9 tmVRL3bpgpT8Kr4aoFhU2LXe45YIB1A9DjUm5TrHZ+iNtvE0YfYMR9L9C1HPppmX 1CahEhTdog7laE1198UCAwEAAaM4MDYwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMB Af8ECDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwDQYJKoZIhvcNAQELBQADggEB AAHiOgwAvEzhrNMQVAz8a+SsyMIABXQ5P8WbJeHjkIipE4+5ZpkrZVXq9p8wOdkY nOHx4WNi9PVGQbLG9Iufh9fpk8cyyRWDi+V20/CNNtawMq3ClV3dWC98Tj4WX/BX DCeY2jK4jYGV+ds43HYV0ToBmvvrccq/U7zYMGFcQiKBClz5bTE+GMvrZWcO5A/L h38i2YSF1i8SfDVnAOBlAgZmllcheHpGsWfSnduIllUvTsRvEIsaaqfVLl5QpRXB Oq8tbjK85/2g6ear1oxPhJ1w9hds+WTFXkmHkWvKJebY13t3OfSjAyhaRSt8hdzD zHTFwjPjHT8h6dU7/hMdkUg= -----END CERTIFICATE-----` // LeafSignedByFakeIntermediateCertPEM is a test cert signed by the intermediate CA. // // Data: // Version: 3 (0x2) // Serial Number: 4792439526061490155 (0x42822a5b866fbfeb) // Signature Algorithm: sha256WithRSAEncryption // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Validity // Not Before: May 13 14:26:44 2016 GMT // Not After : Jul 12 14:26:44 2019 GMT // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, CN=*.google.com, SN=RFC5280 s4.2.1.9 'The pathLenConstraint field ... gives the maximum number of non-self-issued intermediate certificates that may follow this certificate in a valid certification path.', GN=Intermediate CA cert used to sign // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // 04:c4:09:39:84:f5:15:8d:12:54:b2:02:9c:f9:01: // e2:6d:35:47:d4:0d:d0:11:61:66:09:35:1d:cb:12: // 14:95:b2:3f:ff:35:bd:22:8e:4d:fc:38:50:2d:22: // d6:98:1e:ca:a0:23:af:a4:96:7e:32:d1:82:5f:31: // 57:fb:28:ff:37 // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Extended Key Usage: // TLS Web Server Authentication, TLS Web Client Authentication // X509v3 Subject Alternative Name: // DNS:*.google.com, DNS:*.android.com, DNS:*.appengine.google.com, DNS:*.cloud.google.com, // DNS:*.google-analytics.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, // DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, // DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, // DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, // DNS:*.googleadapis.com, DNS:*.googleapis.cn, DNS:*.googlecommerce.com, DNS:*.googlevideo.com, // DNS:*.gstatic.cn, DNS:*.gstatic.com, DNS:*.gvt1.com, DNS:*.gvt2.com, DNS:*.metric.gstatic.com, // DNS:*.urchin.com, DNS:*.url.google.com, DNS:*.youtube-nocookie.com, DNS:*.youtube.com, // DNS:*.youtubeeducation.com, DNS:*.ytimg.com, DNS:android.clients.google.com, DNS:android.com, DNS:g.co, // DNS:goo.gl, DNS:google-analytics.com, DNS:google.com, DNS:googlecommerce.com, DNS:urchin.com, // DNS:youtu.be, DNS:youtube.com, DNS:youtubeeducation.com // X509v3 Key Usage: // Digital Signature // Authority Information Access: // CA Issuers - URI:http://pki.google.com/GIAG2.crt // OCSP - URI:http://clients1.google.com/ocsp // // X509v3 Subject Key Identifier: // DB:F4:6E:63:EE:E2:DC:BE:BF:38:60:4F:98:31:D0:64:44:F1:63:D8 // X509v3 Basic Constraints: critical // CA:FALSE // X509v3 Certificate Policies: // Policy: 1.3.6.1.4.1.11129.2.5.1 // Policy: 2.23.140.1.2.2 // // X509v3 CRL Distribution Points: // // Full Name: // URI:http://pki.google.com/GIAG2.crl // // Signature Algorithm: sha256WithRSAEncryption // 0e:a6:6f:79:7d:38:4b:60:f0:c1:76:9c:4e:92:f5:24:ce:12: // 34:72:94:95:8d:cf:1c:0c:d6:78:6b:ee:66:2b:50:36:22:7a: // be:ff:22:c7:dd:93:2c:40:83:2f:a0:37:29:8f:bb:98:22:bf: // 8e:c6:6c:b4:8b:8f:e9:1e:0f:bd:8a:df:df:f5:c9:aa:79:ac: // 00:e6:ca:a6:1a:74:8e:67:f9:5f:09:82:3c:f9:b4:5b:30:85: // 0b:ae:28:c2:b8:9c:23:7c:6a:59:66:ca:8e:bd:20:6e:20:e4: // b3:46:f8:06:56:99:5c:b3:47:62:b6:e4:f6:92:10:85:ae:46: // e5:c1:af:c1:a8:8a:b3:b6:f3:fb:2e:e1:26:56:98:e4:aa:de: // 29:0b:71:ef:0f:45:d4:c6:ce:4f:21:d6:59:18:89:df:7a:ac: // a6:93:97:de:45:e5:87:06:e3:c7:a4:f2:14:39:b2:b1:99:0b: // 7e:85:cc:3a:62:c1:c4:fb:40:7c:e1:7b:71:f4:13:1e:e2:aa: // 94:7e:ba:a6:b5:65:e7:f6:e9:c1:c3:1a:92:62:c0:aa:c4:74: // 29:43:ee:f4:a6:6b:81:c6:50:7d:b3:a2:d2:b4:8c:c4:f6:cc: // 9a:0e:65:32:8f:14:65:8c:a0:30:20:d5:7a:cf:48:fb:84:a4: // 3a:30:fa:44 const LeafSignedByFakeIntermediateCertPEM = ` -----BEGIN CERTIFICATE----- MIIH6DCCBtCgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQK DAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVkaWF0 ZUF1dGhvcml0eTAeFw0xNjA1MTMxNDI2NDRaFw0xOTA3MTIxNDI2NDRaMIIBWDEL MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50 YWluIFZpZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMxFTATBgNVBAMMDCouZ29vZ2xl LmNvbTGBwzCBwAYDVQQEDIG4UkZDNTI4MCBzNC4yLjEuOSAnVGhlIHBhdGhMZW5D b25zdHJhaW50IGZpZWxkIC4uLiBnaXZlcyB0aGUgbWF4aW11bSBudW1iZXIgb2Yg bm9uLXNlbGYtaXNzdWVkIGludGVybWVkaWF0ZSBjZXJ0aWZpY2F0ZXMgdGhhdCBt YXkgZm9sbG93IHRoaXMgY2VydGlmaWNhdGUgaW4gYSB2YWxpZCBjZXJ0aWZpY2F0 aW9uIHBhdGguJzEqMCgGA1UEKgwhSW50ZXJtZWRpYXRlIENBIGNlcnQgdXNlZCB0 byBzaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExAk5hPUVjRJUsgKc+QHi bTVH1A3QEWFmCTUdyxIUlbI//zW9Io5N/DhQLSLWmB7KoCOvpJZ+MtGCXzFX+yj/ N6OCBGMwggRfMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCCA0IGA1Ud EQSCAzkwggM1ggwqLmdvb2dsZS5jb22CDSouYW5kcm9pZC5jb22CFiouYXBwZW5n aW5lLmdvb2dsZS5jb22CEiouY2xvdWQuZ29vZ2xlLmNvbYIWKi5nb29nbGUtYW5h bHl0aWNzLmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2dsZS5j by5pboIOKi5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2dsZS5j b20uYXKCDyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdvb2ds ZS5jb20uY2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8qLmdv b2dsZS5jb20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29nbGUu ZnKCCyouZ29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyouZ29v Z2xlLnBsggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdvb2ds ZWFwaXMuY26CFCouZ29vZ2xlY29tbWVyY2UuY29tghEqLmdvb2dsZXZpZGVvLmNv bYIMKi5nc3RhdGljLmNugg0qLmdzdGF0aWMuY29tggoqLmd2dDEuY29tggoqLmd2 dDIuY29tghQqLm1ldHJpYy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAqLnVy bC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1YmUu Y29tghYqLnlvdXR1YmVlZHVjYXRpb24uY29tggsqLnl0aW1nLmNvbYIaYW5kcm9p ZC5jbGllbnRzLmdvb2dsZS5jb22CC2FuZHJvaWQuY29tggRnLmNvggZnb28uZ2yC FGdvb2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJj ZS5jb22CCnVyY2hpbi5jb22CCHlvdXR1LmJlggt5b3V0dWJlLmNvbYIUeW91dHVi ZWVkdWNhdGlvbi5jb20wDAYDVR0PBAUDAweAADBoBggrBgEFBQcBAQRcMFowKwYI KwYBBQUHMAKGH2h0dHA6Ly9wa2kuZ29vZ2xlLmNvbS9HSUFHMi5jcnQwKwYIKwYB BQUHMAGGH2h0dHA6Ly9jbGllbnRzMS5nb29nbGUuY29tL29jc3AwHQYDVR0OBBYE FNv0bmPu4ty+vzhgT5gx0GRE8WPYMAwGA1UdEwEB/wQCMAAwIQYDVR0gBBowGDAM BgorBgEEAdZ5AgUBMAgGBmeBDAECAjAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8v cGtpLmdvb2dsZS5jb20vR0lBRzIuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAOpm95 fThLYPDBdpxOkvUkzhI0cpSVjc8cDNZ4a+5mK1A2Inq+/yLH3ZMsQIMvoDcpj7uY Ir+Oxmy0i4/pHg+9it/f9cmqeawA5sqmGnSOZ/lfCYI8+bRbMIULrijCuJwjfGpZ ZsqOvSBuIOSzRvgGVplcs0dituT2khCFrkblwa/BqIqztvP7LuEmVpjkqt4pC3Hv D0XUxs5PIdZZGInfeqymk5feReWHBuPHpPIUObKxmQt+hcw6YsHE+0B84Xtx9BMe 4qqUfrqmtWXn9unBwxqSYsCqxHQpQ+70pmuBxlB9s6LStIzE9syaDmUyjxRljKAw INV6z0j7hKQ6MPpE -----END CERTIFICATE-----` // The next section holds copies and variants of test certs from ../../testdata/ // FakeRootCACertPEM is a root CA taken from ../../testdata/fake-ca.cert. // // Data: // Version: 3 (0x2) // Serial Number: 67554046 (0x406cafe) // Signature Algorithm: ecdsa-with-SHA256 // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Dec 7 15:13:36 2016 GMT // Not After : Dec 5 15:13:36 2026 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // pub: // 04:f2:d3:07:ef:7e:df:cf:ce:f4:f4:0a:5b:bc:9e: // 3f:cb:1c:fd:0c:46:dc:85:fb:c1:f6:d3:b2:ba:1d: // 51:f1:98:6c:48:a8:15:46:45:63:ca:df:d6:c9:ac: // cf:60:3b:c7:4e:dd:b8:d2:16:ab:a0:09:24:1d:09: // 66:1e:4d:eb:a1 // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // Signature Algorithm: ecdsa-with-SHA256 // 30:46:02:21:00:a6:28:49:39:43:6f:80:e4:43:a6:1e:3b:aa: // 89:5e:c2:25:60:2a:e1:39:bd:55:43:ae:4d:5c:a9:a6:ef:ac: // 65:02:21:00:c9:c5:08:c6:59:93:b4:86:70:a5:6b:54:2b:5b: // fc:0c:88:6b:b0:23:07:2b:c7:0c:27:de:87:2d:96:80:d5:56 const FakeRootCACertPEM = ` -----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE-----` // FakeIntermediateWithPolicyConstraintsCertPEM is an intermediate CA cert that includes a // critical PolicyConstraints extension; based on ../../testdata/int-ca.cert. // // Data: // Version: 3 (0x2) // Serial Number: 1111638594 (0x42424242) // Signature Algorithm: ecdsa-with-SHA256 // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Feb 13 09:33:59 2018 GMT // Not After : Dec 23 09:33:59 2027 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // pub: // 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: // 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: // a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: // c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: // 5d:98:5f:5d:5d // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Policy Constraints: critical // Require Explicit Policy:0 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // Signature Algorithm: ecdsa-with-SHA256 // 30:44:02:20:4c:aa:27:8f:d9:83:32:76:40:17:a1:a8:00:1d: // bc:d1:45:b2:53:c6:47:77:48:f1:c3:89:68:5d:f4:7f:5c:52: // 02:20:39:68:40:5c:fd:f0:2a:e2:3f:34:45:b3:19:2d:e3:4d: // 58:cd:76:42:19:09:cf:5c:1c:e5:f1:71:e0:39:62:b9 const FakeIntermediateWithPolicyConstraintsCertPEM = ` -----BEGIN CERTIFICATE----- MIICLDCCAdOgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMjEzMDkzMzU5WhcNMjcxMjIzMDkzMzU5WjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNYMFYwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEKMA8GA1UdJAEB/wQFMAOAAQAwDwYDVR0PAQH/BAUDAwf/gDAKBggq hkjOPQQDAgNHADBEAiBMqieP2YMydkAXoagAHbzRRbJTxkd3SPHDiWhd9H9cUgIg OWhAXP3wKuI/NEWzGS3jTVjNdkIZCc9cHOXxceA5Yrk= -----END CERTIFICATE-----` // FakeIntermediateWithNameConstraintsCertPEM is an intermediate CA cert that includes a // critical NameConstraints extension that disallows the leaf below; based on ../../testdata/int-ca.cert. // // Data: // Version: 3 (0x2) // Serial Number: 1111638594 (0x42424242) // Signature Algorithm: ecdsa-with-SHA256 // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Feb 13 11:33:08 2018 GMT // Not After : Dec 23 11:33:08 2027 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // pub: // 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: // 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: // a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: // c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: // 5d:98:5f:5d:5d // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // X509v3 Name Constraints: // Permitted: // DNS:.csr.pem // Signature Algorithm: ecdsa-with-SHA256 // 30:46:02:21:00:fd:11:41:d8:1f:2b:b5:49:8e:27:6e:70:93: // 2c:f1:c2:e7:b0:a2:40:e2:c6:89:45:fc:99:a5:9b:dc:21:fb: // f6:02:21:00:b7:4f:98:bf:1f:dc:92:e7:db:7c:aa:33:7a:40: // 36:1d:58:19:aa:96:3d:5e:5b:46:5f:47:f6:e3:7d:75:19:4f const FakeIntermediateWithNameConstraintsCertPEM = ` -----BEGIN CERTIFICATE----- MIICNjCCAdugAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMjEzMTEzMzA4WhcNMjcxMjIzMTEzMzA4WjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNgMF4wDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwFwYDVR0eBBAwDqAMMAqCCC5jc3Iu cGVtMAoGCCqGSM49BAMCA0kAMEYCIQD9EUHYHyu1SY4nbnCTLPHC57CiQOLGiUX8 maWb3CH79gIhALdPmL8f3JLn23yqM3pANh1YGaqWPV5bRl9H9uN9dRlP -----END CERTIFICATE-----` // FakeIntermediateWithInvalidNameConstraintsCertPEM is an intermediate CA cert that includes a // critical NameConstraints extension that disallows the leaf below; based on ../../testdata/int-ca.cert. // // Data: // Version: 3 (0x2) // Serial Number: 1111638594 (0x42424242) // Signature Algorithm: ecdsa-with-SHA256 // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Feb 13 11:42:37 2018 GMT // Not After : Dec 23 11:42:37 2027 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // pub: // 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: // 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: // a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: // c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: // 5d:98:5f:5d:5d // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // X509v3 Name Constraints: // Permitted: // DNS:.xyzzy.pem // Signature Algorithm: ecdsa-with-SHA256 // 30:45:02:20:3f:0a:40:60:b6:9e:ea:a5:cd:eb:e4:0e:7c:bc: // 40:22:b2:e2:14:07:e8:ab:fa:4a:85:2a:41:18:20:f0:31:1a: // 02:21:00:a4:64:91:6d:79:47:79:0f:16:06:62:a9:88:8b:92: // 6d:40:fa:54:cb:c9:4f:bc:3f:53:27:e5:cd:12:16:53:7a const FakeIntermediateWithInvalidNameConstraintsCertPEM = ` -----BEGIN CERTIFICATE----- MIICNzCCAd2gAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMjEzMTE0MjM3WhcNMjcxMjIzMTE0MjM3WjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNiMGAwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwGQYDVR0eBBIwEKAOMAyCCi54eXp6 eS5wZW0wCgYIKoZIzj0EAwIDSAAwRQIgPwpAYLae6qXN6+QOfLxAIrLiFAfoq/pK hSpBGCDwMRoCIQCkZJFteUd5DxYGYqmIi5JtQPpUy8lPvD9TJ+XNEhZTeg== -----END CERTIFICATE-----` // LeafCertPEM is a leaf cert signed by the key in: // - FakeIntermediateWithPolicyConstraintsCertPEM // - FakeIntermediateWithNameConstraintsCertPEM // - FakeIntermediateWithInvalidNameConstraintsCertPEM // // adapted from ../../testdata/leaf01.cert. // // Data: // Version: 3 (0x2) // Serial Number: 3735928559 (0xdeadbeef) // Signature Algorithm: ecdsa-with-SHA256 // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority // Validity // Not Before: Feb 13 11:38:39 2018 GMT // Not After : Mar 28 11:38:39 2025 GMT // Subject: C=GB, ST=London, O=Google, OU=Eng, CN=leaf01.csr.pem // Subject Public Key Info: // Public Key Algorithm: id-ecPublicKey // Public-Key: (256 bit) // pub: // 04:eb:37:4e:52:45:9c:46:d5:a8:b8:c5:ed:58:b9: // 30:29:a6:70:8a:69:a0:26:5c:9e:2f:6e:b8:6b:23: // 6c:84:e1:46:3a:98:36:82:44:a5:8a:17:8b:41:82: // 32:f4:2d:e0:08:5b:7e:07:38:52:fc:47:56:28:27: // 9b:ed:60:8b:ac // ASN1 OID: prime256v1 // NIST CURVE: P-256 // X509v3 extensions: // X509v3 Subject Key Identifier: // 3F:B2:2F:41:FC:11:9A:D3:8D:A6:85:80:84:86:AE:7E:73:2E:69:5D // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Encipher Only, Decipher Only // X509v3 Subject Alternative Name: // DNS:leaf01.csr.pem // Signature Algorithm: ecdsa-with-SHA256 // 30:46:02:21:00:b5:2a:f3:39:1e:06:b7:77:b2:ad:a8:83:1b: // 83:38:64:5e:3a:25:51:e9:57:1f:00:53:72:db:08:11:65:3d: // f4:02:21:00:a1:4e:5d:b5:9a:8b:10:6e:15:a3:2a:bd:d9:80: // 91:96:7c:1a:4f:8f:91:dc:44:9f:13:ff:57:f0:5e:ce:32:34 const LeafCertPEM = ` -----BEGIN CERTIFICATE----- MIICGjCCAb+gAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAyMTMxMTM4MzlaFw0yNTAzMjgxMTM4MzlaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjXjBcMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQBAgMEMA8GA1UdDwEB/wQFAwMH+YAwGQYD VR0RBBIwEIIObGVhZjAxLmNzci5wZW0wCgYIKoZIzj0EAwIDSQAwRgIhALUq8zke Brd3sq2ogxuDOGReOiVR6VcfAFNy2wgRZT30AiEAoU5dtZqLEG4Voyq92YCRlnwa T4+R3ESfE/9X8F7OMjQ= -----END CERTIFICATE-----` // RealPrecertIntermediatePEM is the intermediate issuer for // RealPrecertWithEKUPEM, below. // // Certificate: // // Data: // Version: 3 (0x2) // Serial Number: // 01:e3:b4:9d:77:cd:f4:0c:06:19:16:b6:e3 // Signature Algorithm: sha256WithRSAEncryption // Issuer: OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign // Validity // Not Before: Jun 15 00:00:42 2017 GMT // Not After : Dec 15 00:00:42 2021 GMT // Subject: C = US, O = Google Trust Services, CN = GTS CA 1D2 // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // RSA Public-Key: (2048 bit) // Modulus: // 00:b2:d9:7b:e1:e1:d7:3f:1c:91:72:ff:f9:10:cd: // 87:15:79:74:b7:3e:47:8b:b2:61:55:fd:0c:36:c6: // 7e:77:42:3a:b2:fa:52:5b:0b:71:81:d6:4d:d5:e9: // 2b:24:4d:23:5e:8b:2b:72:5f:21:55:b5:29:ef:44: // cb:eb:82:52:ab:3e:27:a4:92:49:41:4a:de:a8:dd: // 31:e0:3c:df:6d:7a:4d:2d:d6:6d:09:b0:0e:e3:61: // f2:b2:fe:90:6c:5a:7b:10:64:49:b4:0b:3c:08:f2: // ea:79:0c:6c:a6:1a:89:6a:56:32:a0:29:a2:30:82: // 8f:81:51:0c:f3:a2:b9:d9:75:b9:22:9e:27:14:ba: // 4a:2f:2c:63:58:87:f1:5d:10:e6:5f:91:bb:b9:5b: // cc:47:e2:1e:75:b6:8c:8f:cc:75:5d:57:05:e7:82: // c6:84:0e:74:72:2a:cb:3b:55:f5:6e:70:eb:66:69: // c3:24:bb:38:93:35:9b:68:61:2f:9b:d6:ae:a6:77: // 72:7c:71:48:58:33:10:af:e9:80:82:1d:b5:07:40: // 1b:f6:3d:ec:a2:ad:47:9d:b4:94:29:34:b3:8c:2f: // cd:25:03:58:35:c0:25:a4:55:5f:e1:b3:07:56:3d: // c8:d0:63:b8:20:fb:8c:1d:43:2c:f8:f9:a9:d5:ec: // 6f:97 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Key Usage: critical // Digital Signature, Certificate Sign, CRL Sign // X509v3 Extended Key Usage: // TLS Web Server Authentication, TLS Web Client Authentication // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:0 // X509v3 Subject Key Identifier: // B1:DD:32:5D:E8:B7:37:72:D2:CE:5C:CE:26:FE:47:79:E2:01:08:E9 // X509v3 Authority Key Identifier: // keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E // // Authority Information Access: // OCSP - URI:http://ocsp.pki.goog/gsr2 // // X509v3 CRL Distribution Points: // // Full Name: // URI:http://crl.pki.goog/gsr2/gsr2.crl // // X509v3 Certificate Policies: // Policy: 2.23.140.1.2.1 // CPS: https://pki.goog/repository/ // // Signature Algorithm: sha256WithRSAEncryption // 71:4a:c4:c3:23:ae:f7:e3:b2:02:79:8c:13:e8:53:8e:80:c5: // f0:e3:ef:71:60:a9:a9:7b:34:65:85:34:bd:47:3b:03:57:16: // 00:99:48:3a:e0:e0:f0:ea:cd:b6:48:3c:d5:ab:72:f0:d0:1b: // cb:64:2d:3b:0d:74:68:d7:74:88:31:7c:6a:ba:0e:f0:8c:4d: // 78:ce:da:10:f4:8a:96:45:97:a9:97:ad:c5:35:1a:18:64:e8: // 93:b6:0d:9d:1f:b9:5e:1d:80:ea:e7:5b:9c:8e:ae:0e:a6:84: // d2:d1:17:ce:b3:fb:f6:81:4f:3c:e6:68:9f:cf:f1:a6:76:c5: // 7d:a7:f3:dd:7d:58:0f:e0:f6:61:01:1c:51:8e:76:33:2b:48: // 9d:5c:81:51:72:08:17:ba:fd:01:d3:ee:46:f9:f4:b2:68:40: // 99:31:01:6c:4f:1b:c6:56:eb:81:73:d2:79:52:05:92:26:5b: // 71:cd:9d:c4:d2:ce:23:77:0f:41:7a:69:5e:21:25:c6:f8:b7: // ff:7a:f7:47:de:c2:00:7b:9c:5a:45:9c:2a:4e:46:90:d9:75: // 2c:d8:ff:8c:ee:cc:dc:69:eb:6c:e6:15:d0:a3:ff:48:0b:ac: // 55:df:df:25:9d:42:b6:51:a3:66:95:60:c5:d0:22:e7:22:7a: // 51:a5:cc:87 const RealPrecertIntermediatePEM = ` -----BEGIN CERTIFICATE----- MIIESjCCAzKgAwIBAgINAeO0nXfN9AwGGRa24zANBgkqhkiG9w0BAQsFADBMMSAw HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxRDIwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQCy2Xvh4dc/HJFy//kQzYcVeXS3PkeLsmFV/Qw2xn53Qjqy +lJbC3GB1k3V6SskTSNeiytyXyFVtSnvRMvrglKrPiekkklBSt6o3THgPN9tek0t 1m0JsA7jYfKy/pBsWnsQZEm0CzwI8up5DGymGolqVjKgKaIwgo+BUQzzornZdbki nicUukovLGNYh/FdEOZfkbu5W8xH4h51toyPzHVdVwXngsaEDnRyKss7VfVucOtm acMkuziTNZtoYS+b1q6md3J8cUhYMxCv6YCCHbUHQBv2PeyirUedtJQpNLOML80l A1g1wCWkVV/hswdWPcjQY7gg+4wdQyz4+anV7G+XAgMBAAGjggEzMIIBLzAOBgNV HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLHdMl3otzdy0s5czib+R3niAQjpMB8G A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g BDgwNjA0BgZngQwBAgEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAcUrEwyOu9+OyAnmME+hTjoDF 8OPvcWCpqXs0ZYU0vUc7A1cWAJlIOuDg8OrNtkg81aty8NAby2QtOw10aNd0iDF8 aroO8IxNeM7aEPSKlkWXqZetxTUaGGTok7YNnR+5Xh2A6udbnI6uDqaE0tEXzrP7 9oFPPOZon8/xpnbFfafz3X1YD+D2YQEcUY52MytInVyBUXIIF7r9AdPuRvn0smhA mTEBbE8bxlbrgXPSeVIFkiZbcc2dxNLOI3cPQXppXiElxvi3/3r3R97CAHucWkWc Kk5GkNl1LNj/jO7M3GnrbOYV0KP/SAusVd/fJZ1CtlGjZpVgxdAi5yJ6UaXMhw== -----END CERTIFICATE----- ` // RealPrecertWithEKUPEM is an actual precertificate containing a valid EKU. // // Certificate: // // Data: // Version: 3 (0x2) // Serial Number: // 9c:a4:07:e2:25:f9:7c:c2:0a:00:00:00:00:20:6e:e5 // Signature Algorithm: sha256WithRSAEncryption // Issuer: C = US, O = Google Trust Services, CN = GTS CA 1D2 // Validity // Not Before: Mar 23 12:23:44 2020 GMT // Not After : Jun 21 12:23:44 2020 GMT // Subject: CN = certificate.transparency.dev // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // RSA Public-Key: (2048 bit) // Modulus: // 00:a8:7e:59:c0:e5:3b:da:3c:bf:04:51:91:eb:9f: // 6c:1b:cf:9f:90:dc:22:89:1c:b5:98:24:69:2e:26: // 2d:61:92:04:0f:2e:f1:da:ec:ea:3a:d9:cc:3a:82: // e2:b8:3a:7d:6c:79:79:f7:36:c5:52:a4:bb:46:1d: // 2f:0b:6c:5f:00:31:af:24:e9:4a:1b:32:63:1a:b5: // c3:28:9c:a7:0a:b5:73:e2:c1:a7:b5:1e:11:ae:cd: // 19:79:0c:62:06:cf:80:f0:ed:e2:72:82:bb:b4:84: // 0e:9d:c9:7d:3b:fb:4e:05:49:3a:14:0f:86:92:01: // 49:52:2c:cc:a0:e1:ef:86:fe:18:00:83:69:6c:90: // c6:7b:a9:42:df:57:9c:7b:61:06:80:23:b2:5f:95: // 95:1e:9b:34:6f:ab:a3:21:1b:2b:8e:9f:34:4f:ec: // e8:9a:48:74:81:2f:9b:12:67:54:a1:46:76:96:9a: // 1e:9d:c3:ee:bf:6a:e8:49:72:57:28:b1:12:c4:ca: // 41:84:96:f7:32:4a:4a:9e:59:2d:48:3e:ac:29:0c: // f4:f4:03:28:33:1a:73:10:48:29:68:12:e3:f9:7e: // f4:5f:01:54:b0:73:c6:a8:72:b6:84:54:05:23:36: // b6:db:3f:d8:e5:27:89:4c:dc:bb:b1:c9:9e:e7:7e: // b0:b5 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Key Usage: critical // Digital Signature, Key Encipherment // X509v3 Extended Key Usage: // TLS Web Server Authentication // X509v3 Basic Constraints: critical // CA:FALSE // X509v3 Subject Key Identifier: // B8:E0:AF:4F:7C:48:F3:FF:EB:FC:5E:A5:34:36:2D:56:54:AC:97:6B // X509v3 Authority Key Identifier: // keyid:B1:DD:32:5D:E8:B7:37:72:D2:CE:5C:CE:26:FE:47:79:E2:01:08:E9 // // Authority Information Access: // OCSP - URI:http://ocsp.pki.goog/gts1d2 // CA Issuers - URI:http://pki.goog/gsr2/GTS1D2.crt // // X509v3 Subject Alternative Name: // DNS:certificate.transparency.dev // X509v3 Certificate Policies: // Policy: 2.23.140.1.2.1 // Policy: 1.3.6.1.4.1.11129.2.5.3 // // X509v3 CRL Distribution Points: // // Full Name: // URI:http://crl.pki.goog/GTS1D2.crl // // CT Precertificate Poison: critical // NULL // Signature Algorithm: sha256WithRSAEncryption // 51:fe:93:53:7a:e1:6d:34:ce:a2:1d:4d:32:c5:39:a5:e8:1e: // ee:97:56:33:84:5a:5e:5c:be:13:64:92:66:df:a7:79:82:c8: // 35:c6:4d:8f:ff:da:a1:cc:4d:70:b0:a7:1c:73:69:d5:08:ea: // 53:f4:8e:73:27:5a:9d:5a:c7:39:0a:19:dd:51:21:94:3c:31: // b5:cd:06:2d:50:bf:90:09:3e:62:ca:a3:bf:f2:74:9d:2b:33: // 38:e9:9f:f1:b7:2f:e2:3c:e4:8a:d4:63:57:c7:bd:27:fd:94: // 15:c5:03:82:95:35:79:d6:84:0f:90:01:47:53:af:ed:12:d6: // 9c:63:04:1b:06:83:87:83:a1:34:f0:05:d8:8b:c6:b9:39:ce: // 9c:32:ac:bf:04:d5:8d:b8:2f:ee:61:55:b9:f3:b9:b8:93:c7: // 6d:9c:39:68:b4:39:d8:67:5d:cb:5b:bd:d5:a1:b8:d9:18:16: // 7c:f3:ff:7a:77:d9:cc:68:f3:c8:ee:b4:52:06:37:6c:8e:23: // 69:1c:49:81:1c:08:26:80:a1:05:8b:ed:f5:dc:33:c6:84:7a: // e3:ef:2f:c3:22:02:a0:33:8d:48:61:8a:98:27:34:e8:75:5d: // eb:56:93:a3:be:2e:c5:04:ab:d6:88:cc:53:c6:9c:db:9f:aa: // 5d:eb:c6:82 const RealPrecertWithEKUPEM = ` -----BEGIN CERTIFICATE----- MIIEZTCCA02gAwIBAgIRAJykB+Il+XzCCgAAAAAgbuUwDQYJKoZIhvcNAQELBQAw QjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczET MBEGA1UEAxMKR1RTIENBIDFEMjAeFw0yMDAzMjMxMjIzNDRaFw0yMDA2MjExMjIz NDRaMCcxJTAjBgNVBAMTHGNlcnRpZmljYXRlLnRyYW5zcGFyZW5jeS5kZXYwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoflnA5TvaPL8EUZHrn2wbz5+Q 3CKJHLWYJGkuJi1hkgQPLvHa7Oo62cw6guK4On1seXn3NsVSpLtGHS8LbF8AMa8k 6UobMmMatcMonKcKtXPiwae1HhGuzRl5DGIGz4Dw7eJygru0hA6dyX07+04FSToU D4aSAUlSLMyg4e+G/hgAg2lskMZ7qULfV5x7YQaAI7JflZUemzRvq6MhGyuOnzRP 7OiaSHSBL5sSZ1ShRnaWmh6dw+6/auhJclcosRLEykGElvcySkqeWS1IPqwpDPT0 AygzGnMQSCloEuP5fvRfAVSwc8aocraEVAUjNrbbP9jlJ4lM3LuxyZ7nfrC1AgMB AAGjggFvMIIBazAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUuOCvT3xI8//r/F6lNDYtVlSsl2swHwYD VR0jBBgwFoAUsd0yXei3N3LSzlzOJv5HeeIBCOkwZAYIKwYBBQUHAQEEWDBWMCcG CCsGAQUFBzABhhtodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxZDIwKwYIKwYBBQUH MAKGH2h0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUUzFEMi5jcnQwJwYDVR0RBCAwHoIc Y2VydGlmaWNhdGUudHJhbnNwYXJlbmN5LmRldjAhBgNVHSAEGjAYMAgGBmeBDAEC ATAMBgorBgEEAdZ5AgUDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jcmwucGtp Lmdvb2cvR1RTMUQyLmNybDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0B AQsFAAOCAQEAUf6TU3rhbTTOoh1NMsU5pege7pdWM4RaXly+E2SSZt+neYLINcZN j//aocxNcLCnHHNp1QjqU/SOcydanVrHOQoZ3VEhlDwxtc0GLVC/kAk+Ysqjv/J0 nSszOOmf8bcv4jzkitRjV8e9J/2UFcUDgpU1edaED5ABR1Ov7RLWnGMEGwaDh4Oh NPAF2IvGuTnOnDKsvwTVjbgv7mFVufO5uJPHbZw5aLQ52Gddy1u91aG42RgWfPP/ enfZzGjzyO60UgY3bI4jaRxJgRwIJoChBYvt9dwzxoR64+8vwyICoDONSGGKmCc0 6HVd61aTo74uxQSr1ojMU8ac25+qXevGgg== -----END CERTIFICATE----- ` google-certificate-transparency-go-2308f62/trillian/ctfe/testonly/doc.go000066400000000000000000000020001462611535200263450ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package testonly contains code and data that should only be used by tests. Production code MUST NOT depend on anything in this package. This will be enforced by tools where possible. As an example PEM encoded test certificates and helper functions to decode them are suitable candidates for being placed in testonly. This package should only contain CT specific code and certificate data. */ package testonly google-certificate-transparency-go-2308f62/trillian/ctfe/testonly/keys.go000066400000000000000000000031131462611535200265610ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testonly // KEYS IN THIS FILE ARE ONLY FOR TESTING. They must not be used by production code. // CTLogKeyPassword is the password for the test key const CTLogKeyPassword string = "napkin" // CTLogPrivateKeyPEM is an ECDSA private key for log tests const CTLogPrivateKeyPEM string = ` -----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,CD876BDE00043553 mvY+JQH/K5NeNba10dtLyvkmVrH+hS9kPkKSk/exHPezCyHF8FytpOMC5sKDj5S4 180O3hcZytZMh3b7lvyimxZ5HmfTm+ZBxAEZCigmb+pBxSzTX7+MK7bew2XZeQdl p4G1u2PHCzVeyPnRd2XLQ0SBo0T7pKsGVLgae4N45UA= -----END EC PRIVATE KEY----- ` // CTLogPublicKeyPEM is the corresponding public key const CTLogPublicKeyPEM string = ` -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQczNovWyMB74+fAgPAiX60k0M4cO QYn3hIzwSTVAcrKhHDyT85t4BoXdgl6XzMAVHmF7b6GyKoyyya1hSW4lLg== -----END PUBLIC KEY-----` // CTLogIDBase64 is the log ID that corresponds to the test keys const CTLogIDBase64 string = "fMW69rPsQG6J9V1zJeIvei6q+GGYrNxe84y5e0/KaEw=" google-certificate-transparency-go-2308f62/trillian/docs/000077500000000000000000000000001462611535200234075ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/docs/ContainerDeployment.md000066400000000000000000000026021462611535200277140ustar00rootroot00000000000000# CT Log Deployment (Containerized) This document provides links to instructions for deploying a Trillian-based Certificate Transparency (CT) Log using containers on a cloud platform; this make the steps and components from the [Manual Deployment](ManualDeployment.md) document more convenient and automated, but the same principles apply. ## Core Trillian Services As a first step, the core [Trillian cloud deployment](https://github.com/google/trillian/tree/master/examples/deployment/README.md) document describes how to run a local Docker deployment of the core Trillian services together with a storage component (MySQL), based on sample [Docker files](https://github.com/google/trillian/blob/master/examples/deployment/docker) and a [Docker Compose manifest](https://github.com/google/trillian/blob/master/examples/deployment/docker-compose.yml). Further instructions describe how to deploy the core Trillian components on [Google Cloud Platform (GCP)](https://github.com/google/trillian/blob/master/examples/deployment/kubernetes/README.md), and on [Amazon Web Services (AWS)](https://github.com/google/trillian/blob/master/examples/deployment/aws/README.md). ## CTFE Personality The `examples/deployment/` subdirectory includes [Kubernetes instructions](../examples/deployment/kubernetes/README.md) for deploying a CTFE personality on top of an existing deployment of the Trillian core services. google-certificate-transparency-go-2308f62/trillian/docs/ManualDeployment.md000066400000000000000000000557461462611535200272300ustar00rootroot00000000000000# CT Log Deployment (Manual) This document describes the individual steps and components involved in the deployment of a Trillian-based CT Log. These steps will gradually build up a system as shown in the diagram below. The text here describes the general approach, and details key options for the various binaries involved, but does not give full command-lines. To see complete details in a machine-executable format (which is therefore less likely to fall out of date), please consult the various testing shell scripts: - [`trillian/integration/demo-script.sh`](../integration/demo-script.sh) runs a simple single-instance test, corresponding to the first few sections of this document. - [`trillian/integration/ct_integration_test.sh`](../integration/ct_integration_test.sh) runs a short integration test against a CT Log system. - [`trillian/integration/ct_hammer_test.sh`](../integration/ct_hammer_test.sh) runs a continuous integration test against a CT Log system. - Both of the previous two tests allow for a more complex CT Log system, using the shell functions in [`trillian/integration/ct_functions.sh`](../integration/ct_functions.sh) and [github.com/google/trillian/integration/functions.sh](https://github.com/google/trillian/blob/master/integration/functions.sh). - Multiple instances of each component can be invoked. - A simple etcd instance can be enabled (by setting the `ETCD_DIR` environment variable to the location of etcd binaries) - A Prometheus instance can be enabled (by setting the `PROMETHEUS_DIR` environment variable to the location of the Prometheus binary). **Cross-check**s are given throughout the document to allow confirmation of successful setup. - [Data Storage](#data-storage) - [Trillian Services](#trillian-services) - [Tree Provisioning](#tree-provisioning) - [CT Personality](#ct-personality) - [Key Generation](#key-generation) - [CA Certificates](#ca-certificates) - [CTFE Configuration](#ctfe-configuration) - [CTFE Start-up](#ctfe-start-up) - [Distribution](#distribution) - [Primary Signer Election](#primary-signer-election) - [Load Balancing](#load-balancing) - [Monitoring](#monitoring) - [DoS Protection](#dos-protection) - [Service Discovery](#service-discovery) ## Data Storage Data storage for the logged certificates is the heart of a CT Log. The Trillian project has an internal storage interface that allows a variety of different implementations. This document uses the [MySQL storage implementation](https://github.com/google/trillian/blob/master/storage/mysql), which is set up according to the [instructions in the Trillian repo](https://github.com/google/trillian#mysql-setup); these instructions configure the Trillian database according to its [core schema file](http://github.com/google/trillian/blob/master/storage/mysql/schema/storage.sql). **Cross-check**: At this point, manually connecting to the MySQL database should succeed: ``` % mysql --host=127.0.0.1 --port=3306 --user=root --database=test Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 764 Server version: 10.1.29-MariaDB-6 Debian rodete Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [test]> show tables; +-------------------+ | Tables_in_test | +-------------------+ | LeafData | | SequencedLeafData | | Subtree | | TreeControl | | TreeHead | | Trees | | Unsequenced | +-------------------+ 7 rows in set (0.00 sec) MariaDB [test]> exit Bye ``` The setup so far is shown as: ## Trillian Services The next step is to deploy two Trillian processes, the log server and the log signer. These binaries are not specific to CT or to WebPKI certificates; they provide a general mechanism for transparently recording data in a Merkle tree. The log server (`github.com/google/trillian/cmd/trillian_log_server`) exposes a gRPC interface that allows various primitives for querying and adding to the underlying Merkle tree. These operations are translated into operations on the storage layer, which are SQL operations in this example. - The `--mysql_uri` option indicates where the MySQL database is available. - The `--rpc_endpoint` option for the log server indicates the port that the gRPC methods are available on. e.g.: ```bash $ go run github.com/google/trillian/cmd/trillian_log_server --mysql_uri="root@tcp(localhost:3306)/test" --rpc_endpoint=:8080 --http_endpoint=:8081 --logtostderr I0424 18:36:20.378082 65882 main.go:97] **** Log Server Starting **** I0424 18:36:20.378732 65882 quota_provider.go:46] Using MySQL QuotaManager I0424 18:36:20.379453 65882 main.go:180] RPC server starting on :8080 I0424 18:36:20.379522 65882 main.go:141] HTTP server starting on :8081 I0424 18:36:20.379709 65882 main.go:188] Deleted tree GC started ... ``` However, add operations are not immediately incorporated into the Merkle tree. Instead, pending add operations are queued up and a separate process, the log signer (`github.com/google/trillian/cmd/trillian_log_signer`) periodically reads pending entries from the queue. The signer gives these entries unique, monotonically increasing, sequence numbers and incorporates them into the Merkle tree. - The `--mysql_uri` option indicates where the MySQL database is available. - The `--sequencer_interval`, `--batch_size` and `--num_sequencers` options provide control over the timing and batching of sequencing operations. - The `--force_master` option allows the signer to assume that it is the only instance running (more on this [later](#primary-signer-election)). - The `--logtostderr` option emits more debug logging, which is helpful while getting a deployment running. e.g.: ```bash $ go run github.com/google/trillian/cmd/trillian_log_signer --mysql_uri="root@tcp(localhost:3306)/test" --force_master --rpc_endpoint=:8090 --http_endpoint=:8091 --logtostderr I0424 18:37:17.716095 66067 main.go:108] **** Log Signer Starting **** W0424 18:37:17.717141 66067 main.go:139] **** Acting as master for all logs **** I0424 18:37:17.717154 66067 quota_provider.go:46] Using MySQL QuotaManager I0424 18:37:17.717329 66067 operation_manager.go:328] Log operation manager starting I0424 18:37:17.717431 66067 main.go:180] RPC server starting on :8090 I0424 18:37:17.717530 66067 main.go:141] HTTP server starting on :8091 I0424 18:37:17.717794 66067 operation_manager.go:285] Acting as master for 0 / 0 active logs: master for: ... ``` ## Tree Provisioning The Trillian system is *multi-tenant*: a single Trillian system can support multiple independent Merkle trees. However, this means that our particular tree for holding Web PKI certificates needs to be provisioned in the system. The `github.com/google/trillian/cmd/createtree` tool performs this provisioning operation, and emits a **tree ID** that needs to be recorded for later in the deployment process. - The `--admin_server` option for `createtree` indicates the address (host:port) that tree creation gRPC requests should be sent to; it should match the `--rpc_endpoint` for the log server. - The `--max_root_duration` option should be set to less than the log's MMD. This ensures that the log periodically produces a fresh STH even if there are no updates. Make sure to leave a reasonable safety margin (e.g., 23h59m seems risky for MMD=24h, while 1h or 12h feels safe). e.g.: ```bash $ go run github.com/google/trillian/cmd/createtree --admin_server=:8080 I0424 18:40:27.992970 66832 main.go:106] Creating tree tree_state:ACTIVE tree_type:LOG max_root_duration:{seconds:3600} W0424 18:40:27.993107 66832 rpcflags.go:36] Using an insecure gRPC connection to Trillian I0424 18:40:27.993276 66832 admin.go:50] CreateTree... I0424 18:40:27.997381 66832 admin.go:95] Initialising Log 3871182205569895248... I0424 18:40:28.000074 66832 admin.go:106] Initialised Log (3871182205569895248) with new SignedTreeHead: log_root:"\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00 \xe3\xb0\xc4B\x98\xfc\x1c\x14\x9a\xfb\xf4șo\xb9$'\xaeA\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U\x17Xﶃ\xe3\xf3=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 3871182205569895248 ``` **Cross-check**: Once a new tree has been provisioned, the debug logging for the running `trillian_log_signer` should include a mention of the new tree. ``` I1011 16:44:16.160069 176101 log_operation_manager.go:210] create master election goroutine for 2385931157013381257 I1011 16:44:17.160875 176101 log_operation_manager.go:246] now acting as master for 1 / 1, master for: ``` ## CT Personality Trillian provides a general gRPC API for Merkle tree operations, but relies on a *personality* to perform operations that are specific to the particular [transparency application](https://github.com/google/trillian/blob/master/docs/TransparentLogging.md). For Certificate Transparency, the `ctfe/` directory holds a Trillian personality which: - provides the HTTP/JSON API entrypoints described by [RFC 6962](https://tools.ietf.org/html/rfc6962) - checks that submissions to the Log are valid X.509 certificates, with a chain of signatures that reaches an acceptable root. The CTFE personality is generally stateless, and is controlled by a [configuration file](#ctfe-configuration); the following subsections describe the key components of this file. As with the Trillian services, the CTFE is multi-tenant and supports parallel log instances, each configured separately in the config file. ### Key Generation Each CT Log needs to have a unique private key that is used to sign cryptographic content from the Log. The [OpenSSL](https://www.openssl.org/) command line can be used to [generate](https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations#Generating_EC_Keys_and_Parameters) a suitable private key. ```bash % openssl ecparam -name prime256v1 > privkey.pem # generate parameters file % openssl ecparam -in privkey.pem -genkey -noout >> privkey.pem # generate and append private key % openssl ec -in privkey.pem -pubout -out pubkey.pem # generate corresponding public key ``` The private key must either be for elliptic curves using NIST P-256 (as shown here), or for RSA signatures with SHA-256 and a 2048 bit (or larger) key ([RFC 6962 s2.1.4](https://tools.ietf.org/html/rfc6962#section-2.1.4)). **Cross-check**: Confirm that the key is well-formed and readable: ```bash % openssl ec -in privkey.pem -noout -text # check key is readable read EC key Private-Key: (256 bit) priv: 00:b5:99:8c:7b:f2:5b:0c:a1:3a:26:b0:12:e2:b7: dd:c6:89:a6:49:3c:1d:26:70:44:ad:4a:34:91:2d: b6:33:a3 pub: 04:16:44:9b:04:47:ae:93:f4:14:94:7b:f7:ba:ae: 5e:6b:53:e3:b4:85:55:ab:f4:06:0f:65:36:bd:f7: 5f:d7:74:0c:e5:30:c6:a9:0e:0d:40:70:5d:b2:70: 92:cc:b9:bc:c7:d4:16:7e:96:24:52:6e:1a:a4:28: 43:d0:b5:97:72 ASN1 OID: prime256v1 NIST CURVE: P-256 % openssl pkey -pubin -in pubkey.pem -text -noout Public-Key: (256 bit) pub: 04:16:44:9b:04:47:ae:93:f4:14:94:7b:f7:ba:ae: 5e:6b:53:e3:b4:85:55:ab:f4:06:0f:65:36:bd:f7: 5f:d7:74:0c:e5:30:c6:a9:0e:0d:40:70:5d:b2:70: 92:cc:b9:bc:c7:d4:16:7e:96:24:52:6e:1a:a4:28: 43:d0:b5:97:72 ASN1 OID: prime256v1 NIST CURVE: P-256 ``` **Cross-check**: Once the CTFE is configured and running ([below](#ctfe-start-up)), the `ctclient` command-line tool allows signature checking against the public key with the `--pub_key` option: ```bash % go install github.com/google/certificate-transparency-go/client/ctclient % ctclient --log_uri http://localhost:6966/aramis --pub_key pubkey.pem sth 2018-10-12 11:28:08.544 +0100 BST (timestamp 1539340088544): Got STH for V1 log (size=11718) at http://localhost:6966/aramis, hash 6fb36fcca60d61aa85e04ff0c34a87782f12d08568118602eec0208d85c3a40d Signature: Hash=SHA256 Sign=ECDSA Value=3045022100df855f0fd097a45070e2eb244c7cb63effda942f2d30308e3b84a72e1d16118b0220038e55f142501402cf03790b3997081f82ffe47f2d3f3b667e1c484aecf40a33 ``` ### CA Certificates Each Log must decide on its own policy about which CA's certificates are to be accepted for inclusion in the Log; this section therefore just provides an *example* of the process of configuring this set for the CT Log software. On a Debian-based system, the `ca-certificates` package includes a collection of CA certificates under `/etc/ssl/certs/`. A set of certificates suitable for feeding to `ct-server` can thus be produced with: ```bash % sudo apt-get install -qy ca-certificates % sudo update-ca-certificates % cat /etc/ssl/certs/* > ca-roots.pem ``` ### CTFE Configuration The information from the previous steps now needs to be assembled into a configuration file for the CTFE, in [text protocol buffer format](https://developers.google.com/protocol-buffers/docs/overview). Each Log instance needs configuration for: - `log_id`: The Trillian tree ID from an [earlier step](#tree-provisioning) - `prefix`: The path prefix the log will be served at. - `max_merge_delay_sec`: The MMD for the log (typically 86400, which is 24 hours). - `roots_pem_file`: The files holding [accepted root CA certificates](#ca-certificates) (repeated). - `private_key`: The [private key](#key-generation) for the log instance. For a private key held in an external PEM file, this is of the form: ``` private_key: { [type.googleapis.com/keyspb.PEMKeyFile] { path: "privkey.pem" } } ``` - `public_key`: The corresponding public key for the log instance. When both the public and private keys are specified, they will be checked for consistency. (The public key is also worth including for reference and for use by test tools.) **Cross-check**: The config file should be accepted at start-up by the `ct_server` binary, with the `--log_config` option. ### CTFE Start-up Once the CTFE config file has been assembled, the CTFE personality (`github.com/google/certificate-transparency-go/trillian/ctfe/ct_server`) can be started. - The `--log_config` option gives the location of the configuration file. - The `--log_rpc_server` option gives the location of the Trillian log server; it should match the `--rpc_endpoint` for the [log server](#trillian-services). - The `--http_endpoint` option indicates the port that the CTFE should respond to HTTP(S) requests on. e.g. ```bash CTFE_CONFIG=/path/to/your/ctfe_config_file TRILLIAN_LOG_SERVER_RPC_ENDPOINT=localhost:8080 go run github.com/google/certificate-transparency-go/trillian/ctfe/ct_server --log_config ${CTFE_CONFIG} --http_endpoint=localhost:6966 --log_rpc_server ${TRILLIAN_LOG_SERVER_RPC_ENDPOINT} --logtostderr ``` At this point, a complete (but minimal) CT Log setup is available. The manual set up steps up to this point match the [integration demo script](../integration/demo-script.sh); the contents of that script should (mostly) make sense. **Cross-check**: Opening `http://localhost://ct/v1/get-sth` in a browser should show JSON that indicates an empty tree. Alternatively, the `ctclient` command-line tool shows the same information: e.g. ```bash go run github.com/google/certificate-transparency-go/client/ctclient@master get-sth --log_uri http://localhost:6966/aramis 2018-10-12 11:28:08.544 +0100 BST (timestamp 1539340088544): Got STH for V1 log (size=11718) at http://localhost:6966/aramis, hash 6fb36fcca60d61aa85e04ff0c34a87782f12d08568118602eec0208d85c3a40d Signature: Hash=SHA256 Sign=ECDSA Value=3045022100df855f0fd097a45070e2eb244c7cb63effda942f2d30308e3b84a72e1d16118b0220038e55f142501402cf03790b3997081f82ffe47f2d3f3b667e1c484aecf40a33 ``` **Cross-check**: Once the CTFE is configured and running, opening `http://localhost://ct/v1/get-roots` shows the configured roots. Alternatively, the `ctclient` command-line tool shows the same information in a more friendly way: e.g. ```bash go run github.com/google/certificate-transparency-go/client/ctclient@master get-roots --log_uri http://localhost:6966/aramis Certificate: Data: Version: 3 (0x2) Serial Number: 67554046 (0x406cafe) Signature Algorithm: ECDSA-SHA256 ... ``` ## Distribution For any real-world deployment, running a single instance of each binary in the system is not enough – let alone for a CT Log that will form part of the WebPKI ecosystem. - Running multiple binary instances allows the Log to scale with traffic levels, by adjusting the number of instances. - Running instances in distinct locations reduces the chance of a single external event affecting all instances simultaneously. (In terms of cloud computing providers, this means that instances should be run in different zones/regions/availability zones.) For the [CTFE personality](#ctfe-start-up), running multiple instances is straightforward: just run more copies of the `ct_server` binary. > Note that for a test of this with multiple *local* instances, each instance > will need to be configured to listen on a distinct port. Running multiple instances of the [log server](#trillian-services) process also just involves running more copies of the `trillian_log_server` binary. However, this does need the CTFE personality to be configured with the locations of all of the different log server instances. The simplest (but not very flexible) way to do this is a comma-separated list: ``` go run github.com/google/certificate-transparency-go/trillian/ctfe/ct_server --log_rpc_server host1:port1,host2:port2,host3:port3 ``` (More flexible approaches are discussed [below](#service-discovery).) ## Primary Signer Election The Trillian log signer requires more care to convert to a multiple-instance system. The underlying Merkle tree relies on there being a unique sequencing of the entries in the tree, and the signer is responsible for generating that sequence. As a result, multiple instances of the log signer are run to improve resilience, not scalability. At any time only a single signer instance is responsible for the sequencing of a particular Merkle tree. This single-signer constraint is implemented as an *election* process, and the provided implementation of this process relies on an [etcd](https://coreos.com/etcd/) cluster to provide data synchronization and replication facilities. For resilience, the `etcd` cluster for a CT Log should have multiple `etcd` instances, but does not need large numbers of instances (and in fact large numbers of `etcd` instances will slow down replication). The [CoreOS `etcd` documentation](https://coreos.com/etcd/docs/latest/clustering.html) covers the process of setting up an `etcd` cluster. Once this is set up, multiple instances of the `trillian_log_signer` binary can be run with: - The `--etcd_servers` option set to the location of the etcd cluster (as a comma-separated list of host:port pairs). - The `--force_master` option removed. ## Load Balancing The deployment described so far involves a collection of CTFE personalities, each serving HTTP(S) at a particular end-point. A real deployment is likely to involve front-end load balancing between these instances, possibly also including SSL termination for HTTPS. Setup and configuration of these reverse-proxy instances is beyond the scope of this document, but note that cloud environments often provide this functionality (e.g. [Google Cloud Platform](https://cloud.google.com/compute/docs/load-balancing/http/), [Amazon EC2](http://aws.amazon.com/documentation/elastic-load-balancing/)). ## Monitoring A live CT Log deployment needs to be monitored so that availability and performance can be tracked, and alerts generated for failure conditions. Monitoring can be broken down into two main styles: - *Black-box* monitoring, which queries the system from the outside, using the same mechanisms that real user traffic uses. - *White-box* monitoring, which queries the internal state of the system, using information that is not available to external users. Black-box monitoring is beyond the scope of this document, but tools such as [Blackbox exporter](https://github.com/prometheus/blackbox_exporter) can be used to (say) check the `https:///ct/v1/get-sth` entrypoint and export the resulting data to Prometheus. For white-box monitoring, all of the binaries in the system export metrics via `/metrics` an HTTP server, provided that the `--http_endpoint` option was specified on their invocation. This allows a pull-based monitoring system such as [Prometheus](https://prometheus.io/) to poll for information/statistics, which can then feed into alerts and dashboards. Configuration of Prometheus is beyond the scope of this document, but a minimal sample console is [available](../integration/consoles/) **Cross-check**: Once running, Prometheus shows the expected collection of targets to monitor under `http::9090/targets`. **Cross-check**: Once running, Prometheus shows the a sensible tree size graph under `http::9090/consoles/trillian.html`. The addition of Prometheus for monitoring yields a system setup as shown. ## DoS Protection A live production system that is exposed to the general Internet needs protection against traffic overload and denial-of-service attacks. The `--quota_system=etcd` option (which requires the `--etcd_servers` option) for the log server and log signer enables a simple etcd-based quota system, [documented here](https://github.com/google/trillian/blob/master/quota/etcd/README.md). At this point, we have configured a full CT Log system. ## Service Discovery The distributed configuration described in the previous sections was not very flexible, as it involved lists of host:port entries. However, now that an etcd cluster is [available](#primary-signer-election), it can be used to allow more dynamic discovery of running services: services register themselves with etcd so that other services can find their locations. A log server executable that has the `--etcd_servers` option can also take an `--etcd_service` option which indicates which service name it registers against. Likewise, if the CTFE is run with the `--etcd_servers` option, the `--log_rpc_server` argument is interpreted as an etcd service name to query for gRPC endpoint resolution. Similarly, the set of metrics-displaying targets that are available for monitoring can be registered as an etcd service using the `--etcd_http_service` option to indicate the relevant service name. **Cross-check**: The current registered endpoints for a service can be queried with the `etcdctl` tool: ```bash % export ETCDCTL_API=3 % etcdctl get trillian-logserver/ --prefix trillian-logserver/localhost:6962 {"Op":0,"Addr":"localhost:6962","Metadata":null} ``` google-certificate-transparency-go-2308f62/trillian/docs/Operation.md000066400000000000000000000174641462611535200257050ustar00rootroot00000000000000# Operating a CT Log Once a CT log is deployed it needs to be kept operational, particularly if it is expected to be included in Chrome's [list of trusted logs](http://www.certificate-transparency.org/known-logs). Be **warned**: running a CT log is more difficult than running a normal database-backed web site, because of the security properties required from a Log – running a public Log involves a commitment to reliably store all (valid) uploaded certificates and include them in the tree within a specified period. This means that failures that would be recoverable for a normal website – losing tiny amounts of logged data, accidentally re-using keys – will result in the [failure](https://tools.ietf.org/html/rfc6962#section-7.3) of a CT Log. - [Key Management](#key-management) - [Temporal Sharding](#temporal-sharding) - [Alerting](#alerting) - [Load Testing](#load-testing) - [Backups](#backups) - [Troubleshooting](#troubleshooting) - [Browser Submission](#browser-submission) ## Key Management A CT Log is a cryptographic entity that signs data using a [private key](https://tools.ietf.org/html/rfc6962#section-2.1.4). This key is needed by all of the distributed CTFE instances, but also needs to be kept secure. In particular: - The CT Log key must not be re-used for distinct Logs. - The CT Log key should not be re-used for HTTPS/TLS termination. The corresponding public key is needed in order to register as a Log that is [trusted by browsers](#browser-submission). ## Temporal Sharding To prevent unbounded growth of Log instances, it is recommended that a new production Log is set up to be *temporally sharded*: a collection of separate Log instances (each with its own private key) that each accept certificates with a `NotAfter` date in a particular date range (usually a calendar year). The [multi-tenant nature](#ManualDeployment.md#tree-provisioning) of Trillian-based Logs makes this straightforward to deploy; each shard just needs to set the [`not_after_start`, `not_after_limit`) range in the [CTFE configuration files](#ManualDeployment.md#ctfe-configuration). ## Alerting The deployment documents include discussion of [monitoring mechanisms](/ManualDeployment.md#monitoring); for reliable operation, this monitoring should be connected to an alerting system that gives enough time for operations staff to respond to problems. This alerting should cover normal operational metrics, such as: - Rates of errored requests, categorized according to: - read and write paths - client-side (4xx) errors and server-side (5xx) errors. - Latency distribution of requests. - Task health, CPU and memory usage. However, the alerting should also cover criteria that are specific to running a CT Log. In particular, the Log issues signed promises to incorporate submissions within a fixed time window (the maximum merge delay, or MMD), and this incorporation relies on a single point of failure (the [signer](ManualDeployment.md#primary-signer-election)). As such, there are some CT-specific metrics that can also be alerted on: - The age of the most recent Merkle tree head. - The size of the current backlog of unmerged submissions. - Per-log instance counts of primary signer instances (which is normally 1, can transiently be 0, but should never be > 1). ## Load Testing The modern Web PKI operates at a much larger scale than it did just a couple of years ago, and this increase in scale is only likely to accelerate (e.g. with a shift towards shorter certificate expiration times). This means that a live production Log needs to be able to cope with large volumes of submissions, resulting in a tree size of hundreds of millions of certificates (or more!). To confirm that this scale is indeed supported, it's a good idea to run load tests on a Log deployment before launch. This is typically done in a parallel test environment that is as close to the live environment as possible (being careful not to [re-use test keys](#key-management)). This repository includes a couple of tools to help with this testing. Firstly, the [`preloader` tool](https://github.com/google/certificate-transparency-go/blob/master/preload/preloader) allows the contents of a source log to be copied into a destination log. This tool has command-line options to control its parallelism, but is fundamentally a single-process executable. The other load-testing tool is the [`ct_hammer`](https://github.com/google/certificate-transparency-go/blob/master/trillian/integration/ct_hammer), which tests all of the [RFC 6962 entrypoints](https://tools.ietf.org/html/rfc6962#section-4) with both valid and invalid inputs. - For write-path testing, `ct_hammer` relies on the Log under test being configured to accept a test root certificate, so that synthetic test certificates can be submitted. - For convenience, `ct_hammer` accepts the same format of configuration file that is used to configure the CTFE. (However, be careful not to distribute a CTFE configuration file that includes non-test [private keys](#key-management).) - The `--rate_limit` option controls the overall rate limit for the tool. - Multiple instances of `ct_hammer` can be run in parallel to allow load testing to be scaled up arbitrarily. These testing tools can also be used to confirm that the Log continues to operate normally while various maintenance activities – software rollouts, machine turndowns, configuration updates, etc. – are in progress. ## Backups For most production systems with persistent data, regular backups are recommended. However, the cryptographic nature of a CT Log means that backups of its data induce a dangerous temptation. The temptation is this: if you have a backup, at some point you will feel the urge to perform a **restore** from backup. If any data has been accepted for inclusion since that backup (and a signed promise-to-include issued), then restoring the backup is effectively forking the underlying Merkle tree. This breaks the tree's append-only property – resulting in log disqualification. ## Troubleshooting All of the Trillian and CTFE binaries use the [klog](https://github.com/kubernetes/klog) library for logging, so additional diagnostic information can be obtained by modifying the klog options, for example, by enabling `--logtostderr -v 1`. Other useful klog options for debugging specific problems are: - `--vmodule`: increase the logging level selectively in particular code files. - `--log_backtrace_at`: emit a full stack trace at particular logging statements. Also, the underlying storage system can be queried independently, using the relevant vendor tool: - For MySQL, the command line client can be used, in combination with the Trillian [database schema](https://github.com/google/trillian/blob/master/storage/mysql/storage.sql). - For Cloud Spanner, the [console](https://cloud.google.com/spanner/docs/quickstart-console#run_a_query) can be used, in combination with the [database schema](https://github.com/google/trillian/blob/master/storage/cloudspanner/spanner.sdl). Obviously, this should be done with **extreme** care for a live database! ## Browser Submission Various browser vendors now require Web PKI certificates to be logged in some number of accepted CT logs (e.g. [Chrome](https://github.com/chromium/ct-policy/blob/master/log_policy.md) [Apple](https://support.apple.com/en-gb/HT205280)). Each vendor has its own criteria for admission to the set of accepted Logs, which is beyond the scope of this document. However, the set of information that is likely to be needed for browser acceptance includes: - The URL for the Log. - The public key for the Log. - The maximum merge delay (MMD) that the Log has committed to. - Any [temporal shard](#temporal-sharding) ranges. - The set of accepted root certificates. - The values of any rate limits on external traffic. google-certificate-transparency-go-2308f62/trillian/docs/images/000077500000000000000000000000001462611535200246545ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment1DB.png000066400000000000000000000406411462611535200277760ustar00rootroot00000000000000PNG  IHDR")AhIDATxUu?S 0 #"6%*Fe->~fnTjOZjqVK/*m~E- 7*v.oQQQ}_>9\a`|>ý{s9dVj@CJm*8J啺*+Ȣڭ4DF@w!u¤!\-ucm >V'VIC\h]f+2ǧ3C\8wR/ԶW1?ݕ2=65JC\OG &;aY ۳=n-LA2:V{Vf{Ώ [\kPLqc^udU@=qۏ2V Q E!+ @My0YTy^u֔X+Νz6MiVj_ќ9oq)=崏Fh;Fl=*z3ײ{mk_緿պw矸2-O~aں=V f1bxKK/`)'c*u*A'H#{Ǧ{QF2a~݋ٴ75 ӶNؔ3'57?u]gfcA~zj͇m_>1*9Wޝ?۪M_U gy#G<=;s?w߷_p3CQqNo;[N=K|޲`n5,FU.$eEGsgO9c3&͋/vS&1s~'hsN;M>#G6y+η_W]\O;voU{5?yW]ӑmtI~ߘq;+}(sN?mƝp+?<1% ۓn7]'ig}'O~Iޘ@?2u%Þe1LU\O+cG=}/mmm6]UjRJ)V:Sq+OxǏiIN]\4"7ǾUfOtR5XΜܿVX=ʦrϘF _H!mVwym=s3>Z,hh^Ǎ~\Aty&z@ !N?yoFV#on=g3_X\BYE|?|)'%W\?*AЎаß}cSӱ;_W&A XC:mG&8Wu̸AЎRϕc߹ Sjoag\t~~٧+.eW hG ÚFw҄q“jTz|'>s|nAЎmFnz/{wnzmɯʯᣝ7[f~*[輽o(u >ſTo/sޜO+40Eۖ-|_u흷Q #F?pn~9s{GG{] 40(UԣC8Hm(Yz DPk_UAo](H3Vvtsz:ھcϷkF_mU<~ Cʏ~xfAHUyyNj.GhaPBdn`@;j}p*Q}V}c99ZAExmOЎKR}Y/ͣv۵@#¤0 魛,><>hA&A;:}XR}VwpQ>jTv-6muTj9ƤҺ[zaR3Ӷ0>S نӿ=Mþۜ~nIkM@#;~~ BPk綯'Ǐ;v-6yVrٕڝ]O?׬J/ >n6&TRv}*%:z湥%ہ#Gjm;" ~0"lx3j`z]$9 cﵥe[擧幥uYHJ0N;K轚_25G=/&@6ybus(Ձ#Z-O~a$ F=d Z|~w!Xֆt;'R/t00i`pK)u{-Hktk=M#};LiIϯȺz[,*-c|eroj[ q"-J3Ӻ-:ۻXO[RM)LoJMi].IU+r!G/Lw[=YFa /(A`ϙ|4i?lI0Yli_`-4+^[R۝)4fI1bMͲm*-{siY vtHɨ[;k,|7Tv*7yH#FMMMTW;Z%ف]b֣<&k]=ޔ4i69ح gExj9,Hkjm94X;RיBpyfo9ot`h4}pJS˝uGXa`3)Ln-;0Yo&Xai{l;;䂚l/-̴ 쏵armݥߕph4'Wq&{&z#U-e9p“**z _S85ot[#&oOmUWEF_5cԁ\sWf?/! HIik^-Mnm" R灄5 nus 0&f5ّs^ WO{=~&fAɖ> L=[zQoY @/&LvYRf^e^̺ 2kgmYf[zn^60o-"L@,z)o~Z[߱ ߶ajW\m׿=yF $QO>ٗa8AotP;ڭHrC/dgť9\arK7AyZz+^CnJ˸/70OdLO]C``bXg,׃}&l.6LFݚB_L<ކzSgkGx,*.!C7=9j~2&I4G;Ld_ɦl˽; J,Ϭ ^wsuqo ŕ>k.LCw [G0?dqFW, Xu&;{DlO?P3(Ly9:`lTDZbmqlaSW;:d2D s*c(l<&p 1Eot e~t)L hC0VA[}χ0,|yu>ǮU~9پR8ݑut8d[:@yS4Ł2M;!L0ٚĆlߡ-YוP;z&P5(+ϳJ}Iڇ[2L *&H\>cWKk~g[}ϣԎ0Y{譌=*ʞW:ͣGU{|0&I4Q_I)W]LX7u3i)T܉!˸.B벮1C週RGROKq:ۚZ kU٧a*n%_1 L(LIzOmkmWtarCC ?$LCZצ֥c[ڋ0ٜەmuZjH˱5  z 豼K=qk1Sѫ^HaRD#8Tbls}fH̪8؜g]n)uY{uF?%ǒ4}S|)G-YO,[s5 1^TEz5ڧ0d?˺ί!Ɬi[}͕YFW6\U;}KK~՚Y0߹%U{7 ܚ7awP. {jGv,*MY8nmի~v CV79I㪿X0)LAM<,ad݊ D/e>e~E"xbq8+.R&I4 L &"q_8rEWۏ|۫W uȇQ]ܛ=d&I4 L &[Ű/}[hkh{՗xհ4l^8t˓_~ѓScXp\(ov\va1>W]}aRD#߶ZJVvH+Bc}=q6cl Ŀ8O0!>#EhcQ qAbڢ"eظwc,SIe6jPa2U[ Q4 hGIx_9Sz3}g1'~] ;&⥗|KD#QaR 9LF@lxRo9@ fi54Ch=zً;U߫8SD#QaR 8LFbu =w>Q >cj\ (L0Ɂ&ʮ&dg5 0:ԵO9L?a2JD#y'ڲۿ{<䊡R߾q9yۂETaqdTc80FJ^4ojߞzr(¤&_%N? ha<;6Th YԦ衵U>%L0Wv5zT La|haaBdo\d@;j}?jY15.S\}&U]/D&W*ЎZ&O h]}j!(aRD#&54ԓOЎ"L*aRD#$hGI%L aR0IaRD#دLT\rEUjNMc {{άԬ~>ʟ?.0ٿdl?sӾJrTsv8m=MͲ/-K{Zs)I|e |QaR G$Lƭ;ns0FAJ}}#ZWݕA%J-M۝hܒ m&}iWcٚJJRw4]G7˗BԒ<=V|mՃiGI%L0Y0Fph[r9 QG{mOK 7LNJs6RB5kO:J$.L<M|Yר¤&IaՖdћW)36Ֆ4x͵پg`MZSSei:=#)M}?~K/Q}\։'oX|ĈՊy~|+^=Z-ߪ>wU'o_˫iO_?zY~̰cO޳w^o~c10Ilz5#t9KKtPS[e{z_ 3/JSY'[SLf[K!8]|ץ^-`{gyF0RXdžw[vޣߝ)-̺vϲtP6=].4]?ͧ6) 7L dagui\^=;uvڞ5ӯw؜W ޒQ J4򹞝}}{kZůO{[V}`i?Ve}eZǪxͧ FpiV]gmA00A vtrqH@tP\:\V:VlI_Ys෢f96No4߃ IiYv̻9M`4Z5!{{[]9&g kv }`J&PVNX[V-=e)M-Ǘ"n?tWδ_vcsiZ|{.}M+K;*L*5hU/{q~ /U_xJdcq"L=Q^jP^x.^yG`xMbzCh#{ϱl&4^#LGscj0-IJ7NՒ>L6ކiu^0a=wxiq{km;%}4xKE5YT݅bOǽ¤0`W I9a ڊka<#|Ɛ"c:a`Qȹ|z+z&W4}80Y8#@lL{&W[?Mf&w S&"> -)Jٕuz[FC|hRo ¤0`s~ŹkW_S=2sa1<0YG3S 166*0Ff6x>z"6y݄ zV@ClM٘z:;{&7ws>î098Tܛ]pda)71}05a/:mM/d d [;{=]O}#܃٫in~׾ guz{'0d|O0y݅9y,_'y6LEzby, &}M)\=տ0C%Ҵt55s}jN+g*Y{||nJ^ rS/dqquB®09¬?"|Ł }>aXa LnNv[6&j~[3¤0+7Un֝]_SӱԳ[bڎ.4]^O#L0WZj\5e<ſVC`<CM/{jOeOar׿PW&i}z&>15z'c⊯+L<0h im)8>/hZJǛkzb5L693תl @؜8Wf^^,]z%E̚ #d/{ C7Lٙx1%mE8*_@d[ G9L>炴o=0(}Y}}PdkoZ(Lx[z}saRa2Bdky 6j8jϾ}oO:eBk:Y5\~՗U7~}>gW?sC09KD#xdLIYeYy2f޷!lsa9zO- p2)У0-iS{7N^&jϯ'jzKNKêՠc㹝۾qy91MEWCjqNfS8#Dkb|b"P4E,fbYbwpQ}"X&Иoѫ5bl g-Bil<Ʋ?TeL@jK&I4ptf >Vv a2B`9equgk0-z dqc2k1L5mX~0"L?{0ŰC\Ia J?|+54]UYlhG&#ٛ_S}..tS>܃^1l&#e}_g2jʫbHmq>dY14k3z*a5;C&I4pm̨|Tkw F;ʀ qE],*;Uic/z1*Q\15qi#׸hOq81/).a2!0F،e(Ηy4|od^xMؘOFH\b$εeꊋGȍL $A8J~[46w l hGHa2B^mESWGm15z#4E`!# FOcFp?y{F*w4z5QW|#F@ibXkw"{qnf?YWeTtL $A86mQ]]CWmeI0)L h@;*L*aR&vTTJ&vTTJ&QaR G}ߙ篛^_‹i'LēvOhͣk hGI%L01B.?yv5)gy괩_2╗^>×^~鍗v\j^R5ZC#QaR ]5zS&ѣ KE#$Hɽ3߱Vv}WDޥϞzO3:フEoՃFZ5R[Qw&dg~5nI1j}ݠ&ب//P }W_ڪy&΅OUs408¤F~}vaR }Pq^wOcƌXMV0)LvTTɊI&O\2 ?ljz:n+5?z[։UJ_%hA&A;*L*5pdc.|_Kn&߲Vc<P1ϕ5U,ki>Q4ʿbj{ { k% Ӂ5R0m\ӎ J4 L0Yի?^&Նɶ;R*QS.\^3֚NJ0V3Dx L^t[M@&OkߜX=w^ɛkzh&Ly͆xGMm9QaR LϬ*)"lx-}.!H &b>mu{ ߖȕߞBd[/䒴=G&Wԙώ¤& C7LFRh0Y }߅ J1/.Xpy _#yoz~Qbomg{i^&@|vTT$aRp8oqynvUuǜ~!L5Q&gges *udQN4ݕ Kﵴ0.LH\>%kvPe|‰'ʮ$dwaE*Ǖ5A9}u[i)ltv$L6{J7r:Z=6eH!|{vjz͆5KS/fw|/;/}5xMZ_ӺZUδKj{YZid[ZTcmnzrV߇j#NjO L ,κE֫RX|05+άޑ˷uF"]پ=We0?Ǯ=ͧR|7 4%L1|ekhʟs9gRʹ>g]=lIfZ)OYm;"N=KvQ޺e+9U;zD^z۾PU}}oO;! L f >l}sDuckg>i@#¤0 As_DuuW'.A&IЎ 1LyeEuoGnz2@#&LSmhGIխokWnT?V~5e ha5*[QJ*SWHSm ȑM_ɺ haɝiŶ Ԉ0/6옭w`7}OF_aÞ. ha_>1RnНΗ̟W絠Z{1?|gV}COOy>o>, l [ښBRJ9跢szSӱziL=N|q'׾;~]5H/}z^`q.c|81>'=۷]/+#6c?3i_\pޔ VϸxڝMWx+~oi<y.̯k_ٛ_7z۪Q16[Q(yD{ߣO=pGcY71+.g\t~6yb^ yⳜޔ_<T>/{ nK.Yt9g_UyMMEf g\ʗzᛮz]z~ ׼^xscڹgnzs睳pO>N;jqpW}\#FbW;igNj_4{}kK׾ex/qݫnzi%}՗]PG_@!m]IIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment2Trillian.png000066400000000000000000000665231462611535200312770ustar00rootroot00000000000000PNG  IHDR")mIDATx|]Ue4Ii %PRBQ ,"EXhV(P0 nqPJt3qNF3t!WV/S5utftfXpYgew{rz^MOߝY߳^QV5Ma k g :30R @F [lT Ұ@y,l@.j6Q2ZV aIh4Ѫ,Ȫ̆6T!4͵JM]&B 0y r :DRier^bX &8,\,1Y&:.+Z6 &8ϑ$\ԇcR[êdULiyEKͭpײ|{OKQ'C߾+x轲/՟3'lkaB>m*RA n\}swꫨx6ѷx̩O j<ڠ0P+mC2O>Q:ԸԒ5??rʟ+n4uFj-5.uٵ믚QevET9VKCAWi4jze_vlf,=S*66h̪h3< jW+]ڠO=>@{-1rjEﭟU'/({pRIRQ~6*6hJe˴AQ)щ ι?|_kNTY]TT~vMpс q5g vi.9T!A?kdQQUGU0c鳞e, 'O~ϼ>UPmPY @S*AL}˫wH, ڠf I'spۂ5S;K iBkfR @'@* A5maTN8J*"LQxa:raAE;ru7/-Sw_ =&8~vp]|{]CZG0 \p Ĵ\qP.j5 zI bugԱ4sϞ=/E/B'¦>pettFj{{ *N<)(R4\pFEe?z!Ib\ˢޏ_;rHj@CW ZPQ?`RI&:+2WkN5sfD!O~v0 ar(aRN-sߚVQt)ur/uFDu]zDzNh)a6hIf tng =+]ˢ߸ẓ~vƵy=Rj $%&q5A̩mT vƓ:N|Uw=u:wh*Ycz⃃F Ϊ= u}ӨO=~US׿t=]G[໢ Cmä—oMD#z_}Yَu4>)*ȮI_:Fܾ5&]xum.ުuHNʽ^U!N%LPBauɺs7FM;AAN=/u\0[TtF aRN:Pz|uä5 z}\G+t{~ΣnE&^Ug.ޡ$L6(06@_Ji>@}Vi>ꮫvCst=!j\9ikq2GFMwUG]vz*]=O]_P<Jcץ95Jj?UjSv Б0iREgԩR}Sk]7yQ&0:W~:v>>5,&]p&YG1SZp]׭& 8fA#&LDSp_ύTf{Njt{T@6&~0nS8~ +#j⣰0o8f:r&ha|Q7iEqyҨ\z5LN:W z~cNFӅIu\2iZ =^r IY]h⚩6/$t=L w!QӯH]R@SG,[ұF gxQa3iʙajt^u֒ʍ&A4:aRF HMä'rSMUv-//ҵnj~06v\pp]#[N\AIM+5$ ЌVʄDe̹c10cN|:foԈF$FKչs+&A4:aRäS\{4%_#}¤ mF!IS.&^ԗV ǚf$}Ѧ/t9a:r&IS@S𦎜 ss*uܩB-7@N:]w0Srp70r#w0u}Qn#YW6ڟwlɍ{K!zL=@ m퓾&ݡ&{^zNr?Kܘ;u=RTu9'F2S'LWg5W}ï:mԁJ?=OBODN#'Z?in/SmW%L6(}7]kԴj_Ԣ:S`iNM)|ܹ"nSW[F]7«ښl@ЎsZ*dqsIjБc”979Lgn鱵Ec;+"LciR GVHM:"L@G@JkJ;;O1ZCF:uWR'0 ڠ(fM{IIhiM1D peLɑ MKsUm?Iڠ/0qIRIPi$PwzrDPFM1Z& A[:[R!t!RIQaAa:rEmE0 "L@G"L6Б ($tƯV=8-y/+&QmP!w\՛s:ed< zn^ i#LN}rڠ\}A+jr :׭oI{Chd!}S>a"L6h<$E(:rVi_h~+>:>;ol;S39aުlMykF2t;g?/¤:>uuَ0:|y|9嗴]x~t}Jסd rmJ J Pn_7 ΈC7j\?ZaR!XQ#c&/yMQHTU ^K36ImѢ&#WxSt}uyuQ?^a/726j_p$.u#_`BN{Vܑ6UǞJTm-+Cg9uSz5[B!1u-$s $t .L*,3jId=_7ڨicIKs}8͵' ,u궅=6d>+N0QUi:Fnu&#7a2o'JTԔ1M[tU=7_={N)f&V{.mP|O0iuqۮ 0YaRK&-۟omIJgZe8609*aRLznGa@¤%]0J,L֛iAzp\%mA#L-"t&@u *#Egb"~vt䦿Z{nR߹E8\ŏciEE|! fVϽfΌvk!Sl&鯠 ViQWL>cڬxtt~ 6(ޮ$=Wz.M.mPT;_8h۴u\Z?;wuŷk&)BL[)P9 N)?V37I3*u⫥fߙb_u՝/24,T6pAb *d-m5X1+ #A#T:7Aj%?I4:`:']2u&T{]_ۿotjkTpbMᄂR_U!] Бǂa]͞LDQIQTR, "P ($t(IaR#gMmb ($t(dF+k/;V {5mE&#G`iEto+al6'/@mn=6*갹AZ0 90) ir ZMDQIQ\m6c*ײQa:ra2Wk@6Бj7](i ($t&FPpg˶0)K< B= ($#=DUC\Yä,1T)Djiy6saґ؃և6;90)Ma@y >h($t(dZu6H*PAAab=mo{mK6 nֺ.xǯ >8I=*c&u6ݯ{wg@?voo|\4C"^}6]j\vޜ<\/PH\^9u$E&u@}Q/8)w: L -̨sF{Hۢ}ycUue֒NQרsIۅwAUEsPTIxgt^FQGӿ_mm'uMlAx}ëmrQ;x4L@mV6(ﵿke NjSοtI]W7 ڗŏ[C4RtCJ? j]0j[~蹸v")J5~zNǽNϘ]/cՖ>^鱴\/N;6ڶjmR[K\N9uyJ6]K<={IUDo<5br}ˮ6ר:|I# <|GaN:}k(PTR5Ўb&d :} ZڠR#i$MaJeگ՗:z >j^iw11Zz W֗TIm7.%o& nED!|G# QGNLDF'|MzNpäF ӈBX>e3Fu2udRT_}F0B^o3d A\)xFt1~Ȥªf$djttkC6'TGF0핞oijcad0 9}r};:/K"LPB97+]%;>H(\;Wz Qk$TGJujzNI(qLWIۨä:yT &;anR?nMuO3~MwLWR[ä4uʃaJWOwm}__[ Y;?;H3AeOf ;jml=4UC{*\NGxl6 `mДOJUL} GYyٳn]2jk?TUWmt#6P%PϤI~vc 6nSg) JFu MSXm<VM۠鳧6K-[f j\WPRYofbXge ׼5þ@gT9◆c[IMt*1O>` Ԛ dhmФɓxեifV0A BK{z3 jJS}*k={u\xKaUw"wV #Cn;eOEՔAp۠ƺRߴ>^mzgt,yGgR?ik~?uz+5sfxUNxurWϚg3QK* ge7Asolh| MeS?g,l0zJBMQPǗ@i2T<`;Hȭ}H;mcMiRids%#܌5v[ `ӈ>-[&Ljl@AGR2j6M lD t4v6Ɉa]9kjdf F|s 9`Ӥ7e(t l5kviXd~ 9[eRS[JVvu+R`s(e:L$Q79F3[PM2v>;080S]heTSh3L(()-lh$Rۣ0Ҫ^l/m@rIcU no-`j} :j^ZWY׻?? ](u=;E~PlcF}js8NQ8% L {8:s*8 FF9{ ;vY כrvdIcS1n*m}9!(:zun! {Mj$o`T]^0 j} &W)@ѥ{MjDehuФF'uin:0$~{I`lhњn/P!Wn1 rh3ݹ4١:q0Ӥ .WfJMjam!I20QUwn)@`lL.(xkkw;~6H::Ȧ jJ{uNNN۠5@y +cQ`H1{ed MjdPڽ@ÌR*1֙KS@aiXSI{!?fZ-U @a7nT[?ϡ ]I`bPs 1':4v}l[*xPEAnI2ˤFEkJh{sLE2nZMu(?VXG9,FyjG j1ZP6ٰZor_Ujӝ~ͤNM ezAҭ1yId#S(wsʯYn_^w;[kX{ab^tYJ@boB(tSWѨNG ( 鎑|՜<¨vu j>U0xA2SNs'\wf$"s@jY~~ܒP4ӃŮ'!p kJ p,(lpl$230>LjjV@Wʳ7,EְΚz3?yAӂ/4{~/<{ 3N;yujV͌cٳfY}P=꥚}۩4oOx?zs6.Mg??hSSX`lTQ7g9g=6ጟ) VOhjj)5Ǜi]7|{p- Q}᳟O7?|닃'we~/OVS=_<ҋ8}˳gkaBhv_t`ԩYƟ2b6͕o8kc =iS^ Fіufݣg͛4o?vS4;Sҡ1:?F5'׶jfRw)}}saуv_Df,_9e_?;6XBgHݑLy uOѢt%~qy΍ ,;υ_^Qh28#+:SSWO}y~՛.y.PKQEQԐĈ*K8ԯϜQ~Y>B\Vs? c*g֟˖%8 "&qt)5̘>a [:{o wwK>eX-C9:4 Q{ʬkU_F,BHY(X^}? |"_ӦUY=m\·wv K8K-ugk_*C>GVYy*+Wiҭo}1:ȜSgtu{=>sIΘ>ʖ7SԉҊK.087|( 97JxW>Wү|hi6_>ZG}'7.V~Jã=W??}_GTw__|"ACyy/}xK$*Ez @QSz{v>}oPul~Vp**䣿!QC;o_py 6װK|E) S{J8Hキw5ƺ[oGKï/:':s ^ٵ2}qR:n/7z6SlC&jQ~3?cNaMom:!:$aCpʄ\u46ȱ#\1sn0M}wu2dQI [~:Il5G>=f^)L6}f(|UþVþXҖ9:2흝#*p9&>;LP^{xuzm^.mĞZwnFq6ԔfXMeb=:^^~޿{-},F=J!öt#R9'ImǝyNF:b!7vR$LV<璴QlaR#2Iн71&csfݦ<ڏta<_}l_ۙ}zĻڣxy5&]lwz4Uc(\ԴVw'.'L!l;vGt2&]k.g[휮6-^/c{los̾&Vq.mi{z[4]&5B{m]9E>n6LJ-{y9cv/K`!L_ ,_dI?b.LUC=fr /uzڏ,aҽ~we}f^ lXc˅;nb6ocܫXğ%&UAe}De<Ȣ>~7VaVVVR5b_Ӭa>7'NLROa펍ŧ5'Okʻ͡n@ؘ9oa2>5dž`xuuMsm>.glX0Yar(f Gbeɤj1gKIQ3xnS˝0*v?Gbs_Yw$0&~qe/_ř0_t-~dU{t r3t09fƱ~@@Jf #!j;rYх&c,!,) z{i.{Nٍ,FEVno30;Mv$ϭ!CG<01 Ml۞60^hB#:A,0ؾoʆ&kF(L/ܢ`ݶͳHgv 3քfc,ԵQҭv2)L aߤVek۶[a&E9I7Jy jO UC!Lpi;nyo~x[N$L&xK7#&]kyKW̡j)^KNtgO80%lVt+}Ƨ׮nǽU a0ä;d VdOXf}m0Lj:{(aۜHUaoi;f v؆-0T*LhTX]F*bLTVNi$0I`(ȑ c6CTjN>qE菅.uNԱO&lys|0vq?=vu%ɂ u6H7"טJyI9L.?ymHkh!IXos,&yk?cfq|q/J ?sTjeOĞƪZTzhZa0 >Hzܪ=6m&PєB23gR;%=VuƎ$mv؜]툵Yn.<S9Zaү>X<4bW_X R#@ '65 I$LRaXaN>֧%6"lv؎z:}@4&fZ17ho_io/Tk  &uSYsN^YD[^{I"M> &¤[c2Yg ,5#,l+o}o_c_F_äy~$3;l5gh?l۞2l.\ZeF|N0JtR5?a:l>)MV;흝I$rT$(a2PF)uR{K~)\2薦+$8׫zC4򨐯 ~bTR"L& @$L|&GtjPZ.huX-ӏ| GrзF??{e_<*;KL7=k[(djq9ؑsם8jg0'L& PtyޣHϻ|= -4<+QKK¨nO=p[6H5es"jg^¢F5=Uӂo VѿW\6mg!L& Pt:p.dpwe{LХШ7-)ljlüӣe{O]WS@>TSSF+`w*=W=J#:wF\&GAQ~uuz#@a0 >鑧YM+(ۣ`œXB4F\`8Ήr/]) }tzL=BF݈D I$JV$ n s&)$aC]=W>[K8H=ﹿwa*0OuuKzOS`ÃOS3a|Eў}zC矾T Q&R/wް<+MUvW?6_K_xƙ o$?8X& |&)اnQ6$\ |&)äFWrS0c`aSc#?=q{ٳyu]>lrp[ 0 >s0I&'rT@; IaYT*/ٖ(4)z̫wra& |&)YOThԢ=gR=L?wٱ}I!I09äVvj=Q@SMuu a¤0 >Bމz{+~g+幟m*[~˟- z8-a"Lr1*50 > , T`xzF sI09zaRKqm'n C;ϱ5aC{ă]>G09:aR+N65ZEM/?BDiHXǻ|=?T,Mm";nEW3 > *ld!D ;yWQ"LO& 0zaAuwe{~($a|&TҞQ&)$a| L&>G a0 !& I0I$LV kkX~WcV`&iÊò#bexnsi]n"|}|&)䘄Icà CQ3,G /a) v{ hlvОab/;>ƻ^C5x<ݮ5 1~2,"zףvmW>>G ar¤nI$,mۑ!>֞QxLuw{As6#Ln^6$r{>_i$&{v4eûYFivKP챏{ۇsYmq{k^_﵏Ba0IȤ~4G?~4/jD|Mѱ jCowQ78q ?;JTmG"?Qٖ(~ߍ_n#Lb>c3|˳7Mۙ&n&W ikk[=e_waU{}23yͬubÅƄ~&t}.۞~=X>vy-a8dSNs 3w2Ih?0y؆2MkÛ ߛ%`o!ɻ=.ek>yVe/%LRTцɫ o$_BT;_w\t#/}}5*:JI]G!NW`LBzn !uCpuT.ێ-Np`o{ld/S\":2OQ:vپ&7y~m{~m V M@x,0Y NE Jl6Ż϶1'OmmM bڔ묋=Laműx0IbsSR5]`IRCw;>^z,]Gä_}+|jJ¦BnGÍr,_GW&ul]i]C 3Ɏ@y.{L= Ϗ0YaxBnԗ`{ld h;;&5'=tSiҴ)I?SL& UaR#sBjw]K覙jzj04kX=w[/a|Ւ8h;+2 FsG:{v'0y(Cǽ>ǰK,0V:)¤i0¤0Yidlۃ&HYs$d|IdWüO >c{~|cCy'h<{O}50Ϛ? ʪ$3IcytWaRJc&jI^diQ@Zd;[+bi7 7LLbx&wwm?ke &@ !hK,0ڤ?—: z.0_| ۤݶMlBpd=vB/w?q}/ՃrJtNCpyݟnzy榕o6 /R;N(2M5-He0OF_VoԂ>֪I=_$_. !rX=v[/:LJb#1 }i/Sccpv $Iw26& DNf &Lnt//#q\d &׹ʾ3L郌 _ԌPt}&a09¤Bdy"(+ɉI7 խ4VQ{?PJUO C,_ LTǼZ7aҍQ|qMYFm֗h}!~ڐuV[`~&;H4:G }K&rsߕ6l~>e~flۣϞ"\IE&{Lx\vxz{I]^E+5ֿ~iu )9H)2)UP^Oʤ*3 ymK&MIPʝ[2)UD:2TUdչm*!R42)!}1iItʩXp RD&IdH`P:29$2 ݀ ];QdˤoL "2ĵ-y2\U9P*횪ȫ/Xj,/wʣMq' R4UVPٖ;Id ّZMP(ǣ/ȤH,Bi*rfJ_[2)&PUBL**sEvs52i~j6NoL"@;&MKǣȣ,"lw1/9YJ(dR¹A퇪L:jn#Cj|H@kdR$Q~Q5RB*fL;,(6$v#$2 $A(2I $2 $A(2I$2 $A(2I$2 $A "2k=[տl|A=UR-EꦂySՄɗQ*5m1__whG\ր$@E& dɤH-7c#"zc3F\PsbM<3iœ&hve HQd@&LV_s*q1MY=vԓ.e H=uhO6Q|39 < $L=3~t$2s$?tK.^nH=Z$+ډ'&[*p6yIظ@2<ԟbX*zH <`ȣ2uQn'jDGR󟶻$A&_W'E gG9\ dVE.T1Gޏ zL&;Оm@xp=$΋_[jc2Җȗnfp @ߐI|pp=( 2 !ρren1ɉr'V $Ad "e2""[檗}] +k~CjxHRrcccpY 2LGI2i,ep>'^!J EZE^0;X_oݏi\J$L"Qd zL:IJHHX>|ٍGn*/]x&·υ~$%'DFF'$Ad8vThKeSeC;/ 𻺸0;ƓGI!2&;!!a秿<(@cff2?EWWI/@=zwGO(oT-_ IsAKvӸl2 Lg&hUkDzv]׎lv* vTQŲWЍyDE& d'!̶cEeqqbb*W%$~4rE;_0l[/9Y# LvLvH2<},膾/<ljT,XnT`euvMѬ)6Yf;_.uNuJ6Ioesfޖs)"__+Y?J(2I $ gY&bDzEz-"@Ώ ZrdTva-B"L>xߧ_[6\67es㜏)q 韻b؞Lfe{a+Te?/9gGI 2dTBߵ6|K2׿"dT8ev:kF!AE:A&w5w r(l&>>vZZ'WZɬ|t:2YZJZkdGI@&E7b[!^F~ϹkvD*LJkZ3dZݞLעըr/M7h) kG&eӑu!9罁;Q붊ȣ]NB3]<0_-dD&ےIPacKu!o27!RRX/вQцLzt;:lU&8i ofe2s2%Zƍ`JF6O;_Soǫ@r*t]WoO&/rY:v.w{7q=$:+vl]~W @&(ZE Z[-U3UX-ώ4Gt>c ' culݎSxGZ2R[s[뿙iYZWjy$H\=m j.Wk/۴jylhsܥ?;0˯q|֡{7Xs%3ɣ撕KzHtVz|6Է2 L"Ѿ/kHiP㾱N4Id<qU&gnM|H$2 @0ܹ"q|zZS$gd@Q6/W:y~6LVVȣoGpok$^ռ&Edm@2XYJb siXyu*z$Z5gFy _ @ߗɯw'|IucyZ#ǎb?yk&aUߧN:O$A(DgF ȣp ?s7(/)EӁߨ5e~!ϒW$?Ѫr QoqOP gG̱cq%\9$GW}om:z@_ yNPzy~>%+kؿCcZUcCRSz#Pb$A(tX~^{ W!7sTJrwb_B< d{cc_pnu _[Suߪ&Ms,22؃Co ? G{v ;wyJ2i_M}v9z*Nڭ!O-_Gy!sİނ<<ڛT_ϼQx\6; O_ݯQ QiaaRmi5"=b;g˴XoİU8ANAAtK/a Y+=Sc)L#2o0.g^2r1nh& OOmΛ2NOn[cucޡV=4 gF@%dxț@y, ]o; /]ؾEҷWLP9VTe O>Ⱦ G}jΫgLWM,=rt{@#M1^3ss9WgL2/, %^Ÿ#h"pK,=&r _sFDĀҖ-1&?Oٕ?ɵ?~k>]dF͟2nгixIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment3CTFE.png000066400000000000000000001016251462611535200302340ustar00rootroot00000000000000PNG  IHDR")IDATx U}0 08ȠuCPEE(*$cE J(QT%#UƋ j4bC*`b,%7xKZz;iHK }8{69~^?0g>{fY^C*ȯPŅ*ɯ q}Y+;\J92a|; `phҴ]XCxضކ7cWWxg?cQ?\Xwi_VI$@1hРU>l_W!$Q=*s/;WEp& g‚9BTFNYE&>}I$@+k>T_>g\=a0 6M2vBRU W]$P& kF=P{Gv)I$@i\4k 5)P$L&ˮϮPC 9ǝ4׆U^ I`舊*Vy\4u( Iu0CeB9'qX& LCeJ=zWPP鮥&'/z?d0LNι$L6 E̮0/WI&)0'aj?=I2D_Ri~$EDɿ~U{ŧ:ܾ^IID&vuzjk_zӯ~-'T0I&aRJP.OΌ'xNsuױ WW$L*<`Ya"L/äޤF#op%H~+ܮd[<&% ؕ~#P0I&a2ۊ0 us!K{+me+aI$ 4Uu/<.c0I&A$LrNsʽI&)$I@Nʬz95uI0 $a6 I Yˮa"L"¤V]y[ha_z9Ʈa"L"ädriˌL8n&H!*z9kMI0I&aR#IwZޥ7ξtw_=]rUUm[{L:uۗ e~5įgz]moy_.‹?]r?4Eub߻߻}m޷?ZkaÇsbtw={s.t9r˟{O?$aڒQM|,a۽)g*Kʊ8zKnR?ܟ0nJ:uϝ◂fhq 6P67] egww?W~!6=U!QPPOߛ{ S&¡i Sc!RaQ{/{foޗ6< izŞsm7wյX{mMZɕs&ar7{cy o}t4QްWWB;!nurUOh:+&ܟ04T^m2퇉&<iTSsik[_.̥P:/{4LGWTtsaz+))9H$lkY/?z+Ă0U)&:i NtTStb!1qa2#a4{En>,<`km_3Lft5L*i0{)}7u5daRyϩpJ A+l ;~͌]Fӵyɪ9߮OuPUu)Vg_/'kI,Έ LdgAՕK/]1H#a0j߅&>ϭר &> 9׶K=ҝ0t^(#="<*M%L1i^z>}:= :MIBf lMl@t_Wr_\(^)աӧli1pׄ^ξoI$L!זEJgjmw b&u^jUw?[Gq֧/PB z?ƦFF tOw_"چ,dq.L͵L-\1'1o.09ռlNz/0}**Ldg5לYfծhF3*ssi'MV"; ET40馵jar:z=$h;UjM+9aRיcWfF;!x)^I]trdoɢ*Q&(P+&Zm'7ٰQ~PU L>S\Gjޓ_}ht1pQZUwuKX );l#/er &[4" _\؎.1.td*y&uuN$\ }-ħnA>hb_"SDwd*l:7]0 R#9mgluwϾP0=P[lt#ڏISSَ45gp8/N~%y.$a2-i+vJnAlkt3=L4ˑhjdpē0 X(t4%ʍSnZogPMa)P0Mlg1>׌ms ScMAʮQ ZU2$aϋFFqȣF 5`hAdts2rlw0 76TnRl6챝9~3%L.7G#BTۧn[d>a2N;wض|u 9&u\76؝&q_UG&[doa^0S¤s t59um O09 h7ΪiW}`G_]ѯaaɱLVx+aTcz=6wl\cCe}>A!L@t4 q(ִv"uTxϷߕp[|]b7fdEJʊYל9ba2Uݱsɮ1` [LbA.~:V0 a}e|v:5uu |(jD4eK}:ΝT@T|Ifm:?YS_ǝVZlV{4eI$=Ӛ.Z6 @ !۹(ja0Yd?0Ѭ4;KI$&Lk,RAI󾷚m#ǡ5R}n:,a0UĥyaѨ9&!L&0lDʭ8HQ&߱ms\k hB|$a M6PR] dd LcVΔMz&"~eSpU F6++D\($r-L~k_Zw݋uO=ccƎnU5#-,*Oaцفи$& m6t@]Wa$F!&)$:L;;/..~wd[:wsm k%ayNar9v0I&arW[cOۆ_"ItVhl0I&a_fެw ?(_"| Lb ~w.a&ww?xLC$I&)0Iᄑs ڬ @a2洜qL$r/L~oz5":m0 aެ/9{va9&V z1@a2^Oʻ.n^{ض힣"fj7v5nkc܆px}=۠S|go< o,>+m{G_]q{_۟Nd~Ŝ>-/xŧӧvWEwPO$i}/M>8ouĂ٥7jNN9{rkw?LJ PRFտZjϽ soM]rSۨyq|챪N=fuciNbyb zWmYל۾΂3a2/ԌF#`qT/C5U ХUȮ0 dJ#d s ta* C \fs|LʥE>ދB`~ Q*ts۴4*B%j;s.}baL]uFwqx'/矌tXM=wJJv1I&]ы nNsNmK8Pi4F@,Lj/844}t# Omsj=鰄Ihl?\Xέ,,*h츱L;}OϽsм/kyw٧?<) :pQq{#F^yE#UY L0[u'̄x4eTSmme ?M hZTij)L& @QSKS~mqa\% ÄIt1`NɯPͷa>))a2Hv:/^apEMM7Lizsj+vDIaY&5`9&53Y jVZv5&ݹqQaj kTLUIhz?UŮ@IMxa$Nf[ _vw5Rq'9&j[8j!~4 6*w%LzOzdYji[[_kUU&TᗖTW+&ݨ.gਠGLp9רg4SSe¬V8Upӿz.mĉ:QaMUSu.kiz.è鲪yLF]Rϟ' D `1|gԊIܴɯgL|=~5KaҕΝԨ.N(*_Ly*`]Q+Wi;}?C+ARb~tWdy($@ߙfc]u LRIDpIΏ]AΓL LPM:pI\>O2"ä.Ehҩ\4"LdIFa+>LRIt~d2Lwa"LTI&tW&)$@J<(Lwa"L).$E@E %L!PU>g&x ն 0T&GvO sm_L2.&~}]jMC;0T&Մo8<P\RCeJAnVqx*꺕m8_ KFAzU^IYs9,W8hРej %2JYeΝWW!PQ.}`/@vY?jˆݚjH۶HiEɿ8 SN! U׎?FA {U(PV6S^5__v|X^wn0@S~}qinu?#P}Q]uy?VreVy(ި[]#[9ܤQ%~F*_4>zGv(::}8|p㚫~2)PX4Xk?Zcl.\O͑A~`;8ͯU~qv)PĢ2@W-~_%tTj/Hi_ll3\&b\G8 B"PR4ik ]Hy$i5,Ht.%.֯&1Jΰ8 Lbq]~ճ[i:.γ]HG_Lb-t4<.Ư&1JޯRv -γt\g@2v Zկjv :`.Cl5Q &>r @J:wJS.Cr Zu9]rIL{fSaHi_mz~ }GkI,γˆb?iӽ&1Y\a>ï5J#&^Q<< `T`](MSmgc6m,ɏ y$Qx깇 /n Ld= @HU|df@Ӕm&1J!36HjA*vYfP*Pv  Mb@:4uIL{v ZpgI,.Cl1Qʍ&~YRZb 3%tLkI,ΣkO[(<3i;e1ۯ}6Pk>-γ$F)_0,H"@W#:vc~ :ǒ' I1tGII@LQIW O__@H Ze+c8 &~ΤIZT6+}~dURV,((x/^ahʆ6J *߃~61E%44|s;6|λ[Ƕ6q?ES{ʼ[gPs_ ~p;UaAoޥ -q';tI ތ!w?==tP:v_5pQIႂ9Đ dij.hAqi{~C >P6*J{UtB^-(Np|hM|"ЁBYEAT_A'Li2h=Cy]>#:bmPҿsܨôAT_AsZ?ֹAO=^QQi~uSgN~66h^IyA z҉+- cާGw܏K~bz^+((zCA@A~)m8G^5 5ť_P6|X5X@ HseʞPJKjD:@Fʆ8"OqiinʇG@,0ea:@Ws>.(kM}c6hoȊW9$HSiyI'ґ2"E%[+%E{iLiK>  =u:ON)uvC3ڠҊwݧ2:ᣲk94HmWL?DʔrE #C3ڠs.m%T&A'~CJJ޸k@PS}?m0xnSU_P\Z^^BʘZWy& ~BDeZTY57P0 ST ,?љ'mPiLkh Eߣ@ejI 60 @JKy2A5EEmE&ǎٗ]~ڦ_ǻSΞW鍮vI^WxO}o^{M=r:hIdY^5ibW3:MͺI7ћru.͸ĶK!rڹSbrȊ۪ɍnׂtȮ6wl\F܂^QqW;uB,TU!Ieb}9mv]#).yVU L0 dwgEUF#|cYir̰X|䕻hG&&T_W[4 WRVO:ϥΠ^?<56و;:_K-egt-z3 _0 ڠI/M9 ~ڣO6At~ԣR+hz}T T]c>t 9ǜOlkWfrK]x~ǩ=WĩI(LN:YKX unsNSR'uSI1OaLaMha8L_uiSJ{p}^W!XO1 =^#c:U zu}Ýuh .PsecOSa QaR;gJ nu[xaP IΙmPI:1¦v} xyI~ nTۤ@56еQǼL)` #j£0<.q$t4L8n$QIri430:I\)@j\N: & TepV=^#nDQmHA0L;I,LѶBAGڛܔtMϧMOuR¥n O5UP¤oԇuy L@iއ4&3}Q2$y&]&çgH@n-PJRJQ d}/&UÅIB;+H]{$L0]:1T.9\{ds+jk4PMq> _I xt+F}B_Xs;妫OÝGͨ)gnaRyY*7z@mP߄I, 6!&1,7>tZT?%[M,L3vg8mHhq&#QaRJMh1p'-4i2agAu:̹s90sNx:f0QTHDsV%L6o¤?Qq+@+(jJFRI: S]&^ԇV ǚButä;OɝB{ޮIA9W$h&L—tlj> .*Lsڛps#}QۢJ^p騶0FXuNjz=ڱ๐z}_6a:r&);')\nP|3ԑ(F MSyutn׿ј*hiq F4"O]Tg+E8Lc4mVԽ~w¤Iϡv~*n ^tVKWVp1cQJ&ݩ&.mfh\%L@G]:Y=RTu5'F2S'LWj_%uk)h|(:p@mG,8Nx-կWMۣinz`RJ\m0 ڠd<u\cPǢ _3}OO+tj O3r׊kN=^mIԊ+6^֤vɮkMTE\V! Sܤ1" _._0IJ-[!5|n0  )}:)eg:j?xI*0jS/i:IF5k"8ΙTO\z$CZ92iin 7M_3*I r cGn?j5X0  *"XOèi:ѽ~\K$a6(wKw+T9t\cٍ216(Ώ_:g/ä[:ݿaɱk{9Ꭽg9;iƤ*].;re%ד#6ʵ)6(*@cY1:jN}sˣzuh\WaR!XۨHNmDQTO;.&>v$t2o:/6*Lˍ tu5/z_괅G"O9{rVN{+R~VU͑Jm-+Cg]9rkwjBbR[Im $t2.L*,ueUIdmmԴ%{_toNs:uKa++f[Ԩk4UQK&#ׯagKTԔ1M[tUm'Ɲ0&d \ڠL ]J-aRY\5] 0aRKF-[I(Uop^%maO¤uGa2C¤Eݮ0,L֘4W/Ip^%mP/A#L[D(vM'Lȩ)Ttъ u_,WQOƲ8uJ{Z_#[U<*VTT \{ccW5^=.4ȚNdp+hzT:f+&RҹQmV:Rkc?6(ܮDm+mNn?NWr]fN蚑 }ϽsmS]"0 @uJiU@u.h5S}U:3R(ZjgvEg UR|*PjSG1|w Upd ^m5P4]}Iڠ^*]CǛ E`0S;ۣ*jtN,uM}udU#ݖs+%VGA -ֿڔC*my+*ۣ8a:raRPد 9i(0 9 ~ `LQa:ra0)sMQ?ksDQa:ra2vF++ao;W#G5mE&#GbiEdo;0`]pGJ=n.G6myamha:r՛aR"n(F(6БZkT. ($t(dzr "L@G"Lħ*P``ADQIeGtwۗg_& _AE [;r;92VZ/;$xAο 2įA@KTq]~m蚑sal+׃ku\ Xzlc:^VAsa:ra2j$(ـ 0 @.w}u׼lw%{l>ӻkW.yVsZxyI춅.Z{Uֵ\~t;ν /^s>{|UlߪRۣIm/a:riuR)t.`i c:$z}ݓh:)Zwߣ:NE(eT-ɾ:Ymϭ}@lRJ@BۇaRt6P!i hJڏ4)oq>k(d;zQdz\T{cƨ{U,B>jv)}>Q(u.~& =I :r$uI:>ntRG'4Lj@0)uelgQ:vQa2S1Fa#L@Gӎ>r}::/Kq׈cpDu9 / |:P^iԔ,=o3mRO =FAݚ*&z-fEZ9`|b QCi5hR-lMev4ld;PT\d Ibi[KsP}_ꀏܛa:(Uh9z?ɷL+E-JF}4fO8:HՑ+/_*Vz.r-Gvc׽>ImиIc Ug_|Ё2ιʆݝ|@ħmS}*SJ筏=MR+)/>HʔRY/2B} WʇfSeS](5u&Iڠ|եimosA[ijq'uAACY|-6PjlAGDwt---~Ci.)+ny5t2>\s~S\RJ; |H3IڠҢhjN8wo cF>~t8> ,?TPPɭs@ZFsҽ6|#AT_AFy IЫ*GeG.[)Sϩk/*)ѻe@{mPeп)(-mmйS>,.-z0>UW][ZIyG'͘]اNJYvOwe 9-{~w-)+U@06hQJʊQ=mj? >y1*&N2؉Uh/(UvU:E?:FXߣ< UZ t^{ԍ[X;iA:fO&Lgy zJ]B]>P͇@7kP*PBz{ G;{lk&Z.rWOuU_#3%fl7U  -6hq&v "V|]>kݡ;!S\<>(;Q춈.yha i_t$b⣓ .cϓI><_Gpzt >4Lqѯ6$"X#W4q M|5O5&q)KLqA_{Lb]ZLb6S\C#;lPp.;M&0Ӣj=9[Mbc#$ohѝ?tLbc3$Uf7;@\YMoiX@/XnXI/؟>JzVtl0,&q~4v6VeNoӘ;V9v[?v|X}]^!dlTn`w/~ad teL#oPQvISpff Ӕ @8<쒌1kFZMbUfvIFZ32@ducw4nKݑ4  S-20VZy 2UѰVH`?٢1(.Cџu@JILiצD`cwF&>:F7}k~r|$F˦K\Xh>@.y{lѹ8Gu@.0&1 ]+4r5PGS2KzD#;a e,o6ō%F%݂1 j]&~:o݆VeT&v|]JM<@$7$Ft[=$-(S.Tk`L/$etfKܥ.4R2q&~7 S JmpҔFvIl$R0Ҫ@ٜbM`?0R i&q Ey~% }lṚhY̯t@ZWY?t_s P|^|^0dS Gv[BۡM=D^~٠%2Ԉ\?OQr_IݦTd |-vۺ˽g+^Q&qŞ^*Cdn Va\J}Y}z)Ln2klr e:Mտ=x%65DEG0¤[hPĶz.55uK nkILq5{<~N @v7L7KG }KQwĈ^cħnϵ$F'MI09ӯveHhњ@lF Zi rh3ٵ4Z8~rIL]ԅ*4]nVzek Mڗ] Uu}n5\jT|!{Mu _o .m&1dS#6{݆KӴ#6B BQ`HA{ɆM6p6ۯõ.\ϘA 34KojTqy^cI9dF90t^ 6#`"-3v%?lZƭk`v? snU`  '(0X5Ĩheo gj[o_뀉_4bU[:_O,F#rTC9LjXxˏ1K,`W@nY}Aҭ:$.] 9(7cʯ)_xc ZmM~ N >QIq]lnj2MǩQr,Hs$?6ǎ09I jI=UE*Aښ?oMǑI/ 9H׀;GRi }?7 זD]9vjc"^+ hr&~~4w>s# (@htdS[ @JVWaqY5655jĈœ'ϞR?uRRf=SNV=#*T17Fr^Ő*++޷_fTkk>)uLoߤ:'?&M~UcQa-ΛP=fu?6v+k065jTkttywv=W^Ͼ+jߞ=`z/>VK{M.<;'~P Um8NS&////B~9-fPZZ=]7yk~jR^2yb9}`v,{ܚXy*mlc??>oj"HYYGUvݟ8 &<'LynR턷ƌqQ ?[5¢Bnκ؛\,l*D7N;aC|xBm~5}3'MI> YOh?'AYYye|b;_;o{^Ʈ֑_|yzڏW\䍨QQ~{3Ϛߧ [&?/Fs'MH:Pv$Sn~z{ƑU#O69g'Mxژ2ȌqF=~2MSeqG0M15xhEG'MY❱cF~UKun !.sӱUf 3O/ G 5aCu.6o);OaX?@-UFX+.ܼ5dBF|`yyg¯?A;*2 CxN{n@̣,ό3Nj&}Yg7Q$UXTTp{iik}M$`om泱ˎ= fG"aC]C{~W)hiEؙg|⤟_89*(+cnjzGY OTxǍYgaG淚!CJ߾Oy:k8>DV~#䇧?XZ6jƦGO=BEzĚJ?@NSzox~/OȆ޷; =wC8[_ԓ'hjj#$?Lm yd6A  .>t997[L >0 w45 :aqh7rİ|j'89ewBLԓoܿ w4>{ BTo5}r89Oo(_vmfۯ<| ?#chӞh'QY?**ʏphI}{DZ缢9I$_eBk+//C `kkFn *ڄ ~5߅$P* 7S=4ű[a_i_߫ lm? s[h~Ei'.p9&t `myw^k{f;ćgۗu mm{ɾj6QU&1%pk&ps5w^K@~>{uzzm[ldz۳}F64GÍ=xNu5z mf'a2\F=*LjDN~cw3Ul}1wH&wc=fgyo}mP lǵ8~7D쟍m߂lG3 ͍0e񱩊CI{۶_9z;aȍ? -٤>?=0:]Klw(6rۙ[l4l yZlg>n4va|>O&miϳb,L>9lxPޞؐ"Lj{]uͱ_w~ y]B;bkC?9}fBl m۞W.UTܷݻivk&KL;ewS^/{&KJJwii~*Q%M#ڤEֶ.vbdx kkht-<9v^}EÙ O@GR O7>V'56Lm!8mW::ɦ6LJW]kDžCz00a; Ceɨn\bκǧj?4Ї;QarQyAq<{P;p$+hU ] coY;D䵟Wwq^W^9?rG%Jf Gv8Yх٦y)BXT@ vjc$<xLkn8$$1hD+bpb -bEVkŖ%V{7>cocK/]RK-<]xkkZjM5wm39 $'Z33{Lff'={Yˊ,٭^ֳ'25m$u+Ns#[Ia~{҄9DܲB@ Lδ&2O2Ilמ^^?3 )&LV&ĤneIk-L&42t!VkiKjd2鲔7}`b~xMO"S84PӍ?koY%L"@#xIIdʤZR"W̳v՛TL6]gd9HNV.WWV}y!*ZvKKӥI7A`Sڣ2~w:U$S˾ʤŤOپLg$oQBGLР*qc.^| F_675_Gjdc-@LfWR L3/oLgS}(t#}ƻ.]]7z/ʨU)F,ޟBdc7ѐr|} $baE&7]Lx砏Ѻgg4\O;2g*%g*V!\IhueU2$2 4@ S&E gV/ >$vŤ_`٧bA3vӼ/qqm@&LH:wE:FBL:*:29>-s?d25vQ&`A>wƄ'2YkjwpOZQҎfL_QRJ쉈 n꾪Aurs,}) D&F0,2!r6 ֊4U&L܀;R,Π@;睒7 c'!eZm]3\a,ٯ2VU:WTX&L.csxeҽՎM[lP&w[ٛGUGOduin:B&l6ۦ5 ~Voh v4eҏ?e,_|^t$@Hugރ* L"@#8\ :XZt}gv,#1/٬UA-SDʃSQcgr @&+Ҭ_AOYIꖗdt#^2A>#|Xt<_w}~tB; ?tsΦ:p|ˊ/oQcUgUAG7Ϟ\?3;-\?Rű{??β[M ~z,ЖIz7K=_W:8 _; }}U]VM?1{D&F A D&hGɤzK%~I.PcIsYQQ/| @J$2 4$2 @;L["z/8htX ׏;bL"@#qܻV$ҏwsT(cIcgQwXe-%nQ\8NH6%9zyѶHITTu ֠Fz1~V9;6jh=$2 4GslA ;"!8*v1K(RMLdSeNO5]yT)Y)铌)z+-BVZBXQu *feDQ~%ˮޚWuV D&Fzɽ;n^Dz\uGЎ?2;;'sg %i 2|.*/UHݲ*ϕmhZVYGQUUס$$2 4ÖDmC$" @; (2L kqK0nYӸ&hG$L _X2v52Odz[32 47}cjDݎ3%vIb#[㕋s쨫]vYѭg賄ē;T @#0OW[_YځWj}`opW[{w,j y-Ў2I ,zRQYI$jfbox[Lɤ7^^I' m0IQC #5'7'ꫢe$ 5uJ e ?pɁ um ;nDW3 4CEvI&8*vd2 4'm44hGL"@#$ MhG$IdhD&hGID&hIdv$IdAEi"4e}"j(w֥uNļA?mC&LYl璎Uò;WٱriVեO+%{&m` >Qd@&L׮B&F޺D>x1;ўܔնXg7ۍhv`29erKmviszoQͿ>wZy-W~'Q5gh9 3Pgۣږ >Qd@&LD&FpxSg7UIn&bS7[ߕ!(dS 29xeΛzdYH`saxJDd 5]AGL>Qd@&Idh*&nDW͗SV~ߨ[ٚgi9Yi޼d-|E`T&YwEБq7yVƼ +I~*CzF3Y ܪ$7wi2y2)al gZU1x(Hж`6ڍY'yfn|wZwCl7ۇsEе&{2v|wq Eoe%||aҒYIɟ7|ay4G6D<\gU6Z#]K"悔Sn}8ZVC&X5/אB 7e)sq aL#yGm{*MvSﳼ^63~ZIl+/2n.ml)IUd&/YE&3S&{崧25ͱN&sě/2bR_;ZRm'-AGۼ-Vl[kYO/Qd 2V&/puRwL s?y4BeKct2"53wQ˶ȋ}3Vt2Y~5vso^U`;A/1qL%/2Yt^/gеo]kMWǦ+k<+cO'nόD& "eRY?%UE}:a3R[Nuiˤ__S]b%H-Lei{,_U ˏݜ&L֧{Q&fv@^d-n22ٞDT&O>;&I&M;mAG_lק$:LG&ItTPY?,|a,YGTSdz{Mx CT&O9mZx]_92I}>ySJՕUII}3^Ȥ $v#d ?$_Kˤ_{C?ds}خM:$He>/D&{TeCb-ˣ߳ KO<-[9^-sҏzdr#[]%A~HιhvN&>xTO-V&5*;:hWӗ_dh-N8ngپ.nb#lٶ<&tcpn:ܓL&w uw~t O"zn2 }29d(ɑ?8UOd4d}^mg-G(+my{}P`"yO2x"PIIdO>feptPݨɺ9ޖ.} ɫj\,2Gma U= -GtY]xͣ6wN&5`+Zt~j܂${:oydrd`WoSAev.X&;hm{V}I7yЛ>xeOh935/2grו CX&]QJu]u]X,{a^nN%% 7=Mq''DrWWi*z.2ym7Dej-h wuUE'N/[X6px}/>.?'Z^=?i_pYvw~Ldh%v3[~^t<X4u( :ұ;X~΂nQM9ʚ-dfɤ/'GxcAv>t3t֝msvE;LUxig{X.%j{oFMIC]$i?h=QzSsΈ$=)TT?䔓"qDj-/}DnJ-d DE߹2h=bT/_L"$BDe٬A52 Cu~Uf거n I ~WR"k'ʆn?n=C+2L @FځW6tDbrT(c(ˤ3]M@7~O)Ka*~zqe&%ȫnuuCK_5ɤ$Q%n='$_TүdU:IdAL"qm׿n@; CJ&5".ܡT(<*ӧ2 FL3ZVҦ.Ǎ[ #T4alyIe4]t(y$*GRIdSuvѳΊKr'3L"@#px46{_=hGa(ɤ$/d:M]Yi'l2* *ӨMsAHT*;QGU*W"(A2֪uKZj]Z^fI] e:6rx$2L 1f_wK"ZY#= e9GЎPIEdD&Fv$IdAQd IdhhGI@&IQd@&l<ғ<@xW/," YN'Nkn^+\րFv$a&GIWgU>:g۹cr,~jSiYsg|_?n8+af"4$LfLn~!ֺ\eǎVZ8kƝMLD>)DoTh6'i5{۾)񳔣hG$c'>^x?5zt[SO;-I@#0EЮe}7m \LыG>e srsO*.EnnRF`䛿(G$ǡb ep=$ɁWZ^ o[wk8 &O|NZ\zF`vm8*v@&?.0oL[ES^3#\nF`H{hxm@;zH ?z!EYȄD~/Q.3@#2I#9( 2@ιhv&j$VFaAdE& b0Q}C?x0o_>w}%VRV5vjesYAdE& bdSj&0QtѼz^qR*y]k= >\.%@#L"$A )?~NzUUWw=x$OE u &=zMZ'@&޳*ibU/RAtl> {~h:+3iGI$2|q_:OO:~ҁǍ|Ғ._׆׮#([>^qo<iKOzO8~E".L đd}"dQ݋Q3Ȯfo_Қ]`٫:Dc7v$qY%X9rȺq671B'rmv&o[<2 d]!&}ƪPN ~E& dhId2be"6Gg˗OsO^f62>2xv?emزVF[I26oշ"6]۸+wmś_?묜mV7<[V~tg2 7=]K"ZOMIۗ5V%vJE;L$ W&եEH'5&Z&N@T,3jb'(-y³+&GIU!ʤ߽{bo ђ}VVNfsz-[կdv;Rcէ.۷&51+寣٫O>n ~ PҎ"2 L2yexTxy+L$@y&uIS+-ҹ᤭n}2j IDqeLx"݊6Vֱ;V9.WByRۦoWWxư;,y|7^b?T&[c;ML cۥcf (2I @#LfL X(Ϳo^\&M\"ZX+Geľs2Ud$/nj?&d2 >Vv]M&c?T2YK\adwE&b+c 3$L LxTlK ysw-Fɤ2^[Mj]] Id&Zl[_2fu#ŽGwQ_d>I9S7ю"2 4IexҶ4L zp-Lfyݠ;ՃD&U/uUu]|zeug>˽z"z $L 2L.<:M'V$±Wv2YB6XdA9 ͵ĖbRȤnni'mp2 L"ͼ#C\#- tAhIdv4irHi,'ޜ?n3 2LЎM?"W?AGF cdS4qT(zFYk#ћh -@#)2k hQ(NĮi'?ӿIt-~:{Gg?th2P&8*vbhڬ-ܵyDl}__ܜ7eNA̗?X^mK~s-@; H of.oSN(8`^4P(x"}3/| @; =`{+<~⾇֯iVF ~vᆺ):ѣG]s95hPe^xhײD!&TiWu6fs(Eؖ=M+iҳrHV7pM19ofoM 4ԫvĈvIo6?xePlp WsQǭKK8hhGG4mꔦUjL?UuY8y=>ѣ@Y"VwȽcW?{G<|%ssF=qBc @#u~Grs~;a?y}w\.6Sxg ;wFcN='G| }(#? {Pw?)PIDATIvӊ?ZbFįbӓQջn!lae8`Fҫ3گK6)ul|s zQۢ]=&T Ο&G5"<Mƙ3J^Ь3T*[}Abpt&/K^3{?ӟ:->夢?PXЦܴS+οڥ ?s/_}Ǎ9U{E_PEPYuރGW]MT.pVXqa)aBm3J~7k_'eǥy>wO81O!@fg~#w[Po~Y(}N?xI7I-:qrbw$w~(+ΏQ[^elřeE0W[pWQ݁}IENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment4Distribute.png000066400000000000000000001047221462611535200316330ustar00rootroot00000000000000PNG  IHDR")IDATx |U՝MO  @ Qc!(hFAPJŊyШ\t(֋S72#)2ul/;ږQWfg<|ޯd/ײ, %ђ;u(* o;?qW9O>?9HQ۩-Nq*i;ﵳrr> `pd_r֯W>].ܹբxefGɅO'*7/=wiSVI$@ܧO/[tp=W $Q*s/>y8_+8IӐ+n#%Q]ZU=hM+6}I$@sjkIi/7҇_y#L&۶cZHjyvaqa%a0 PT;}P20IH3𭆏 7ToF T$L&˞/wPC5# *I$@ڸ~ ~N$L&Rޛ6-|0CBMp> P& )LCJo.G>i&G?͏oϾk ~>odWu{[? LϩzCR=slUCfaRqv2ÆV/[8v>G no9 $@yݽ#PTm_?}((LjdؾyHhUns/ݦ%zI\za*-c&`4 ~Ѯ4z:oH0GܹCu؇uN c/߲$&LCd_Hx%a&PVE>bה k[0i\V+\&z  P~leʅIL~ۺ$L20IQ]&ه2ui ڤQ$)ƩNtjW8L LR&ه25?ڨ`f?c'ܾ5vaaa>Iz7DBc[.©*f:)uujI*$PfT;@%˯=l7H>t2Xf0 6k$ɀ,+8| LR&O؇RۆeJIߤF#%H߽6}-㎿wCcX8$ =?{%Lij+6}0ƞ9vLE{.iE_|Jϫ$s_ m} +Y Z[N[kw8Y0Zm(k^{CYM}n+TDBB>}mΥ'C=Njƻ!RaQ{񯹏bQ=`k[sã^fD9,}e &_W5D\3_ZeglMT|do`n4TH4u׹pJ{0Y9~x+&ܟ0>Y&˼^mb+7y&6Z;UNsըœM/=n(lu}lE" L淟 yaN5tsn~X&Ӫ4UDSG5UarɃjzD+$&~L]adC!ka!vz-qjՎ. }% F!um }&wL] ~-Qz^s*&ӧ]yO׹I!h4Qշ|]?Tx]}oV,w9+:EvQ_G LdkAԥ+g Wii$L&S@+6׊][Qg(]ꔎIMUߦ"XF!߼iQUv4LQij-aßӧӻ81=A\龲h]Vz!Jʗ}9=p աӧy,0ًu+̫Id72m_uozT)>Z;y&u]jUsۤsQh NXG̎I鮺;2JDz-BʽUǠ0[{ AsL -\Q7}xǾ0UPv^nZGA1¤LtVs0iv=]ь'| rFtW^ fE"*PkTTh4ZF9^0 @K[NFGEpJfԧM߫+F[!x.VImbrdW<7UXԢ@LL0Y ۼhQ:lG|_Gjڣx9H_Gwod [چD&hI!eheom}4w%^0x|s9u 5vr+uWͪ0-?5Ls%Lv 5Vl:^;rk4mu['nGdO*l6QIe&{ 3.Ϳw^*u._BqqlL𜚂6(N_S*LR7M5iRKmId"ZKx>T,SnnIF"֚=&#IZR`Ѣ<)QfLrM~o)LNO2v6LFarU^g1{ɾ&S*sjeװkWU_7|0IzR]އFqȣF 5Ycuӂ82dj؎>amP $[aٙAoU։#!QJ'n[|d MupXkZ|}Z Ɍ:ط޿j_2?t ]QI2{sT ϡmM#r~8 dꔂ-z鬚z6;^> 53Nu3Q2dׅRnͷx!r*?$L-ANSjqWm85F5mrk./D}7=}Ow}'|>[|[ ~<^|%_=5+o$Lf7˯?gϘ4hqqssnsBI&]f8žy9n0{ rpc)djTrP@7Pf/:Cbl;V?oUf|g Wϱ-ij_tSQ*=sXդY__/v0@o=Ƽ?3ܱ_h7&v7B<:zϸrZ ΄ɬP1lx_K>mѕSYp'kj+k@a]25 B :0\C/&q~ҕC_ߋB@Ƞ~ aׄ*v iT4x_Jq˖kE'n$a2eT4;韛дduO?d]w˷,GV8~aQ:_!0 dK#zaь)TRw^J#k:i ¤FSLJ#G3{N<ؔX!ZKDtƊoʼφ 3œYsG_1η/|rʩc/[r4 8Z|Ū&A쪚8m{=`*,)j@Q`ӔPV*?D9H5jrpsiou[0LD;;8P Xa0 d}L0v] ;]yW瞰XN¦&&4UZm9؎m}]Iai&5`5=&53Q jVZ5&͵qaaj kXlpCt f2 HZT ̄;]V?j XCO"j[0j!]hlXnO4i=]gcoJ$NiIN5r8aҌ j; GE<*`*CF->i*kHf©s}(`jAZ5UZj =^OsFMUk3'mOPu?E9 `cϰa iSOX9U!AISvRf*#PUVuUl}(jO[9JS;a sh=^tWy($@Ǫ,0I& !u BtW&)$a N20V@b4ɔ!&2Y:DC$ShEn tWd}_']AY$a+a K%{d0I&F:xMw]aa"L LRI$@($>\T#0 _CT2{Ȩ2${~*-cERخ%]s JuWS/:0 5OMRԝJ$j `T)][_ZER܁ۿ~o2T*Ԓ.ozI$V4COlB$oڴ`?Zv^RCIz|];ׯ>Fz7lW?uCCIz擆ث'K#c.ۇ0 <^>( Ͻ.݇0 eٰ?a+ݵ[ n0lCIzF(7DYztp?{޲ݲ%a=>]䗎,+wmJ$л4JBNn'*u]yS#D$@Ez{_9ʩH\|%ݾ%aH@[f0Q!_gGb闹y9v(9E>7y=n}(7\DRP&QZL9VvNnXw8?}@j`$aμ7PL MCy׮]@jC9El{]@_Zǥho:~,:؇2zv͌SG)'Ʊ}(< /tR P\w!J-Lӑ0؇]Pӧ{.TRP&YZԿ8VepC//K)}(۱8¤BSg`ʖǸ?;SH@Cehst]Ǩ;|-F%>\G XjrjSu޿wz#P@7byfqZ{0- Vl_L ܳ۫Wf~}Zyxa3؏F5tl-Lq6dwdx yF,(Z/ä:_ijS5m/0VzUv=y{>-n_G᭽*>-829/xjZc@) o#Q<8O];)psVikm [y=ߡ_(f-?FypԂ6\P#p^k&Cc[a܊M5`6#ޡk0q m@ӔԝV|rK ,j%' y8d]C@k8$d(@ )[WMw޵sHb;;(V+mZa 9$dLpj_G{OqXmQxdŧvHLzS 8$dhqV|yIZ/PpCHFSo[y6X,HF/L*Ti8 IӭtWJM]!$ԩuS@2f:u Z!$Cl⣔X,HRxSgsHɨrm+8yIPxI7-$i_G_QT_ѻ 6iT,Σ@2*fq]W<6)UVl%( `h+>US`+|_ݾCb_w4''fN1il؀ATWAO *oeQ4Ҵh< 4T9~E߷bS8E ? :yBESI /yI頺cɊ-$mP$Gt66nqq][I@s9UM\DT-sg?8~Up!mcmyvaqa :Ӊ=щz|eH/,6^j999wt66 cjKJ>XPRg_Ts R NmEnV+u3=ͩ@;E kD:ToFF85٧ R3 ۠E> ;( E̾6hA9^o~pJHqၕ,#A&y[\U Q9*]D'J:eRe{Yϩ=mP$Z>TThi,Ц.t*uS3{ڠs}*ڠkO ),*|ҁRX6lO8;Cnn}*ڠӦq ƒ{PRA${_AJZ㏜Y&s~ADZ4dTYpίk5g,>99MlNRo[Z|3Kڠ>Q Zzmγ ʖqj)&i(7۠`T-=^ܷx"Ņ:r&Tm8suV> uf锺Or)xdg:mI1OaLaMha0LOuQQJy`}^W!XߟnczFDoz`tycaфIWuzՙ v( jTtR9tn5UԂ9G h=tkq2=Gk#vo?9 M?Lv>uBaP(5m/=VI߳it,aRJmWpn:I|ድ؇@G.¤IS~}JT)H9)ҿX켨CL4R+԰`47aXTg?h҂6}ߺ-8]̌0(`äL6¤B2¦v|  ~ nʶޓ=?Pp@6󺿾f0npXW@1U Qv)]%\I3Z\njDZEqyҨ\~L6LN:W zcNFIuL2lZ0LW=tbԡ1a!یdHmP¤m !4T|4 L)M^&X꽘pۂSMʹL3aR7:<F 4` C (Y[a2}}( ه@G.m¤؄uHl -PJRJQ D|-&ޫĘZ@jdGG$CEoZ*(k&[+4xziL {n]͒0F)9+äjemБK0iV0 ߄DM2#uaM5Rlmzj>v6æƎIMUg-aA&umd`a4礿*صu_/fj~kaRm-c 5u̵&@ޜ:r)&5 ,&f4tV&l-hNTk9s픹F3&u N3l&5"RuʳIuO0lKi¦k0iB[kG#f{"s @v z?ɄIWj;±6C>hӇp=¤jؘ!sIg8\Iu.7uL3CRglV2>} 4}e :%s }y&Uf;`Ss;IuO4Ftnj>/eV4׀kW7gF"ME#z=amckaҌpz.ck!ރ &/^:C#[8tz9LSpsMR>VgF#QJ@?WGGvN::fƶ0+j@#ބAuPä:qf =Ff@w$L=9t؎_[$h:&͇U2 kG:k$5 A=Fml=Q@Rm^Scy6>j+LK#}=iʴkl jo Б2\'*G ꜙs2l$S=utOQRͼ:P\ QϧPS#b:jK=N|}-\GKy47=Sc6KmP2o:MP9sQ1L_ BSL}"fSW[>򷍺opW5mB;zωT&0 Бˠ5DaaJ[ILgl鵵Ek;ek&i20`Xp?ZBjTzI}HБ˰ҧRf<3~UfhUC/}u2 ʌ2+Jkքt\3?3¤0 #a6I)iZ+G&4-,VaߌJ&i2XQ翹[[ "L@G.J#H#-ӓ#0jZq4֒0I :u '"L@G Б($h($a@GEmE&#G&ADQIQIQa:rWK<%r\լJDApuVI7I2#{5˖k[ii?H-VPU%>ܢ&i j׶A R[}W> Ҟ6/!L97טy9].Kꔵ&1[6F J6Hdw,AɞjӮJ6Lj[oI{C cd*}Rޝa"L67$E #:rVi_N1vw؋WVW~;LkKɮj)oMHǥ+g;^jϸr}g#GmPۥs3Q`ODUs\-;sZW]5E; :8Q l t[M1&KʮAIڠ.*mMm"]?a6:`_Uj{X=mnDjQm©iٛ|H<Wps{X'L@G;LR/89$;536Б0>_J$mE&#G&[Ic8*wNN[5g4mE&#G&£heԩm嬦 ($t.,h`w 5Vw(@̦ R:lm-"L@G2LJYmcN]MDQIQdmT6pQa:ra2Y @6Бɪb](b`ADQIȥGv9=&e[Gr[AE ];r3:OXU2CrdaRζb{P*Pj 2zڠyf6A*rQIqT Ӄ֍t;tz-LJS@:q[qQIQɄʽ @y drGWV uԋΰϯb„7p[x}+g/vn|iU83:׾:I*k&u}oz^>o}:񫟺φo5zM}j*D}˖kWNmI K6k=c&aRtn/PWK @lt6Aq}/]9۽Emă;4{G߫yj'mP_ޯދfHv๯Q羿`(s*&2m廞]>Lߦv0 vuԑ[vYnhҬ-ӡҘ#NiïѡfzXa-lz=Rw<ڮqݼuN=|iem~GVu;qRNQDOK^K&!SK0ie(uA}]ηWpRr1-+ojt.:i hއ 6(xnpXmQbډ l+}]Mߣ޿Gmtݻ?5~2=+>K֐QeUۥGm"}I%ՑSKO'nq_uHd3S oק jqR/l@G(:lO{ߦ*:& F(աT*&EۄlY@K@VjW~)LitΫ}^C3Sv>)W:M` { } aWևTam^7.o:& fED!G# dPGN>VnjN*DΆI=Lm{T]X ?>a#0/r}vd^T5K{X3eFDm{G&V5#6HOi%2 lO F1s_$Z YٟlL}ysٜӉ+ivcҹt@QcV)j[ʪvͼg\:NG0t&Ҏ\o:qdwƨ`*T/I @y jЉRБs?V(kmUT,MKt&aGhn/PCXKkAZp)E ~IQHgjjUk{UJ0UPR4=/¤h^zgﯹ&٬)ko$ONO:t{*w4 i*]jyϺn4цPoiTŊm@Ci5hZ%J6= x h!L,-򠑂d+5ISTJU-GEgZ46(lQ2۠d'!LZ՟сRioٰ i=e^T*Af'LS/*mBDU@PR%E2-s Vlt *(#TT~v2&kOVn:Toת'_iBnexWR 2K!L!WRF6L#~伩v(9UNS6Q]ZxgyIC \r`PbA$+C,/`?/>՛mIC $qչy9Go}::T/fu~SfŦ*Px6Iڠܜ#>ե^itM,LzOc$'/ket.+7O9995;@3iN:~HDt]Aҏh[V&٨ƍWɠ6=⽂‚3g@CiN:D? z 7y]3vOU%LHI.-쾥Grrr2k^T5Мt *,u66HSUOۻ_kO $ _cCT5ge]/Pn9XTZ_Q]M670j/=ǔ@IuUe|@~f:%Tw++Y~?j nE2ܹƊMwUjekA*HqMDu Y= u:Eш;E0 @r#_<>dd١~'ErroQT"%ssP|tЈ,Qm*N{ Y)( S99vw _~Aw]ߨ0;F3ۺ^0 } B$!i?MojٸEGGڨ:Im!B@Wb{P*PuCnUOveqT7p<Ԩ$L(@~/`߼)j!2/?]IS89.[R[d)]̊MuUШdIjːlރ9DՂZF_7F+Ɩ $e~$MNYt%]3 Zg:]&D?O6@T @ Dν̤p;''9]մ3^!vm!OEdkՆo5ۉLv;qr~6K8U]6e]jsr^rG!GĴE0<)Stp?{޲އ MVlZ;mVGT{_/@}$ *V>THj_ʎТۜ Vl,7 $ZZN ]=ϯȘ}@90**)KNC yr0ڇR&-oDR Øk((Y5us#RF$w{R#LK^fH}(orm֫:ŔJ\jC9l̐hi7-@ 5/{RrHPjQ g?{y݇2ڿ)) HyRۇsHۇrP?,{O%l|i~#>UC>,O>@: +82? G=淩FQLɑCp?._aQ4k9NVY=q8ڭʩ-体¼}J~o>}G#{˶uy9iiTbG&Ygů{C@Xis88m!吤ϲYr=,nWmTcDz p8Jyad0J}?ӕ0HrpHRNgb2^-fm/Wp8&v5ZUA9$)i/OpH}ASKg3 ՘/:GJ(t @ZbF^4ZjGE-@r8:M4a^őz? Z_e EORp|يʱV4˷ޔ~[ UҊQ2MZ&}?9HiG&pH+LvzAGR2jֵj5k92i09$]B# @^є̥Nшm @L\0n7*4 lUkޱ;lŶa&PHL+>Ux:@6ʮf%]V:@$6ӊ.pf{]/ iArIj_˪b ɘe*9$'P>8pb`2[]heb"Vl7-@ J)w$R`HH6e} H-$(7[-0d1+[*՚,k K5[ VtC?t\/Pzʼ.|b+j]X^oS;8>4EkH'd%#^iuԈ+v^+% Mpj@[콷2c{-@h]+bg5/nzs5V6tQfd bL՟x^x ^cU+at_IБ" {{H^G`T`թV|}mfdG+~н4uѫsUY۽gG'_z:/V%#5յw~ ~/{ARzl09=P!ww߽[@Ӵc^P܎@D GQ#m^zx'A 5sVN!ըv*+~} z\ SIBn=?"HM5V|Tm!Rދe/ܮGMߘUSgE/r_Ə҃œY`]Ikx`e v[Xk{5wXQ,: cDj⫩!+% MiUrX{Z;d ^S |MX_Ujl?RgŷC 6Yu]MZ"+:1dz_ϬV~rUǽ?@fQ_ Sy_cegT2FB3u5Zq*u VFYK[ XmOR_l^NSđI = α6{K@V9bΐߩJ>ʩRj_;=k# YQƷ}l͊OmPN`h(,UN)0`#_=ӪG7U6vV o#)݀~UoPـ#-)4NzjLeyN٭[5NˤmӪOwa7xک2~L3V:jc m+Xa0ZRCwjjIǪOtIu\lߺj;NO<`zO^<kX=y}M_:>wĦ2p@?uHBkN8񴱏O8m̪y^Gb65H>^5fN:PR\QnnΟǎ4 fwozOmpC`lU=+ƿۻyK9v̈cEE Zvx©c~oL_?Bf=j?4oۧFu-3$ vH-_{Ae&Ns#g[ L:Gb4Zi8c{=vJ_8cMPEQաj+ѥп_!~UKum !.u߳*#*3b۪P8h/>G-y IHKuݽܞpژOǟz/gY .Qe' V]rb2J !Ae>Svޙ7 ?}%ZRsj>|ed]gE|&y]1|gL/އ OEBy97G"GlGIғ# xOEsGSTsiEسnzΚqV ($/)'鬄'*QSaC5c6 ٭$yޑwK`ڬw=kYv~9i) uo,ΖvE> IT{/#+ʏ(2ԣ߽ywYZǶq׺{wj|Q充_>h}/OshRN)䄩Y$owoҧOZrѻ"u5۟>;U ZJ7o4M׷Mat:{~>82ʖ7Aף߽ hk|5 BTW}w/?ۜZ  OkQٳEw.}긓O(4=DuecZ I$DuYZ I$~!Be7..|Ʃ`t4>;}vDؔyM̾0w>qU>JNMiZB e %P$V7H kG}E,H"hKBe_\TXd0${oڈxN993Lfg3眽NV4J\(q % 9\Jpfl=_ϲ52Vo Eg,<ÍϹus~K B0pQ:+ѫ6~E?2 ˭cӢی J&EjѣkEk/Q2WmO+MpWXiv>h3kd $9rdv|5޳&kFcS4SH&n$W[Kx,X磴d N&;hT:}|%zԊ zno7kTHe5l'ՖƓ"5429VosrzEnqw|!"ս`q{$2 @!8T;} GA,deZ *R x?Pˊ] ǐZw0y;Ǫ(.dZɤҀ!ڵߺ]?.[K'-ݵ:I# $;FRʸێOyHBH+%^XeTJj ФiV֩je#۪ZW"]wV¸C띾֖rMʣGR`Uv9}-F˭hVKؚJgQ=>ĖɐtV@Cz,z n͗.f ?t;KD2Yn1%9\i 11{T u:D2i\jڡׯL7K\/fk^n_^*aPfb/omV޳q9:dRPg]b["&"#@ O=lťn}6V29mڴ }1uq[*M|1QcO wamv9kZ:]yU6#VERw7k ؏yޢ2Q Vb}ǪZgKcjai!LY8D2yZ6X Js2Ym&`Dk NLV^׹Mo+GLzWj'gqF&Ims80!-W\vnʌLΞu7[]/D(HZ(1-w& A+vy6k]w$ؾiYV`drwmVTēj:֏"x0hiЈz )/KLfL̤` zHt482YpI]j dL:qd雕َnV8" dҴRݜ_t?LM␉nX|z$2 M?C GR&Mku>FkPe2K+jWRْLFbTn7Z-#I[+XKW Ƕ<IYwW:ɤ|; 1TMN׎1PeRSO֭cL1A? O9:e҄L"={ץǑI~ϽDnʵjF&Io|sr$e2h tǨ G&LC;Ip,i:%fOuk&0f~_iQ5 )f,>Cd$972Y}d^7{(2 *{2i]6ҍ֌.qw_2r4en2R{WL$ҕUB%D&B0nr$eRh#?rd8qM~jd!ohdX+w*muLroYLYUhqwEvfB-MB&T即L.eֲz͌LZ!ʤ,.F&kuVZ3~r;tMcO""2~NQ)3{"bt_Iu2gx=%-d td$d2O̺ ֆ8 U& Ū;ҥPZe49}㝂_ywj1bIhȤ--fz:-\UsOrDeҴZGIg?22:dr뉮іIؚ.=w> S&[4"c02i\o5Idףyۥyg5N#`(G\&x<X C "ҝmID&BpP8֧"Q[T6+b{|@uĬ1uպ~H׳'Qg·㕟@$oҮ?H.ǡ܉='2:2i5N{_f<Ipƺz򯟭QU^gN_7?MEqY1Uo=.8'( w<`mU:# Xщ-&ٔF,e|]7Jw~tYdczO-$2 i 9*2 @9LL#P{KiDj v,(\/`(/"ཷDJ$2 $2 @9LXȣE乄2ֲӽae"ynf eЏ ϗ+ MJˣX <2L 2LP"ҭ2{k4c/]U|5d$0<!Ɏd0.9TQB^qnrS̑Id(ҎօDqk gPr1deT&1 6iKH^e+n< JK5#(OۯȲɷ,+y4dGrd AHݺ>j xcwP#vF[%Ia>Fp? H"f]IϤ-ېmEjѴJ%IId(&-QڃHFErm @9 (2LP ͡V;E[7G6gp6( d=^1էmq$PP"29eRZ咑IFG^'sGҗF&Br$ɉ.2PeOsx]]D?LF&%IH勨#zkk8Zo,zWkmučXd@&GY&e FΊL &*TK7۶IP??(G$ѓI/yW?F&B`^=M ;S8dd^7{6VAH`G]RGx2 ? ٯ.A(2I $2 cJXE2VEtVJq9V\WT5mYfӿuT[v[ɚ G) vגc*YF¿F_˟!7z\M"BmWX!'"=Np~ޫ rv;]cdپk`82N!DkɜϗǸf>־r.e/Ǻ7[ǐr$aǬ6 M[2ޏipO),p=ǝ5{{e^z=D&s^Uy;m4/3#n'.vgdB^t}vwUə㥷SGew$kP'ېdR FPk+ccDVj9b1n*<9& m fD+fhV|۪uhe[_;^jѢpyGv̭sFt_zJ^u^W=y/5mM+dʤO 3dR~X2Yg.ӥPt7]w+|7-Yܡ[Rd]g횾=3kSN?ߩijĺ6'd!$1I[&Eo_kq N;"/'{c%Ez{FOCG|e/䵄|vwVoo=MI_$;ȣlG^~Ɠg_}ݏxIB cW1$1n#bgy a-#Y .5WE&S&r:X܉'5o-?T) {U]W`G|1" ?cԗF_me֏^$AL^tɅv=3K'E0d#Ss>≢Jg,+!i0J,#j֓EЊ6MdRZHed(ǟz@ %ZZ{W vUmvZdՊh3ԖIZH#^/d:Fra,ժ:N1e2de0`dӿ{˿~g`|ׄT7>{^2UǓIs؆L"2)~K.kB#3)B*bh֓!ےedY[&E>KȦHL``Zi3"e*A$d>Y?D/#-Ѫ$d^׋w|LLO ʤ(>5dd Zp~X?__x2X]L"2)-gBJ'/[卥{hJx2Ԣ)iv<V& Ǭ+!‹L`jZ+P%1>6உ#g#)Z!d.Ӫ-y$IlSq")Mw'"uÐIgd2׍dKH2:N=#)6"dGxQtbquFN ;:7}j.F'Lx"o9L-<2RP8x2)ẹ=!#i%+2I|&*BLRO S[ZD ƷLӿ{permLfwN}?d[2i& LLsb?CK*ۓI9?WL7Lvr7u֭kd. D&S=lťnbmCntRɺJjk~"uԑGw}כ-Uft4x(IWex-drOxi2W֕$+2tkIɃ* }GuU9n W&CV%F<*t,p68' IJk[?iMbODV2scho2i&/<0rdON5 fL*k?+LV>Yטy|"|x=B2in@"DIH{z8ʓCdr?gtC5uqɦ+]T"mG`jZΞݶK+6Nߣ6Y;2iZ vƻM+:g&5x2 `Kka曬tsWl/k}drʤWe_S'cxb:qIGC3[!ݟNȤ\7zck}{d$=e(2?Pn x ,QZ(*-fe^oX7n왞۫/vK>Z>d;Ҕedf}?s ^\I?JW,˼e[y닏ۧ]r^edrd2 !_+EN9N߸QCgัlؓϜ'HGZMK?L/xJ~rG)?^ϡnE&'Lx~\}-noy+.:u(JXsҐ1_|鞤1" *'h7Q$RE`/ɛcdɋgҸkUvD% IKkId A(uZd2e&Fߏfcf4I.\ )"k?}ʤiU4gZ[=IO&MIt+[2)uȪm*.idRB"bx٥=IpɆ̄Cd"$2 `Pd1@خN (29eRM>7R&It"5L8F* tMȫ/IXj,/3JˣMRZ&M+l$2L d;MYtI@3y-Bifw93HK_<yàVB[&GRtS"nF&kȰn?f;+2L @Z(h"zc{33_|R{AasJ6{~7=-=aXYրBr$4ɖo׸|jq=z3O+6)+®!퓬od@&O C. zGt(B Ey]}OT*r6( 29vG3z"33n@!0AD_!G=QCxe'3'|עYM&ҼB_pV(ǃ{!L|HOrI@.{yse&Vfa AdE& "erڴiݟ}ݫc./}(?nμ=k֬l 2LP"2%/o#KV]!GK EZE^WzXOG[ P $(2IG&mr̙ng7{ewx#Ͼ\O\2Nμӧ$$2<HKЩ3Ak?ae4Z~(z%$AL)3g'y;8eO.\iFO7B/_}{Kȟgd 䟏[xܾc=fC47(*H=)vO >V+Ѩe٫r"8Q"2JEX)SΞ0sH-s}>wI'왟ydёɑL&c1,'F8Q"2 $2QNt|+onۜí_;uY9tsxźND[y[S'$نiy-est[&žeZ>I>vY:Mg߭WzMq%vM|/ݵheNOFgg_= JA9L$P W&KQ%"#]`ThET tTS]54UTV1L;K5=CЪY[6:Ѧ[ѫT]}cuPY/ռuu6Rjƻi~j7RyΉ/ ݖ+?*m^ NFC()GI@&gֲUl P8494zV:[6YLZc(Vg(2[^M-dP>[{]4X}wKΙ5$L 2~2iZ"[Ȉj IDAT~.=L-*B&Lj^XKGgd*"/sd? dI|r5:_U8[bda2A %("eZR_;3&r$Id 4OJŞ/#?r^X@dRZvԚ# {u"?խwJiF ')՚DPd> C1$L 29yeRZZ-i[G&Mʕ&+oE& d(IdҏL"-O2nN_rkKWpC&1$jOC&s,7|]? kJ9!R.KKt282Ɠzͳ=2[Ӫu#s;m.-dD&ɤyEPX,E,yq͖H:]Be#G#!MGUioSkZ&8iJU»t]E)ejTƍ`@y(Nv'3®}z*X5~h}ۮd2yfWj4{aQCyɥL d,6:}"bH|DNc$Dr\t0V8i:f%V*BN0&qܟ~+f鿞#H"Qt]zioNz{>.PLv7R@9 >dڌ:サED3Boed|+\6/y9'<[KŸXGcsf3 1#3g)(oR o(Ga>m{xiB&_|^w ޜ>}lFWӈWާrkf7MZ9((GaX-Sվh_h}1lNԝ?o;ӧO{{ΜY:BPPMSN?kf?s?۱ -Q>x-.͜1Ggwvvֽ*@9:HO=9wGϙ;^&sAY{v=O9ٳ-<اLr?_(r4Ȏʌ1I?\7FPg8e;w/]U˙)S2QU=kVU# PNdrym53O*{Vm{qZ2ZE64vʉ1=̬,:}=fsx$: $ "h*H Phg p_OXxoggmѳ;#^q7^㉔t݌<-o\f:e}_?ٕ]b=nFQԩS[03NKUpkVVOV4/zY}ç<}֙,X?#-s.-]vsk~r{ϝ7xrfTB׊cQ4$~^d&oiM]tRnT/KNMt_]ry-9U2 #n+>/;ﳫVUvGYqyhQy|t@y֙:C"y7WbEVA{vL6f}IKҖ8?_ro/%Zɵ_'?~5n+>b4B|}5O1IENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment5Election.png000066400000000000000000001131361462611535200312570ustar00rootroot00000000000000PNG  IHDR")IDATx ՙ]0(PaLuT J**,Đ7 YIkvɆ,% nHFbѠ[OLTwWLTw~鮮>pu>=_C9zTY]8@irVG;~R^Qtr+8hZQ8O.UTV/;dv:I[~t k ߹.Um77?/n =_VT.mtHLb&eeeP߿n{.C/߁IB=幗ʪ?8߯et1$fXQQYW#zUj8vu ZiLb& JG[5bݶ>\I7)>ޯKLb& Ǎ(_Zf]S_}CL.O$ʻs^W$f\QU__Z[_]"f3 PXY05/P?f[U^1Ia~A {14(U^+*w'-1Iƍs̠8謋&|X^^&CI31ԯ11(.Z&}˜aCYfkůd&.z.+t3f8u5vه;txcGٟyQәIzHl)\b&A iU3}([>;*4Zx{{LZ!!fJ3 ?a^P4PVVge̤"^1`&U3>5W.hΜ'#O|3 ZqO8hJ 0$PLdǞNw]SSӱO$foMN84Yr-F9h#M8Z$*&3>4=JE(->cgF_yW\>Aef3  .he<_e IT,f>6}(ca&~F_%`$/rKu~i Sa&y3xjG1Db&EkBfj-dmα3f ۇaJ$f T+>Υ4)`&QPcJ$fuGsz\ZC": ITf2>w׍tm$fJUfTkM ITf݇_8tm$fJVkoB6дDj&L鮵t7ho3 =9:AftkS,72SƏu9ۿ!G |Vb$fv$-L.l3dS{Mi3Ǯg0͞tKϴ"[oIX0±K߸qnBMUpuj]]lMٮ}ٟVԾzU產`sKo[l'xUK{텎m}o_>``~ύN*Zi;n.{ο~#O|3 q5?cD,@II}Ɖ]ɘ5Ժ?U ﹴ؋;Ly(ǚ#ϯ}v~nuAtJޑIY0*ζ3lMG;˞|}MOI)!s3.>yI9:52z6Gޫ 9-}sgc&c7RI=)sHƽқ\qtvjkuԞ5WwMfGO[;5&z3هQtFsW4b&11`j+]m%+Bƥ3TUcZb9B~~ӺN)u5~VEWoN[چDb1]Iyśpdl^tt%\)w"ol_]zC.XaŷnIImD抙 |+7.q:vLK2J4ZiLt5P*59djmv,#ڟ3ӧ=h"jm)ΩDL[M+)3y]vȥsb&1P/nn*~7qZ mЍv$J5{L#IȰ(+QJ.(R^` LNh {j&!3(odwf`&㣡'6)*ګ $fϋF;F8*"V `&edrlwρR3[뛯{7y&rg*ǖ(Id5ńNe8>ZSXPDXim&;=S<{? R|]5>epN5u&F,sm&3`&7꣭^PJrM %I$34IWZUD4Ӷ%J=Rk'e%$Ŵ0ͤ%x\R ,9Dee l/`(Lb&0FGKm5ZN3T]RQ&$*d3ǯ wȊ68?KN>e_V,Lb&zL* l!9|cX!F(Ϥb&Ql=,opUkJM%ޖnJr6{B+r 3ȏLBťC;"B}~5T]U]t>&9Lb&1I+vF4ZKCZId5`&5dHz3 ;= =3m9Fxj9KL[ʕb3ɷ|Ko[l_>ws'2#4 ~gA|8#M>383}qL3;<ό3+hڒ2Gd2 $LBAg_|ʾ厛쏞7w0iFX8'Ink30Pfr׾ɇA 4+K E$d*Bb&f L>+OL{򽆆:`&! LB{g߹iNw]Lf!$f2TNO/xWk ]`&3SlLb&Lk/ؗPw6[Lf75unxa&1Eb&m ?TSSs13 ɄGe߰*zcLdScцVu=>8Ͻ^Z"5s]u]>}}Ez3\jjϩלVua05F>Dҙx0=^fVˀw\9kƟ;ھm' CPGz^?I3Θɒep58h22|ڎ7SY?8пA[wTI`&3!q \ C42\f9>~} Cϯ"* i [*ӷz S)*tn!: 9;?_i2ȢڏHLf0kNu6zbStX$Pt\nh95`谡o?}ϸ3.+g'.9e{WPyIL&LC*VOPצ(c)%TJ0)M5d&S!UПr*s@\JMu2]af\2ØI`w4€xƱfLf2'ǸɰuZ4vZ9.L4U?6xNZt3 I(x3ȟY50JLeQAU`U2Lua0f23 3 J/GGM44nedTTf̝-WV#v.U2lAB:Zyf1&VIjH]TLG*m44QAmg!(ȣ O9TDNU|FiJ5w̬*ʸ_K!8aerCWXKk.+5 Rϴ 3["zO]cpgXEX$@qѣVG4 4IEʹUFS*:>): fUOLJUN͗t\fxP]w" L"=8*FtWL"$f NrNsfa&1u 0TUhH0pIb&N2BɛIm"4QԗEsf פZ'Pfa&ItWL"$@id*Hw$L(QIA+`&fDQtqT^t4#`&f03L"(ĸ87Gz zj=&$@ٷ00(Ny53NLĘ'ͻ}=11f}S^8鬶\C/510(.޺Z{ +11gm_W-%1`1A}vb& ʲq㜽ԗZfVd}(1aNKbjP_hݶU_+Pb&Ǧc[O)2n_kPb&G~b(Q>)cs/9WLʦ~L+ʵ}}a1F}(1GMյo,o0=(W[4`Z2='Pb&٪:hȀ?|SbPoh sHȵ9ۇ3 з(JlyEǶ N>8ϭ>ZH[$C2wiWMr#Gr=|J$@ "7_̤B㯪nXReEe=pڢM1Og05؇rG0}(#Hd+]DUܾه'mPFHÜ~9FN85AM~ᾫv9. PN:_0h} =?r%P^WJCQnvJG ƹ6߇rC"+@`,4n!JvC>G~w] eD8*}(|X/ {؇2]۸gNΠkt\CYi0NJr嬥"* PPeyTPyzJD#@aIU^Y ]JTNR)7ov^Ѻpq`nHKGN8#J9s(Γrb. f4k)U6ڊwi (`:\o`zkc׹id(UfK;S|pj5]pTr?eL玶7tƹo4 6JmL7(N\ʟ+Ry^Ϯm7>+: YgԺ6QUTUiݎӵJVG>^yy[Q *޳(f(^V^yq>^G;q4n Ew.g*9f((3G,ThG=C)訙f(vg( b%"&JJD.2GC:3~3)ú' Zm{yJyvӁǂɽ޿O{Si}gRe&_O=geZčǞRk}}t7S<(dGwZȠ DY(S|V5!1l3BSsBAkU_g|רH"Z8JVf Vbc4{>t?C5P0~~0&RIV+uTI1ѣVbttGkD׷-O@ Wx8ڇPj%6cc=MZ5&zI|ʼvL:݌KׂnhQ݀jk;zTMM}?WTT<|wf8vG{1@(:hFD`F<:?]5þy5]O-ر_Zev]삣Ǐ:TSWEܡTO؞0e}m1C'rKgϼa29T˜dA, oIc߫:[b57 WaQR.gwe?5ԫГǷ7D4c;_<7n=`pCT Cemڿr1(DAnR-VZZ?VƄJV{ĸゆj%c$wdB|IV"3D[wd\Ka'!(Qh>CIǒCKfo2 2@4<;% 3#uꞠ+DG{nuT>)j>C9YZTVz7vHh5)LӮ7ߤktAkr /%LU+)o(W=Kꚪ^@?@>ov #o/IrC}-1ACPUU-=Q 5oGqسNh f~%g0@}7L0S}sUTjM/^5l-]BjMA[Ty1O 6_5xAT8w `}'; W1@}.U61Or%G**~k\QRQQGN4-ȣ 5 n4Vgvu7;_x߶?is+xŸ:|JCTBk&Nki<Ľ.v̀ *iUЁ ZI7s\>\ߛ7_+[^Q=bq,ǤOq$fRym}(!/lZt0F;|dijt#LzdbtMne-"zn :M:4L33i3%fT̤LSX]/ pM矻$fRK2ڇ?Kvw/^ȓZ~I7dveݥL͹V(d^M]{Lsi2ƦX8M"u\|\=؇3'3ij&Cg`6)?w ymcV<ƌy]{w c.2$>~V>uUo!6hߏ;`&3w eyn9N?kcI_c&䦒ZC;E \W>vk˜~ϮI"zOs|SEMv^rFt[tntC? }(1}m&erJVL"3/בiQz}!].7c~n_e}փǚTՌxq׼^RIJqL#'>3)BݕnD kn:B7dI&Y^:vzVi&{2^09ett Կl5kf?h&uk}e^V*TFWGoyդWd=؇3WfRIݔRSScX: 8!ԧu"J7UG=qo&u̮S}d îCԼ\zIٌWj )i uSv L''eJ0E'Lf2T.qpR%#shRd*E(fJMLSÂf`E03ɚ?hLyL[LAo& 15D6m*<]3O3)Sa8cn{Y'c=7ĺ^ ?b- QqPLbJL"$`& Lha(DZ8<)*ʪMnL"$`& L awITԅ4M2-5LeUQf3,Dk&ΫZLT̤L}̗TI3&ͤ~L2vQzIpK5>tfRc1c?\"Ǭ-/3ueUfa&3s3R+PL"8 2ffj&Za -3J73k͠ZG Ù4lͤ"2HEK53gKLo>tDӿӽ12a[ 2zf< KW/4-(h'2KR5.zIW;uJXi5&;$FnR2CI=03 ɘIMd9c43fLJ)UH&e&|44:42ͤYdpgΛv s'b&e 7f2_f܌ nשoTsU2d&pUx4|&iqC~acc:3i"Z2xCMй4BuR1,1h3=03 >6 n$e"ɌDJQJE w5QS=5)Dјh8"[o̠&["A3I)(mVԼw̤IP;؎>bp[*fRm=MdIs*(Ϡ_8/jIL\tq4oyZw$$`&#KOAL}57c{/N x.fRL:Z嗀WwѨB4*}Yo|Mc&L U?PJUՕC{-X3If3#31(S׶Af X/gr.|$`&Ru_6fRѺ׿ @=dIc o\Ԟ2fRP}Ts6hw.iy6@50G z%Zw`Wc+3)7i9}WcԒHjRL bcg-Fa&11ꓹ6X}Aٌ+Iu-s0t /kk;VcN9M2s/mܑn*UVVILCseg#IީL12xf=gL3z\foygC2[ҙS;=ϭ|o#⪶רּoD3f m$f2!ӝ\I8A23 i- Ld*^ usH3[XX'$7&)j V1kuUyd&uIfk&eFuOFO׬V$Q +Ʉ6w̯<7zmkg" -D)d*c 32}]<7T?L;{;\>g*.r#ɔ/62u+V ]e/t ;>םO,]5>3ȃ0,TcPߩ_lsò4ZwyudZ3VRs볅:\9~n`&bl&e *".USF)ΩȤ̒uӻ>(Th0K3)3e<]slRxoq'+C7&jC]3tPfr +I&8)2`tP=4YzܬYuvfLj⣟M~t~gZ3K3u k[T=[gfx{L<3̛̘ ˺qרw{'i2c\Ծ+3)kuDY3y֘"(#5z^K9xwLҘ6h$@-KT$e"I"u>D1-ͧzu]JyZDs L z!29ً<`{)3w}G[HwAxM4ymLe"ٶQ674i F"O4\a[$f2c̕ơ,4*[B&1si,f蚔=&f_L:?}UF8F.S"J=͇4{Y+hR{SO3S[z0JG5I0ΠTj[Ljm`X%(fR?tT(ʩ5ZS3iMm5pXO/i}l& V:oQͤbB/x3B12JSڪUuk?v1wb&1q1٦AVZm LBI%Ω:* cRV\Ww (SiJ Q̤Ε)t 3,Ou>aUZFE+f&g~bk8öFQ;bF12JGsL;p<2c&7h&(8=urD;QEZ͵~Ovlmb"W0xk>CWmc>닱ȆXIMB_:*::VUMQ?EdH:^3U5ϩ"+#3E8^9UTA3tNgkuIr_?<O}]/-B˶S2gW{a&1=Lb ҫƬ~R5UAz +a`kkASdtjc[Az|`ۙжLBIIIYɔI2EW-+03󸌕LeZ+ 5RМ_):6lcʮ:\ SK:?bcdujݨ}Nkm ut=}9nNs_+K6{b&3;R@MTP(ݕז|>MfR+irxdN(j RʂO$iOf B^jr:`pCJ#x"^PH?/)=蜌YU\ҷS1#=x]2vU5hgT95c5{SM*m(buvޘI0DI$`&f2$NۤXu+I0DI$LFio=D7L"$L`&f2$@7w'U1a&30ɂ ,}pmg3w51a&30l&_}/c3)vt;+YKwAǶ !$`&f#nnp;qergfRy8j,1a&30IL"dͤ=޹{0A3 I(f3yK .>XYs3o۶p}3e/Rw?^74ס= >ӾcNR)EY3ct^ssec,3i\%a磧=JLjЅ?* ]2a&Rd2tQ1U$@F'fRBng3)ڭDwH̤;v@>Pgoɰ1H^d>ìA8I:u$@gfp|3fuKSi:+FPfRiPJ {Nkmj458/SS3sV(QQ>胢 =1"ɴ!キ[B=PfRa)2K`6YS3Oa̬ FLS9hnC0f qW<9Bٮl:]}FP,fR+MŒ4 鎻" ir, u>Tګ ~׿=5j#?5i5H=liª([)lF~3u&=5vY\cî7l"4B[_[42DTI?MGGM+EN2l] Q6{j&eTMvL[p#]_plR;>U[Om;4覟L]u^:ԗMNf/cR.x-2Jx.Uq\ɥvJuAӉ[y=bb3}!Msf2RkqPI3{L(3JWSI#M~ o؃ |LfgҝotUa(ϥZ̹yxע d dwZ0a&XJ"(o *g732PEEE!%g**^q SRCF/ 3)Tgw=2z;l$*J QD0eYY?7AFGz~QUU9tGĽ;: PҨPDgYFSmH^2Lg̤6![k96ze1cP(ztf>؟~۵?ѫϼqXOz ;bm䗽f܂X"DUK\ɷN fҰ\#y3U8:%ClO8-f;zқq *++VYY"vtv rw$C(;yd&-DkXGqҍ?IJ(ޚ;nYIeGkb%`g9s"j$@-_R]#}w2l?IB޿![WVVA& ʵ63$u)YtS 3 2JIL:BQ'a-ml;j4cgeL#3iGW0t5uU=(ʕV=~ڶpJ֙8n߈bGGr7LsKDQ]ѺW㦅W҈qEHid=(c`&-Dϸ![466jCL%-ƕ&2sNs %f VG;Mwv JUY}w+hiʘI1JnȞQǶ j7QQiE街踡F7d"Wuߔ"HTGO&cPUF),JIJɇϮ5ɵ )ݢ׭owӜ eLpC VYe&oyS43zI$@ʲ}mN9r%gta!Re n4@ve2;dǨLv&B!%{Y[[toR<$"I$jú%W(fB)m83JA)CH3CJ֌W>cPW528G`ͬHQILtfC$ʷIk|T-֙Xq'3{= YX8􉤉pIVQZe}wumIO %ʗIQ h=c%}JNdXɊFmR{PvN)嫷xC)Ҩ-tc+HUUea'OP%c"2Q_-'kR=JD'en@tf;C nJnhP,7\C98PZ膖;Dj\슦ე1n d}(Q_IީW(#Q-JV՘'b5m<ͩwEߙ7LseO_#@l:nT/6[-[ћgHJtnzC:Fr9 k@؇rcr93[VVV"6ZHJE*INf(}(o|MGZgvId,>G=X5YeI]eL(jؚ}_/(WqWJTUzYBۇ3D&{W /to>aƂC2j?@ht1(ʄPPeeo;צέ?M>C$+fWVWkmCghuTu]#ya  \<<}{5+kihMUMշk+(//cUM;E[VVa}CqRVTWSW~n;P,IE?Vr}xzٞ؆, X44 8%jI oy[r<]VboJ(,Fi#D[w,MR04v[+@QDUϔ(JlE2 >sCIux̦9 n藴BAWWY̋T ;.lJ ۆTi7 >Cb|"bL|JM gd+3Q2MnY}?QDU+-OV$jֻZ5kh(&TfLlIzE~{$j(7yG)i^{b9k<DZfόv+Yܨ&R@QIS0fET-[6xmwJlP2hmPFfLJs@)ʮN+R3JFtPhARAf$>㽞QV)(3& >}9:&Յ"IPk%Pj qRJdčD=YDU] e{ z_;`(7Y-0`}6Y*iu }Vr | gXm*ϫt7ϻ??@iJs:zJTUSl [yGCĭ="e{/NDfgvXExxΰe(w[-9cT÷лb +["{.t |1Xg0gx·JG]Kfrc-@z)4wDε3o!FѠXƌ3i  %dvYH^w`T*>c+jyMO6쮙c% (+ћ܃s۽sIjM<u+30sʭVٖCT{=4Vr)o(7[TEYVqm2w[Uu:τ{_fc&U5>_X3iexWcfbg^􌤎JOg%#LN hlb >GiG=) CgDeaTwɭl~jc\ZA#ăv|m +xVr}dϰ5qH%3I-DZ&j4FoLT3[2sku\]{llzP( d^X'=eQjo6+YMuvo^oZ=,@Qʪ c2P̞2XWUXE<#mVr 45@qg66y"?[-@IPnw+T o^I!Gx?/ZD%EB E-u%4k$?Fo[.Nc$vYSehtz1rΑIWQ~Z9$ @ $hĨFEXѢ'^Zq1`֨6nR؛^ n Xl7ŒUԨQ#{ȗIB<sff|;7@"πweow;<[`P Dyke8}sJA{#9I,c%q5NGu*0UJrb8HXrTJ±0+3sI?'N 6㴼L}-g)6eKfư#27*+DR>HXo|9!~;%&{fa~i4q Zpd5ҝ-]95oC"mLOK̡55kdF{g%sf/̽eUw#>Ȫ#gM :=;zȼR)o)˗z,w{971ðET$pƩ6&W&_W̦0cΛ\XRS>>/uܶ?$bvw*߯tߴړ/?o|u?xu@YdM+<2yB{JJf,6yYgU 0HeL4)8ѣ2[*^P|_QQɢHVuN{3$3MlD^שy?64S9>cƔg,05/:cYdJ'N~hbWSR?n_o.?`shPKcwWjv73ccF~pVaKE ք{ &{|o_Nd^BLܻy۷swf̶ ܳg<:)7~ĬcGCX\kȐEb$[&T8 xcJ驟O~쇂 .`w8 Qr)ѫ(sǏa=STyAox(rxbY\t?8 Jv?rdÆό@"i75+MlS勊s-FhG{Bg|SFC"x!!2?s`HKK-=-s ?u]59A|fy3~_Fv4doNno}, g;2zT'JH64Z^} "[t wIoϻp|AQGƌtgEhͻqЎnr.Ka"gyv gǙ32+ Ъv(~p]%wUZQ@pȐO$F77'CF z`uջV4ګ_jUh(O}#GDOo]N60C F ! A,G+kv/| OU~qq܋JQ@#p<%%/U^aCn;2Ds4pGd;48hWnE";]+7W蠧.oDފ5+%_-A",O-HwaQ[{tSO @BQ;I䠓Iw5NGX XLe pHԞ{1XeIm,,C'R&庢:7gh?@cޚ?5=k<T9ن}} d2ZײXY` YΉ~ \zi `ux!gAk].M$d2dHaId? Hϱ,qbgd?mrlue_ώƒhc5 1ӓn8?lewX±g}r.@CD`Ϯ'Fnc~09!4'Z&^KL6iֲB^^"}2-<}uۢW?Gs8p^,YkYr]k]P6|V|(]l}nDq>+blKj:EIَur[2"]W>h7~"K&}.NW<p$4jE1oƓ2gfmw:w#L1jk996[瀍>!,ڵg~d]dMWNw>߻km%!F(㼮CItI*6{$@b4z1PΉ ~4]7EoVy i^-Q+Ћ&KBZ,B/u<u[.7k9yӑm)r*:z2v65Ȥi/.D"X,c%LT4ۢݺߦ2ܫˬ/ 'N&]κKZL,mߺ w 閥_5+D&IޔINN!{K&9G(z^~yGqS_dP/]471:zEdl=NNH%ݐI#U9'X&ִyzL׽Jʤ,^wE&:?^-|%hˤVhsJX e{IY$2 4Hod u5XbS,P2HB2v:)ie-z$/dLArnfL.˪4@Ly#c*M&Ku_w Lִ>EqWer:G9tE&M֝:n}-ndצu۪u;gjNGЎ&Lڱ=yyKAb`iu$2 4r2}rZt<> ݿԋ9{]/QIRtgE&F`Ym ?/hG$ɾI_;n>2F&F`<dO̓\$M4HhGLLȮi k+y' @#0АnH-{++v]&2k cFt9@#0@ dN2Y^l@&'I;l@;@~dA/44bd@&IdE& d@&IQd@&IdhQ,k±.談KKtYpg^X?dˤ?ez,~<gXw!}r͗nV] g:OO$d]7G;LqIytNJ5ˑI:\x$hǞ8=e.og8vmznoC&LcMqwɚB7^-/|%1ZU;K7v>r^ۚG;LqIْ$n"<ple62N}mhA%R s׷ƣ(Lfǩ@C֫uhG;L$2Lq%"Bt^|pX2e4 l$k5$²LPL2.i|2Sc]hF_[4z(ƶB&O&Eۜ$=WU@&elu"w=&ٺec ݗMw`QvǓ\sLfE|nc3hLWF6eY3e/οb75- y~#kז^|כ&ktW¯^➔tWnV,CʒI菍`^`D"B'=KUQZ/j9QNǸΕzc/hU0CzalI ݚi.ȚbU_9Gw#I%wUξF]V%NGސKuQS??hW<:}~F-tnЋ]d2qeR82)?lY4,sNӢ@O.t7]w}8t-t n,mgZ}gHۢve~ElEXbߩ˨DL" L,޶|8ӏd}K<^I?9wK~B'{|ӑ2E h~ y-!Wƛџ>/D޳#(ˑq'Ͽm?!pN{bʳ3EÍ1drI,"%1$7[wWeQ/ҭ~u1ff _=EȖ.r{":G+;]hIvKm%[d21e]N*b;d>{"*e9aʛ;Gש[n_6;G?fWF5]Kele֏^$A$Lλ"wg#%+)IL=|Ȥ`J<,O%+)42-(IF$'0FB++˔R7Iɐ4$xj+ } f_f/LVZYDf&.j]t{}%UQWIVGZUy2Swe =BsR3xxǿ]\PI\<]߯);}TK&+c2LDˤdLT.kB#]gRT'C%ȴLee|JXMHv^(8ta[ }P&j@ ndkŨ22AU&G'zI&3Tǝ6GӍ?Ru kS"?LFV7!$A$LJP~^H^~޽rn4:n=5LFhJٲ#]I+1J"@#?( (K&A/pŐޔh.82LG~L6Ƹp")53LVL: =n'ýz>fzC&đ5MD&{sOuoz`sx}j5mߺνʋ*Luw/@Iy=qR)E+'K&h#HYݕIGc>*BL'r݉<0ʹ "4eNa*#S;+ʅ]2}?dC7d AZ.22ĉ/Ms IS?7^ɦۤLMr$Xe2O_؊L"=d߽ GVyfR@d7jmiѭdޒ935/ҭURUi/2 4'#U҂*}^ F_&Xe2`Y˪s:B'uL Z=dҼZVgO20ۜAԋ,kF&F(*IDATLAiZNj<كSuE&*'X& u7LV:η1 GEN&d҈~]N.E&Ɂ "w>MJ:ɓþCdr?gtC5F +Qv\)ړy2n<h*p-z1lt˝Ȥ7t{Z*|WesVsJOd2]2m X&MQ2utaeiTK쵋o|_wnjt3м/^,kd[|n量+Se%}Ž[.R~էKe{_~:{3Oʐx $bЉ<IyORzJҮȪ,[L"$gעƋtQP2)#Jvфw(er*(2&(> JQB0#=2HtqA{̈ro1LJdUQdS`e\+˗L)G2H#RDMyĈk)uwls\_'3L"@#px46{<hGa ɤH?D"edtTXIwW,:{'iiopZAQd@&L84^[q;̂Ԕ҆}7urc~AsgמwAϻ༛+9fրFv$=Mq7WzaÇڌ3^ŒpdpA~kEƮ6^-@; $L8Ӕ qU}A~-K ] oRhG$OnLMKm <=A e{p>$ɾ7_u^{;5gYG3y)W=S 4~3|X^l΅2⾐%nOsrs^INM 2^y+m;{Ў=8d/_s 44!4 Ў2I 9{9j(@#L"$AGLNNnڵec?ϯwo|ɽﺓfaÆ- W+ihIdv$~,arrrU;@ KJ.ܐJ EZE^KvXq;˩hIdv$#6YKr'MxA5WۻXwxυʅuFe5&4$2}##w,TЏv-q꿮', ^=?^N ,Qd L)~Sr5fԁ!'94%?Ӌ|\zwŚ >Ӈu?BmݫY^jZj[X ;~טc$2ILքUFn~v#}JK8QDzW~ۉPD;LdB~, ǢpT2zxڡÇ2Fd9zӦsGgg=?-DdodGɞcq{ql'b;ю"2 4$2QNyWDk9'^78_[t9tsxRp"'e-:tN!I,d^u,]osYǝVku[o]fߓߦWRݎ&c\l-γQ0}wq8mc_hSE}rֳJZt%KE& dh+ҥEK0TB*F@ZU*\k\بk-ɑRU2֪bUwhyr?f^rZ:#+hGoFSӶUYMIeDvP-פ2љRsagnzݶY*U>Y˲dէBEA߫TܯI_%(2I $ 'X&-1G5mN[ n5Nn *M6#KgcˆB%ɨEJD&WXg{|X[_Z߲LrQum`~a<,ilP^e;uU&[|*پ}f(2I @#L&L X߮ӷd/A Q,΅վrf{FAE[zA& r$5O]ʘlb]4,L.iqڣdo=~/'v$Id4|푾Kw"g<ˬsaU$ڦRkL#ɤAVYe/Z-*Iqd2Mzkz"59B;L$ W&%ג1dt\؅sawe2ɒGy SOdR%]|%[j.RGr~^2i3VY]%]IY獴$L 2L\$$-Vk[n-yi c`!YY:Nny:fJ wA&BoON[,,Z$ƐɽQm,:QfhY}N B7u Q#>@&Id2LGPD #s}"籐%oro\%R&SXlbXWd2ȲkTnm:QI Z7_٣Ӕ4kUƍ`L_~[a:56^庭|?UeUd2ufj}ܷ![M?rh $2NGHEeqӑ5/t<;<~F {Ι-r{ 3R,ܽ*ByN01}h7Zfe99ye9G -[>_~NGvy|.ӱ[lk0oHο[Jӎ]s˯8s;qB-dD&hGC$5r+DX`s9bG#H 2LЎ& ˾$DXc _pDZ)AdH7w~"IZZ`Hl@; ܣ|7oHt'}]:Gߟ 4"4hG"Iz9GWII duII'5eΓDl?\QZj/a@#2Yz{߸hG!E9"sy90Qu0554P(}1~/ @; ]`3 $#P h;𒻱Cq襤$˳drh Np۲^9$+cϮ'nm@  (հ vͲZv ^ ;V|FNrky> (±zȐ'k\/? tv<{CQ$9 ,$ Ў ±䓇64_j6{a(WY]oOKMl䈌?edWLbwt)%%dzZGdonx uYOm{۷s;C))ɟgf ;;aC|:/ 4@;¤/=zԈ]ҍrK?~xl!~w2?2$I_S'aҮEhvt #GՌԔ M}]JunëOɌianmi>픿 MOIX8c/rƎY0npfm ~R"Y_ϟ:N1Ϛ~z'Ru3̣} r/(#\޲ &%|C9FΝ}֢k]@gQ85/3 8i;fS'5O~wlvVd&k/3]0<ŗ߼*w7s'gF@%{ț<.Cbok,V{˗H6Q6n3`87,nx\YYޞ=k뒋_>rƴd+> _t_ .(wsn9M/q=`IIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment6LB.png000066400000000000000000001265161462611535200300210ustar00rootroot00000000000000PNG  IHDR")IDATx |坿'=7EE "(,**(TTPh\7-VPXJKZXY/R[elmv̛L9$l'ddee~:r߬cѻ?Ƀv]UmboV{;O^`7WO?mqGd[p"=@꠬R'8Ӊ^=Nw"edPFFƫ;߽鱕T"c5Q}{¢_;7"_R^'81ΉVMB X'nqu'*XTB ''kV.~o"Zi|ݫg|.p;ago]fVރ5ރtzJmK;tH, qB_N%P~ybYfbeҒ+A*}CϾもp4IVG z (WyN+U[k^ ;7/mo_uuɨ?wOYIdPT6rr#޻ēP{t4"k3G(PBeddGA=3n{wx=DVH.Gs#Y;߯b$2 PH 2tybJ?dR"Ͻ %łH.?RDhPVgSL"Ψ[CMuy]&o^^NWCX0O 6s@zPaImC4W!_;@n~{%Id=Wֲ&ؗPN'P\-i"bק1+Y߭_睌$Z*/=-هP"$#Z&ܟ{D&!8h5 OPe(yWP{S$Ѡh߮&~(Ǵ6v!4D2򚕝uND&IHljeMM|<++H$RyOZǞvbSv޻7##5V yѽ7}!D4(FU3җW.2C$C =kJ2$2 vrYYr+*‘Lg?ف={ػwwa\޽{WoM_ƥ[n7lذ'ƑL!wnԤ*ѐჷEEvҖNcH ,l|M.C2o?{|Qyv{~?~-w=o!rP6-LF?rss?wҥڑUi%f ~췣pA{͚5_:t"H}ݷ׎o~srrnJ>ѠMҗǝM$HwBOʕeɤgviRēe];K.yS'^"ֽ[F {]M?@&!d5:m?t{*޹s]'xb#l޼VNԬ0طg+29 Lq՚}vgf݀]QQ )k}ٍNФHsCdRPU9 5 eRcXs[Ȥ2/V1$"r/F_kjT+9ّϟx04C(gL$4 u(#Yu(03yMl+#Fd$_q _ꋆSO=e۶mKjӮ+999Z2+P"2 $њeu(W&/>#C_<7|xO(g'(TdbaaG5 UZ&=WhˡC)##C5Gݵ X2zMeTV?b5M$*_v#OMQsOګn_hϜv}Iϱ#L$P&Lō3{߭}mKu"Iب+&"<5 ;v'Y{)W۷f=S8Bcdٵn/ZF]+ѣ׾7вx&9ʕN9^w׋ 05dIhzdIw 2̴G2&jֱ FCl"쌵j D_:Q_㽞8"kNC$:a2r2gOkOp+.^y~[vmWh]N#O[5e<X4dnIoxƉNT[uU{j:fDZXXܹs?}=zHVVVYYY_#I( ;/<}hwWKSd$Ek/'\6vW쑓\}}߬WfKM(VmYRaeuq#_b]^ SazI>im}MߴO;f\hMY͹z^}m9|e+ovōm۸ezN*[isp3j߿;$h? ?L>e*+㈦ɪ>w'ф(6jԨ'w^[oM{\zݿ/ LOTע}9Ѿc|wmkݷwwwb@&44 =k?3nT[KN? WIٿ<)L'ZM6^U+䃮S>\ HG>lxP{?[qJ%B ^ճzr=`qDJUvw͕G7Wk>g͙II@&"e%xŎs9#;x }Z}z 0~^L34]'9, ɤĒALX"Si{lb޽Ek٬i(I45v9v:42Yq\C^GP&D#C2szmzmtb^vsUVP2'3۔5|GsϟJ|}︡V&EyIҼfd~[+}$ K՜ftUVhY͒[&4hwyAAuk*49O*~dLPV?먺J&5k?ю%tIo2=V5ͳaT{mSO!VfmLJ% {MYH&3|-LjߧdVFj?C뮳wo>;''+9fE&*?l0u*ƞ8/ܮ'xOfC}?4>~NPݺ-dJE_w=uL#2L&^6q]Tϳ^a/ 9 бA4F&UտM"5Y߳k"j\ece281B]kI@&̏J&l%$JeRQ^x]1VXg;V]o{ooǿlЌV,3k%2L6#.E =z{P%y{`5ūw"q ITSͶj O<1gZoIȤHU(2 dЦM?1o޼$LL| 2n2[̀*axQI+2ycqn&4LjBbXɤXF+lV&̈́8qwU^|JZ4Dg̫aL#QTQBהhJ+[&QSPx$ IBAAhbb]VM7d@&[Ls_g-8!x唹?Md&R%^w3!Ny5Z]&Dj9u5kL3$ G:td޽i!wq#4꽗_~F&2I 'ÕXxpk,5ķm<2<ѹgU3\5۫f}]$2YϳF[F^qd5ӄ8dx19@&l.138㋃zܺu]VVf/\۷,fsmxu5nc9UULL. dHRj6uݎL&WW#٫,9ӧSV-zx.컼l?VR*2L2|D9昷 ={zf)򞬬Ok5Ӫƻ ,}69ٵIsN^{NyL?TuzP,;ˉ-2JN]F6|}V@&RۦyVO"nS1;~C55uxcyzuxn~=axq՚# Iݣ{C3V݄8X5Vzh"Ɉ$-+5DDwΛ7ž9sm۶V-W'Mksm$ۼEA^qװ f yf+FZdl2L, AԖz=ltNDK45eR%]qTw^M|X53-F 2dTȫĞL۷4nRK\s5vQQ}}ZKKKX]{սCZ_rr5+E^SbO-hV/cSYG:dٷ4plTp/+w7V]kz]ֹ]Ҷ=hQ滯GN8s]ڡĮվB&ӛ<^nHM- dëRdHeTxK¶nY"gűmn=H:m_drJU&mTtC|0Hs1D"yY˽dp{=JV*U&%#OTud_1kdRKgL-U/pMYGHJJ%TɦRZL "Չ,{U3iGKۦꑃ^$V&o~b}ي qSjf-WvI`=箋CgRw2L?býtwС_oߞv2О>}!u-?rDfd+yJ`|cǒQ&%* On@=;Z+AURRiy;//4jVYekɜOa> [K tBYw5w 5]`Idh6x7]SkQQQݻN&5.tȐ!|U{Ě^#1=9 H̤JTk&%F$IePVsg?uum2Y&R;&(3L, $@9U3mJk)ݵtI}={ڿկ={r ftIe GsJ3'uK*2F& t{55&-LBrK|Ǫd@&{VlϹz}ԉ#8vܱCiڷ8_ 3N2)ݵO>/Bk|MnuGd.LD&M/_ʤfp &TQ?kbO3eR,ۋIHD& dZLn;SƎKڵݶ]_+B*md)uԨQvK+l}D&SC&]T͛۾ܱLǿFm{iCn &Xe6-$fI,$2I 2cQ$;YQQ/KAkd4Rȑ#CK7xx L `hITUM>$[fMc UNY?rۛly5~uJ][!@& dZ&z;a_v*봿MsHhm'TZұc͛7H]+/({fv7{uA 4L2e,))׭[g;=Gݻ_233p>S$I@&[>n|J؋NrlecnI2XJl'yHhu\fsq sk>SCSگd =*v\sQdV%]1g#\}=y8!P<ާp{~L6^ҾC?mR§8+ .=M&m0h鷞tIo(w8QGWZeo^z*++;Z_uۜk:uﳲ>v>ǢTc 2 *ʐIc %BL6w7W`3uL ]n1++PrIvv뮻Z4;vd2I#$LBQGauUW[ P,cP%TJZXjI&sPH5w9\3ڗF;IWk[4F&9V c oqqK.^}lҤI}s<\,))JiITI}L AӘ΋Po`uMT&M7U߭6OM#l $2 Jy.]nܹ:Oy<+bg޽wddd|ȉ{;sNSNu}R:^dW_g[G-wh 2 C&S2.D/ Z6 I3Լ/lIf@& P׆N,ub/{ۋr*qmd>ʂo#B'LΜv;65Z\:c}7o_GIh2i& N,#A줚`&L4n1X^6v6R VޝWu}UhgQ70 nLL:~qF:ʠj\%2  m۶_MD"5>ꫯ>±|U&Aӵy1'ݷZdd6+,$Ee%09TFN5y)H%T/Sℍk鸚5Uc-ޯڗ0?ѿdؚeO$*gH1C:ϰaIH5fedd;p n۶^`@~ڱc'EKV@_۷gdRL"LCc'5RHfYUJ0㝇dW?9BmL@*Q\'^q3GԩDg,QJ=TmsLSYGU2G _x2*[ڡ}]X8bzc-UrU>W;ϑ' rܲO{Svr*w+l:/}nL2I$43OypIR[&X6}m}7VwJٍrOw_2 gr*IL[V4%}]8~nY{p I:w~- TNbjhۀcr±nydI@&(2)Ѫѵ6LP/o'Wv|eRH$hs>x7[ɩQrK0%rʐ}ϣLV5ۍLJp;2 $L"i%:u4윶K)T7~Sy _̤2A&M JcPX>.﫿]ۃ#t*3 C5e*2$ U$C&u&T뫺*+-%K&|1TQYJۼ7(;x2LJ=SI?0筮~ Sd -Ih#9iL dB #jJ Uټx2iOb' ;O4L&IO&5Ɏ(D"L2 L6LJ"RK5NQZUI˛h'8LP4iD<՜Wp짢ܙi L2 L:frL^sbʤ53>фPjȤxHuϕU?$ L Ie e U;)o*ӧm$t9#~ˤyoI$qݼ,周L*O9&֪e=O~e(UFc4:u5RdI@&CƄj*$d(Zz͔>,9byˑRe5]PMWYmg1i v5ihXSF6L2I$2.&z_P7$گ_BjUN%S#$A ȤM$ 2L 2 ^ąH5BId@&!"G;,E&ZE#FDCCcSɴf-O̫D`d|wBOdrD444[nQaҖn"& 2 Rt,mW\? ־l[fwPڲ|c^A`dwh!z'"A Y\\wP2('7!%47(^"dקdzA"ӦYAddYw$2 N?"HDq7‚OO9wPZ3Cג_#2đ{_ZjwɾΩ$@ SRw%"xu:iO$##X9eBCɘ܄CL48DH$N3C.PDHIJEu6:$@ݵK꾈,fq-({DmPWChP_=y&[hm \_ LDc2;|sO6BIHu=tJdq ϚDs r@EҮޡ+ܱG bؐCL4}z糧+}gyI@PޓywSCzZS}ceJd0QP&ݛsssԽ/w$DڮS~KhwtnɅlu(ICم}zed*H^=o\5sk@'32t,oo1ʞs̛!G1\7?1]7r̅L֑qf_hiST'WX_l1c}.p;@cRWdRY!?5YLk\[!˞pW,[bʕ[T#Mx5c#Y?v=qߜ,%@`B8tYq:7levr4yN,qbvYo=Խuc&g~_v%{#Jo}wh X2nc/:JE] ڴ:qv3c5@31҉222>sc_~~n%Zݪ233?qCC3PPƟdF%eq]Zpʳgss$C`ܹy=h1Yn4:]{wUӽ֡l@\|w" &irC٠u(fdd|\RnUu( bꟜGUy֡rٿ/ e&LJ*%LTPzss4~|<&@ 3+Y2~w(JمizcsO$@J: GX|b6kXC9PUkui\wكE\܆P6"4Yʱ< N=K;!nAԅu(9q G[o5[=v9w].ғx,Z{;9RL]<moG+k[uC,7(2nFW7/օZqgC;fv~<;Qozb'XvwR7s'qkXnլ233?CD̿XgtYt9ww9ʼnN6H ew5.'9NO(-T+xBxΉ2. $D'zBsI ʭ̤Rj2qjO(5rAJKgtu5^6c9aPsb2ԉg,C@̵&. $B'[u,u"exHWZu^_q0։=VD/[|nՁcڞ0Vx1ȉM9Fmp6[uH:Pbx`[03qO<-uH=v $$U2:2a'yfrq x$qe`Vnc-1򚲘#3<wժ jBD!J la399DdU5v|k`nOpdDM㟜g@he5ªj['Qڝ>a'oMڣ%ޱͶ*ff'z'pI 49&.K< rù$}fMγbrH O&%[L 2ڪG_qI JhMγe%9Ud,d$"YRf׏,O@ xNtb7Aĉ׭اXdOV͘Id$$O YDFւFPĬ&ﭼ=N%h^nAw;6h&; "!:%f 3;'R=h1O]<޾j^e՛␸sb{wev?w*7?;=͇">?a{ਣ >pCn].c+ =V800(YڵYo[aS','/G*r4-Zl_Զ`Hv,^w=`D,\n=4/ts{_ZC4iahՅmitD* ӧfh|MW9.n_܂+{SE~!+b܌_jl8nBGj39 +nvq݂Bli#O7' Z*-g[ERid~' J M̼W*Dhr/E f1vAqAP&-,(ۿG /#'M8:8"@hͭqg_r* Lق}B9[3]#`m'qw$@bb0>qb̅2fZ1K#}fnIc𚄀qB32B3=5DR|X ˷$kG:\qvv%5Z;}r?O$K.*)xۢ+@LsNAp٘Z}iɍhL/3^4a|d;>~Qv~Q,%$-b\Yvv%{GG-.{vmw=}Z~ي iDG<4N=mTG޷XU✻.O$U=t><5 w$%yy9>OX6Z{ۛʗm㷈Vlw^d|߲#PJh)vO̥AaZܳvWYNnn2P&3Hz_xkP$ rߟzUGĊ0'1 #۝I9 U"D2IJkeR _;̯|۶u)lPu!n8,P6r<bMW=ɪD(pEy4 I1LJ8bd'|db܌_S r?t=/I\)n_T{MSKNukBhM#ZnE':dsZ̬Le㺹)OdpCLi|˩^U{UIBA4v\mxc%UFÄP"debe 25WJW?(*5t\I>=lޯJtx{hdRFse*d5X5,(Fp.JdR5vь҄f> /5VCoU㴨3dKʤ.,æz<7?1-'q 3x@g@u Mvϫ^3L)G/:__BϘ~ fa2_/ɰZC $L22iIqL&MƓrrf&*ѠƕRgvj)M&ը2 ˰kA ;WԠ1Y06LI&u=s#YD&[P&M-,ChT~4xT˙.tO5Rۂ]MML=S1(@nA ^CdOJaeu(sYId2ed4l|8 \2-PJRFi(e2ׂ2vof$4V!52Ϙ 2u;_?l)1w_Z*(1Bo+L}`d2l]0bk0_FT:oHvn@& dI3a~#~e2uaX t>Ȥ+՝zh%9VWm;$Ai{:ɤk9dw8 2 dˤi4}ǝ 75̙ơ_Ԙ2K-ѤP˼I3NɌ4gPT L7\E&%:LLQ%4>ݛ*'2'f fU}>4;qQ{WCGjUH Fc<f&Wez#jl'ʤqf Gf%Ivy,nE&u;Qo"M&aU0:Z{P?FRt{t?wds2Y@[Ru{/t`F}?U<4Ce yCCa5&6]dRmvvMp@& d#fh{$T4̚aL5SyUCfs~5h3RJ]Ђ3!j:OB Oe"L#Pbjާh~_7i_Fm5ʤ>mKF&7-k#j~^Ԥ:%{5t*I>9/Va2z꒰e_7lpW5zhsu-%zG?뛰5ʤ F*$I@&[Qh QLɹ@w [XD\.pe{L`h°zf԰Djʤj!L$ ,t^]̚gf5=/\aK kd"$2:(^3k2@&$I@&[Y&i N]UյLkɏL[tmd%IdKcu9l6X@& d$ e5H4HdFP FuGsXKdD&[oh|R{q2 L$ L2I $L2I$ 2L2I Lx I@& d@&IF& dcߞv5ɾ~9Ǐߕ{Tvg+ztG4Ⱦc|z^y~o=լCd$I@& d2Fl}v]ֹdڏÿ㖭}ژ:OkOr+ַww{ۛj[:{v>x8$ #n;?)rsLBʤd?]*:d2?薖Ƀf U渿_p kUZsˤBr'q%ӜXDTIM^1 u[NAjx CUdD>)7}4ڐId2dx 2uXe;ҚZ6Pv%R LօnzȤu^v%:/Q4bxŬQ˨Kʘ.M!ʆ߆ʤ>g{'2iri91[/-؞X&LJ&\6Y12jœɠ &Z+Ǻm$2<2{KuP꠆+Iu-sI@&]t$;//?auյeFl͛v7UD"YF Iʔ94e]U&$$Hό d,vɨyCN8֕Ca[8izsy3k3MMP&F&(߇hbt2LOʛS& dO&j.$I@&kCc%:e4y͙pn=[b*'a|j*P ޫ2j5LI635V&%:q#9%$Z!4,512mG_?[ 3M "yBIWl"2)dR']L?~z{͓̘;7//,7lL532?oEqS:ouU&Cyۂsݰa=asNp3궇L2?toF+s/,osT5oNƲj#ޘI.ڷ>[Xϥ0.^d eR&t#͖\nNR;2%e']NovV\Tw`&9eR2%2ɕcӅ7ln_n{;+! Fle]|<I Fő.r)j|̀ uAv3gK~eLᣟ 5~㍙lNy>tCz%A2ٮ-2LX:%X FVq\7wN{R2u\~sɤ$XHdӎs[u$Q"5x ϥ3['502 2pJI?L&OLJԭ3_!ʩ^X2)mI$:_&:*r5hgjH/t0Auy$+&8z/ӝ\U^ 85^6jGJ&a2 Fs3aK`&a}Rۛ^fff+oL"Pޒ\J>kȽߜ\ۥW%$aKuq'I'I$I-heL3 c# \npiKȤYR3_WVt퍶?}x21[x6WuOlj 0fnzMW>eRԐnT&)6X}.7dR߈#d2dCfM5TVAΪc}Թ$IuG5I RXATV762Ljl`LȤ2~*(˩1x2iMDc"A:۟/ v|LJX x \&;6[HɆ>YOD˘C.;L-ms==n;|^I+^Tʆɤ>S/vVM˨l\dr™\ [ELfD2i81ʼnm$2lqb|dʤKM[*ˤ:^"YN]NeId<Ϫ.3IdRT&uS*lESVl72XM9e%%P.*q*I&"ڟޱl~T<5e?kA4YT _L3ȣ&47qʤ3r^ZĪdʤ΅md$kXD&A!7mWW{dV$~I$R5$ffRIv*C|~vf@ TFYK30LfGO%7(f #:SKHIFig7zU_]d,HUt5SǐꚨX&BRɤAB:tF3g$TY,Lļz=lXWп̈$&t,fTT߃2ϬIr/o^SEDH{9nƨuIdpC$?c) WY:"lh~uP^ ;:':(X :Ŏ f>Wڙ)ՖA&!dR,$@2(U%IvJ^ɕ_B{%Wʆc6>`R_gLUFrgS2' ԸQ}Nɵʩ1:o]9>mz^йL24՘ӬjD驼7D4EL 5*oi2Xe"B]ʂO ڟf ?3,4b{iq"Yұ$2d=tR]u/LEXvNb&UheU'װT}mE+٠Q׬MiʛT~3J0G&H&$LDR0Ȱ箞ΘId I@& d@&^*ҹgiPj;Id I@& d@&C~KguwC&I@&$dI7c^ Vjri[QwL)2L2 $A LMQ1hr-%C3ږt.7ɒ& {rs &2L6:H!$L2IM&fykRjVL/3+.($dID&$ɄqZ&ŪW[$dI@& d2F =$A L$2I 5 ۺ\zz؉;2I$ 2 LLh˜9w]̵ha&Dg(G& d@&RX&^t{Lfk /9$I1܉Jo8QmL{:cy{;d@&R13E WgGL&E_wed&ӥ"I@& dd@&/';}I d@&ZL޹y;C簳b2y(}?M{ecܲfo~bms7Or#lj}CsZ&7ln8ISȘIg~y..u,wm1TWZ}hj1-kmAqmnӵ{b]I1xW#o$U2htgAqWR5#VmYR[ƞ9:(VsQX>}Ue+u,S^+Cy 6EdI@&lPCN ~enI Ek/qGO;1k*FI >v(wx}spj0干ֽ4}F}w_U!Acq8gҹ|cy6} M~u}tԀu5QnA־59i+:?/EeFg2uߦ.,qRr>)JzVP>M4$ƽN2CkshuY^[]{T7.E&!ًʗIH[TK.ԥwCʪA}ᜧj )DQ '52ۨ2|FѿM XEpߺ&hJ5(GjfIeBrS ɰP#,&RvпMU9O/~}I+FÎ* {@柡Z,7X/o&R"o" ol$2 Ju!FDbۛn];}m(j$zNJ|$=Z#peRY5”ѓ5lϨU]L5J=',qHy[٘0Vc9 03!2FIV4av643)YUXuޏ_=9ѺC>* W:hzUe7tbmO}ϮۇR&D;r=7egh5&ԀSRU#I63Z@]G&s"EX &$iA<4\IL7]3W&PJS":H4bcŻĆ߇T< gLLB2-]јc|9Lk V@ hO%M!a]$d2g0FH4G&IM-[X&dު Ȥ[wu5!2h42Φɰ:H3-qf Ld1ԑILѽ $G =F2nP:ڨUP.cp$_Rv2}GVF;D>z˚.L'@&cxcoj&4IuԽ֥Td)m=؛Œ>\ԃ013c2ܐ{_za '2Ͼ_ /YڡS}ȤVjP#{Ѧ'8g@TcM2]X@u{w{2ks:'54p>[j* V6~~u,?R3N*ƭ?Ⱥ1vaKBZ*;)h}2%6?89=RfN$5ЃfÕI f4w-/X7+Zĺd>P~Nu~U/$RZtaZ] HDCb^nhM2i{E 24928qc^M1fR 5*ՠ3Q-&QI O5R *I~0TǍnp:_s$57͘6QO+I߉=9tjuɤzĺ?2}=Љ6+>Y )L~}нlh%{Ꝡ2f9!]x>ՕUC9g>C!SJ'2 iIVVo|*D}L H(F 5-؍m7]iQmlEjm\qK+J-X .bm5X6j`=co2I&3<'9yy5+$SoCP<DɁ:YD!IK dڎv%$29R"1D$wԨow%"\j}Ҟ0~{9SedB~) +l"T Npy{Զa d!5oS E"d(Pctg0h2)ݷ'(YH )UU(” Pꐪy "tӤG˸eğQMsXIֽJNoE"C$$trĈ?}蔇wkczzTm]eD~pK YHåT,FC H!m9n AIA iP$ˬ_FQt;e}7!aIHV9M:w7m+?XP_OҳW_/+'m6V"YC:yLA*%QB9nxAI]{fvIރuJF }(IIHdȇ '.G0JKN$w1B2G8D\04侌IFQW K$DG;HP2 fpLOD"p̆H&iЭqS džKO$JHyjd @et“ 99l<IA:Tht}ptf[Ds9(2 =@Hސ(sy`dCj{ cȑU\PG̓Ӟ2+7.Ç tP1J\a\&"ǘ'1ߤv1!\d J#c_tĠ箿Hf}aԈf AIK d6̈#~Uv\P*?jE2 r+Ky -ǫ푣F;D,#Ei AI!*/7r]ynh{6G-dR:PRLT1tE3 ˤ m&wypKeGˏA j5{D^oV"j4B9hjd"W}eǀdjdRDh_! 1q5I g%OH?j!jnٟ9-n "ڇl<;oGTLj,:n+}baFf/;N?E겪E1ĹnوL'R9g {/vN'cID[}Lisjay%Q0ozTI^2 HsK99cw?c~T J:C_qS]D&SDM~%oHΝi1g!0 "*6'S8)o|Yꈒ?h~#7D"+-s?2&UdI;qЉEK&22G<Œ!ʢ$#AdR;վKd>\Vsl`!5&Ah%}wr͌^6bĈeu{=!G!(T@_nW!RuG\)sCB-%v&LXzLš-zmUݟz䨆A&Td}1T!m Җ3i vY=cPPJ?[J3>tD5"87\UVR~B&zS7mΔ6Ɵ$;}uܓbD|lQB٪b/;cDrkHNP%-n4GU @@( i$[pE38)7 z Z=U]EDxr[2$Ǡ<&zU)_/0$(C[K% IPN=aT7#:D  hZf˿7!'v* yʹѾ4$2#s`jg@\ȑ#xd:_vƛk]6«s* uap mW2 =Vr[ʴQxT#*m[^zFOTsbĈ?mƌ)r^ߐ!d,i#ٗ{[=?۷ J _"m.ղP7xFh{ʬ\[ĚC9ڗs> kN lU?@T8}?'˭*?h,?hB9!PRŵ?hɹ"?hɹ#HfۗvER;b9%@ sCI erv{lz:Ihtm%& 32G,run.~;ƶϏSAK9gVbW,;eK\S70 B v| ?53zeNQ CDϘ Kq(KQ#Pf3vCơ${>>]+HRyZ&)ǡlE+^-&\u)r?!@qǡ6gJU^՛>wd䨑R5,ɮ|'ZP*Hq(ڤ21nC)BI/#)@96uaq(YQ#F%EK2{PJsS)C3 q(RcZyqU*$8LdĢ,C7{R׷Uăq(yy4VO/6P&cv PJQ:«Oq(㲺|:G*8T^o2%m ?o|}G)I1JS,b!ÇTr[q('/yA'ju;$ 8e~3~<G3({m`~(r Y&ƈ#uMJvnpByH>ƻKLq(%:g W$gMuPAc+~5.L_Dc3NlHHv/O#G8=#1G%FH#F|Hۖj!nG0#seȑ#7s`XRed'E(-:H&j@bě %"eR Tc^B:8D:%sHw٥K#clRrwXV8$ÆiQ]HSR"YI#[}g-)7D:a0f!J$hz$$ _DJzM,*廬~Ir8BAs8 Qz2`"/СCE!@q8 ёj:ֈP 9A;B!P &"8{VYj)a੷Cr8`8R`dt XSǺbOHFrȖsHVHn%:ҖY|)mT7p8 a@5JI\o:9BYGd Ws lPDy(Cd%u1uYX]#V0,)RRԈPFM*\TDzv=6[=!4K@F=yH t(! J!ޛ9Uht$;)BM¤L%zϢ*0q(5+q*e7)ǣ͢ 0@HWFC(+SXl6Z( Q`8%g*)eV`5_ڡw _?@ߩ4RSq%NlzzbgY_;8VyC Y") u*mgbLx.z)Pz")Io9Q{Jm[_뱕2@,!+0bʐ6<`.W!Kuuq+0&CiS߂~,:%o%ADQ# #-qIPgm8!w d:Tei6'VZ*lIW\iYW 0Hu=V W֏eZ=UԲZ@vR_b~d]ju|tZheedskC2zd"VVJz+PU:yEk/rM^X^[:qЃV`XdR.!@Z%g5Q#٩{b]J$eCY̦rOjڃC 9R[Z] ByQUiD%SyDM#L6(T{cgY[@A|eA!;B*YŚQcwU$eJtDJZaHn%_@bRbzK!Dp[r ot7!ϐȜ`u|T=-= @n艵l) dEsRx3n&$ >'@o:g KHL(=V?1QaH6+ٓ|&m,X*JS? C \3Ikm^:C TBkV c)="̟g/C ܔ;!sz]a^9?YI$GmAPW] d- 1َL$uO3wYyׄIUe`cdbׂL;3I)tGb|=[ % i_UbgAՉJC8ŏB#jF8iVOq!z+PN (@/tJVrˢs%_ _sǏ_3g+)EgLu)Ϝ8gOi9mySr2>g ǽ3)w|ٙ?P3q¸d>98~{$N,K'SN4{ִ/m*r" `p[=+M[8>_ggIЭ;1hnثW}־qUo^w~M`7?`h{^3G+˾ue;.^z}Oфc#! -9x)sj2.QW̦KBΙ#^mY5o'Ιٵ|yj?|h+q_x>ma"?k~ލ|~RW_8kAֹ3 {$V1kF}fN})33C.kg~`ioNii5~jỶKWs͛2g=_zVmq/ Q1{ִN0M}IJQJ;Cdp`3v\c/:Is<3g^h16&$ NWzzQɖI5U2C=T!::p[3+(+H 9FADG".>mңߝ2yGkyA7?o?n/3bYVz?Z=ê@!b&N̹gwX $6|C]޶q]|ʜ|/b(r4H*wפ/T|"-2@0)wQE%N AQ_vvM쬷)yMd N|~?}_/\|R?* ($-=} >nR Jh~Awؑɓ. AQƒc/;ǐ)XHgjYsPP6پW/<,DDCOKez,Kϯ}͛PvoWJݰyo(G!io"IDlJ AFdꞽnS4럻vS&zQedw#qW<$K B )qdjw 䱸wFaĈ\!o^Y3H:{D]Y)O1ov~"mOƏdgpiP$m܉D➽qVhx][!"^qƵvK B p4@9x@q>ĥ@!T j{7r?QBxƟ~s@&Ih{ϑ "nɇO(Idr4y;B߫|G('r8_ ~q?BqlrպԓIe\( q E49\F9|5?1`Nh~Ӣ8~_JuGFg)܉>L6l'bgj} _˜2o~%Ʊ9ٌLLmqKq>xHa\q>E<5_e,Yz]hF-Q(_~o!CD毭vǚť@!8جrb $MNXFJr< |N2r2)':Y2u1܈u]y̐7+k|/'6RN&K~l? DD-pǚD&(Z+mxu2LZ.KPAV 1WBj(s#Cos]7qe]0 L*RX39A"8V!ho8 VsXgnL_Da+/k-?k1?Ds/FCDGꉿhG6?HΜI I3~'dZ~c]=- IDAT^z TK5Ntl/NTZ=Y VJ`p^gYƺ!xlSQb,cgeQ˱ls,uFKmCedX7i:9H2YLV請E1?B~uy{k^j=rm22 `z.dEnr?+:[jd)y+P5anb0f^ZZ=UDsDwzj99+mT˩1Q%q;@&UK{ ڥԱ%a2l)>%-ԧEKmvF" 29t2i,V"d:cJ&sAm&w+y,6~iSGGI}~Z/ZJClu_QzzRXDׄٶcƶ&q9R2)q^q" "LD!@OcR>,?>m UZu|1\cw1d[ѓ]VsWUma+gZr<ƃLnv0~ԆJ&U㚕Vf|y`kj{\v|_L&La$<ɖE+W5DL%w5"Ϗ;dڳcun ףW&w,G +{qF&cIݻѭJ D ]d05`东G~wMBī,W%Q+6xv &H]gUe1Oc]EXΊQg42+mzH4 ̃x2YB˓(1gfkLx\Λ>dNdRH;kT]F&˃oj=RLeIT2LZad lFqoSR"d27};wb}7CO"S)QӍkrχ6=.L"@!8Ԅʤw?22*B|/ !}Iz(VY%b(l #:Oe+WKUuj!qIwgWYӅIwA`C Wܢ:eI&8}Iv%}2o:ʤn#쇾 B۬@{+tuxa.:Sqc*-/=\Pe.q%r7*SL"@!82V22zW4!#}wdR9[rJ&uOkdQ )z|P= !Vd9972Y^_Yֶ8~{~+/2hIUNdݸMn]fvPVI3S)2!_MT IRUBD&ILL M;0mzLYǷw\d??^-7zwnnGI92|E"~Nƨ=]UJ:,7 -s_L"@!C&^Wֺ0U&TwJdY;g;[Fz`l6$u dR݃vuڭ3\'LꬕJB?sȘ uGZ&5ޡq/_?eZQL*{|k޶ ~5_ڶjvxYV!`(G\&x]Ku3zAbx !)Y昲$2 Bջ*>e2ȢdzT N}]-p?k۝/D&B nN2\oQp.F(ɾlI&NdS̜v(#i'(xʴRTSdMMB2}"cʨth!-!*딐 ($(DִQ(J_e2l,C; $P@ܳ5HQoWpV(#j2'B-Hdth3 Xh "z^Y^ !JQgTee[("$P,DF$ܳ(G!e@&I AyRX$럻vS4dHUӍ __2>0oe'oe[|}T({E^D&Br$,"yӦH_S ɤZTuŕF]Vֹ?St;Ld((GI2)EsXdRQ:ׯ G'lӱK[ͪ5 @!@9LpIٵ2ܫͮ Nu~LԯLJ @!Q[﵎WV~~!Uⵎ[^{faArI`6Z:+2 G:7ۖx=j!gP2I '^opl~yL pxqn).:Z$CY@&F&g׬,ؓ쫫t @!0ܐjHk8+r.RU:=2$P S~$L6qV(! d(N&(hzE+gPr<D&B"$Mx P2I $P2LP"2LP $(2I $2  EҋeuNT81Prרu:˜X`D>d2eRΟJu-y UQsWbl[-%j;JB2!M}/EI$LLn[L Nȸ>Hv@ҾAo[vs ]hۇL&L.TR:iWutvQ`LՉ"WflCj=O[NS}LE& drd[ Ԧ^=Vy >X< 2 AIO }ddn6[rG!m!>=k$ʍd^n~uXZɶ$L"$P*J$C=S_C+Y>e4reLLּa첂gԽ2)`y(S_LsYW-!H / qZ,0U+|ޠr5m5 1E& 1vLZ2>ӍE WV]n/'s?Uo~&27m}uvFF^s6>{冼>=}WWڋ/:߭M;9]}6ݎpϱe]j=Y,KG&! 4,JC7[,ՆzPVO.XaTskcac}mW٬ wtdmaS.Fz܃2 灷nS+PYKPG}~8WiUwۦ ]d2yeRΧ2)? Y(R>5MZu:uux]u3jmjRCjYZֳYC]j[֨wev euG)$ D&L,޴^{8%  ݶ"rk~l+{c|%7䵄|vGm>YHwn=&y]O[\i}#w^Y2 CUn1 +ϞB 5!2\?[heU=}4aQ7z<ۡe0HtZn_drzWY_ͷ=rBm_G;lN#YyE&S&crL sI(4L+)3 {Y"v"cՏ/Zۭۏv5ܳVcレֱӸ?zQ"2gwϏ>~_r4ߓwL ĂpEQL#JȲE%;)ӈ`UhE`eZMdR22 2 COzk2Cd'N&k,I_3f5} '!V=ܦ۷,H֪6N1eXe8}wRywH׿CU սƧ$qszƸLSp2B2"$A$LJOWI좼!mIRC=| YL#Ӛ2i.\ȧTILt6|U!.p!l}6Q&f8Hc5_㣷0!)Ad2)?JA`9'Q֤~5:`~|y~C{J'u$2I.9n )YK.[ᶥ{n먫Jp2,)˖hyV& lWB ĠB=@\2-we9L4F!"dtTϛc0Q.22OE(Z|I~ȤFj2ܮEꇙx2Yvu<)ގ#dGiE_[mioyy]1sW"q 'ζ}Ƿ/@Iy=kc)E* 'Z<2[ͽ)2T{\gJ`M=_,T+=؉L"r~R6qƵ>hhnTL˓y-iի>پtErӱiZ^z^q"w~Y|~G{ӏk/<w d22LPfKYV]`lC{Jvc9VcQa95-?D 2\2iIi?0: ൟ }zpI/|3?8&"j?|hă|Ku?ES+R=V><ƻeHLG/=TWRuLIɂ?q]x WE"e_.GMRѲSDUoqj2dYM߼D&IrYK5Ldხ:Xؒ"ǀ> eRK>d!E$H61TՇgγW +\4"2z ˑey㽦d;Ej%*[Qk""$2 dDu?TVl/ $2 n?}P{OSd9P&uVQK+mk8!'CV*=zeRyȪn*%U^%éeRBEKhʩXpΆtCd2Id AH{*EdA@خR2Qdrˤ|LT"R5LJ;F*9%`*YMW_*RUK^ẓlqKL,$2L d+MYdI@3y-B{w9H/LI0XДIBjҶQ\YUˤ~-hl6kv3"$P$%~&QpI믽L:1O%#YOF$V#RDMf=Ĉm[,s\_'3L"@!0D~ ݯ>X0dR$"}2u;d,EڤdEd-lReT$PQ2q3Rg9E*%;*EFk,_+"(*HVYH^Y/m39Vd:>0$2L qܹt# ulzp( 7$^B&Id((GID&IE& D&Br$d AE& drӿ~G憎;k+._t}yg%iS?ɛ6{rٯp[ AQd@&SL&EE>̬̏dW8wNkQq޳j<һ=-? 0,t?5E& d2e@ۋv?m 78vܘ~}nwv܉nS@!@)hM;E@9 $L@JqE(3̂;[]| Ak; .G*Wq6( 29xȬڢZ*P |?Jci9PY29pJK[m-:ߚs s6=[P 7b gPr<dW_[c?ʟbFVg ɗRwsV(ǃ!L/~lH[HG"lf!HOrIC8.(s8iJ/@!L"$A$Lfddt~J_OW,;*CnH{ŁHV׊ϯpțK 2LP"1|d$wkfΞ+.Swێ?OB~ur'dff$$2;hcXT *NSeGqNTxk4'R" 2dq7:aӓL:>: ?׮7ܶt_ܐ%nUk.xg;gN;eurm2 L':`QòZMlCٌuX';Q5W5DsD9Ld"!?\8҉q9njӜ3!_9yI{fϙ{r^ꇿ# LL~d3>V ¶b91ljr$I@&I'Y{[/TEX}RuՓڡ1i˭**ꧥjf5]grMjV.#ICg^Դj]z{K=>1ElNcz[[m&ǯֿK}._+q2c <2V]Dq,Td2ϱ\VmJc6$*Y(2I @!LLJ.CEF: UլB H5Jk^تb!(Tl1gGl%UJ:E+fr6Y ݉}KTtTi;<ۿS6%jێyKkS2O_RUUmO:ձU?8'=ˬR2flO^C;E& ddreo֨iK ͯ]͵EIYFxozOgԚbGx"{^sl~H+<=#?\Z$2Lb,v(Ynf5[#V` ۭޙ*6v%j9Ѕr**eu>sCmnϩY֓Wֳ lel_.T"_i !Z.8/ی:رw["Aan9C"^qwngL $hr,==:'# ޷A(Idr4 iG'_{r5rIP $hj Ք[7FD_~l߫V& @!42E+Z9+rFkׯɽ%~޸ݬ,g @(G s$"E{{+3(P&;Q@9 溴QwQI"T6޻ֿ{7-m7l(_&R ЭKڇv-@9 (ubτccLBG#w65HV  (~h(X9bĈ7eb[7IF J:O fN{+33Cƒ]¥@!By?v5N͉攉__m>pbϗqu״J[9$+Gnk올}sKBrj5jgֽDɇdggg1zrPPBXQgNk]ժ/< tz~Uړ'033>bA؉ G:vL;W]~7|n{q-Q>p+?;;+r[ d3 PR%Lߣ?O?/諊κhnf͜F8'3u_qE_[튔Tl~A]f2e}_?ɖkW\v6=:iߝ?sΚ~α߉39kA=gvS N/M3ޞ%3u/Z`_\{_{w~FWδJHZ7}@\ľ_5[>]ldeۖ.>.=T`4h?[Ayop}e]ss:q*nW:,;+..}s~tE:}ŧ9$qiEo-8#'4"pӦN0nc?3C7=,K-1Ŀ_ >,?ϊ=O?|g߯tEg_qŋ;$+ZjIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/Deployment7Prometheus.png000066400000000000000000001432261462611535200316550ustar00rootroot00000000000000PNG  IHDR")IDATx |սOB4B**ʋAQQQ-VPXJTx,-VŖ>ͭѦB+j3ɜnIv7~>O2/;;;ٙwDV̵b]$%>LMMcD^on<+ 'B!VIHb?{pɇy^׽.0",Vɚ_^*{'ef?$z)B!D ,bqbkd7|f' ޤEy\/ƍ<|y@!K;Vp%*'Dhj6x؊VLH45)`Nb{b _}ǍMъ-rXR]@?N5s|B|-V8;R"8߇XWB-$\(_UVXĊX wN֡HCE<--\tH@%|Yw?}\{\= FxhmM~%׌\lQ&)Id#vK PN1QMuR(3Gj{#R@.\v\ [߯<(IB՝I](dW()0ɏ}d( mQ7Oۣ-1j*Y;>9<(IˠYVpPTg(O Q1;_ix/YioǢ(>`R\sO$&~,Z2I$1 yk^>ՂI_$VaC-9ȾPŬe䴤#J$e"|{yXIWJ?TyuO"Q,3IFȅ˿r2lJL"9;P!xXIgں.,1o7_H26!1?8NatGkJZoy*R&)$@n̊9dȐ^ze\ ٺuzXyy-DxІ 7U 1:On\!J^vbBfF};ɴ)r}9eY="k֟_+^^U^c~{C;Iq8(M>Y/!}m디=z&0p9#//O>Acc\~I(7>r=ce;<))_")Hž׶ʌn>Ċ]~N۔F8Ű3WPFLB (srsY玒yӐK,bFt1~:x6U2I"Jq{=ӧOYgSOÇ=}Nn޼Y>_۪E?شiD'{66LKRRIHAbt$;0/19+8HaS|{򼩣8e$2iirm󛳑9kwl_XO e2`3W- {DsN$V^nnc=Xw\bEEVWW+sdGٵkק5";=w#UئQ`7:bǡ =ﻲdVi߾H$ ӇeYUEp[1=551Kׯݸq@PtH oVy\R&I$UҺJ%$$lJLLGIBa#ūq%)OOO ٻ\fMX$c9jԨ_M|Ubmg|eXbmŮͯ'^g@VWs?\b9geSZ2I( FdPL3{_vRK~jj w;rkYgdePe[TCoʜ,d'!S`q,q eP& Ȥq(S\΋M^Y&#-(2z*fΜqN9唏cQN~Dii<\UK$L2IoP^8CILd;!<&˔(Id#ڙI5o㺻6T>yOL$x=e_CILd8x]SxzGLڟ;|WjߡLd(ME^0>;d(f&-֬Y#srrLF=0{: &R& eL2nO2 LKOËaF'U2I("ZQ'68` +f; \NyZܮymVt5֊ 8;k ܧ%}ksS!B!&}9d`[:|KN%׭yAWΚ*{)\-z3WW6ΟcIBn"Qd%;~d5NuKx}̘1766MGqq|WdFFQ'K1T]'_<Σ+2 2%2+7C'oМMD,|t嶗w5!$Q4?c>c*,8z2>eҿq(c\&rns?qEK V,s \A$)_=+T2I(%jMMo+%Y[ᝒGy˴S&c;ЬB@qH Id,h ;$gVv|C%(Io 2orJ$!9j~:;?rLJJ:&£.e2h/0 ͵EKso1xy¯ETCZ_]pmenJ%*B9I͙F$e2 (pK"Z5:YmNr^бCtF&TU~(,kX,"UvV&<4Ld1rȏDL2T&qQ:2-T35g,GkU³v>*& ̯Ҧw8OM6)׭jgwRK$e2k /^A%t tT&/NQmUy7<QR32$n{vf2I(aB=JM M}( dȤ:%&*Ӹ (;-6hƲBS"7h@**";Ed!X &DYTZQYNCLd!5?6m/2ɠLFL{h#lN.D_22䲅[xI>$22\'*{} $r(vLnzǶ0B8ZG?{Y$0{PQeWy 9cTLdr?~|JdP&."29[%NFq\P'Ϲltn vh]Od7}"̕2TA;Dsԝus}@y ;\/Pj!,f1*)2?ӟ>6leA L"CX`DmdJ TGdvfUX4L 1%wmg Nos=d.1/LLſЬ}@ vVq#5eL$#AZ5ƤLd2:;;ÇcB|ASOu__L$2<,S *mxpT,8*MLOc7AEeWoQ^Qu+(Is>sU9j'Y"T2=TLT6(2,1.ƨɪ*'/^UkPb+W؍xާ)'KciTYJ/桉~dx5u-3Lٽ gۦ,R&Z&xx&NMܟpZ+cb"E&IB Çsg][[BYWW'Gmg);򚄄/c5G4 Wf䜙7XU/o }Zq>ռ"e2diXl%D@  Y땷\,v^>붐d6}OH Lf9tsnBT:T&q(IB"u#);??/~JH32kɓ'{+RLV:7G5Rtدu?5Id,7thZ6"~g4_3279߫\\nGpt;r\M{y\ÓS L7e2s97p$Z MV8ݼ]L@$•xp…rΜ9r߾}Q-֭SLiw7|ul DSG}N֢:6'8ӹ53;vhI-e22N,un>;nRCuvvJ7lIM7DI"%42eŒnxh] Tixw8/RFLH\^3 ~*[on[>cQ+ 2''Gմ[1T/,y eKOAZ[٠}_֙kf?bY&)g,>mtɢS W5z5ѣΐy}eVfY2}ef9Y``?y-sݷϥL6)ιܑ&!J2i<ʡL2"Y&5m2=3"#~GvP{-[:"P& ;ljv25.&D.@vƍ>Err\u;7 ^Y͓~d9UƪLB$ǞS4K7̝*][*uv6Ky`,=4{HBJ!T&Q&Rk6ïXa~{h*}:贎Pa){] [uݯ}JU%ayvZWDn#$ec߯=GLs߿?d'fj5"??3K$b&Y۝~4NF޽ǢLB2W_|ܞt_6)PB*ռ/#SR<Ue!(II1";=:m,E#%=JTEݹO UM`I$eQc1#n9DR{o}rrN3`3Ϲ£NvaOűDUȠLU-C6QMC͡F Id1a?՜1VCShɴO,7#O<ᴜ$C$!a`Ot/"kVV(̃ WͫMOO,11u_kfLqXId ƍlsJKH)@b%h*f*0Ƥ`I,oG"M+P&Iu2֟_?}nor-3YcJOqf}z7d:ڳW]}pCE4w:thݫs2ʴwɡgje{Hl d%Id./= Eq[oŎu2nL$&lвޠL2($eOO>iEsK~y^{9t?\ 0$0I""U^{1'E}bU&Q2m$G<*2=XoK($Rd,$eA$Q!kޖkP7a1KY3q4K>$Ouܸqegg}ȑ^xAzLFL"vU(fz^x`DX$ cL2($er[䄋Jtw~+F"I"";?1d,qwcg(!AnPD4[E |jJ;ۈyМۃ(B ߫{+mGZvnUrG~sg:Y2~.~W%ㆇfm^w|>OlOM״|*]6?."Wm]5ϻkҿdXMOX~c䈣iiOYt!$rmjDG0YƬ,yfY2Ր!C\IeP&Cw?}:bsl1|xYpzyCi$Yl*.unB?,nFOL#u<3&o ^sW>b}l7Ak#/8;3^2PL5jԶ )3L^y%rjDyj`{BBW':~Ugז8S&cWN+=3O@,!|#MYn`΁sy 4G YFW5B޽{OߛoybƌIHHK"AdP&I$2dG$DdB2 >Cm7u n"g Bnc源J싷im.|tGv3ɰ0w]#s< sϑo-[~@^\9`ϒS_CLHlaQv06w---,aӧ UbMgϞJ89K4y͋?eA$*yFJ&GM<S9էќ[RI0dїL"71G dO쳯`}oMbqL i9,eK.W}+]Y7N?rѤ M%WN>qŌrye8e{MLJ<ֳWiB \x;h@aJuIX#P&I29bܩvp(&hj bߐe4MBy 4SI_l )zSȥĶ~@8a$`[Q&F8c#y}Ԥy%C'cÆ 2@\\ܧOX];yA{|Sb񠇛Lye|rV敲.m)I2Ĭ>I4%zfVX{tD&U_R:o0ф՛d1$ȚaЊV %MʭXf-V:S#GSg ګWLΌn29gevT_q);mۧR&ItȤ*pc :) x;[4GV#`J^f+ 頿σf$#23}1¾Ƞ_e $k{POaG]"^!$zPnwG"?[ni^YzzX>x* ּbs\//5S&)2ٜpG"2L79DFQ|bc)))IӦLhy=˱~C ź/|O$I#xpTfkUrB!ˑgId-m~le~\[}-~i.{Ly`=o즳y=ފ;oMH!>G;nc6˖?H`^lyU{}meτ h‹ςyŧ ߻)2`D H!yB!2Eh *sQWBZe0_/]&QA3a3l)3]䱞i0b %#N?2zXd٢CIBdP&#jɕL69"!d{2 COa>PɛB45ևL"Sh 7er)Vɞ.1%u^u J]&K$*??8dmL4rD!2G3W3,%wr4uEW4=EVYMtoK&{",%^k䁷kS&!zRg^jTVIorJ$@;0bF&e4;i *Be S} !x菈yh> )DWdړI%;H( <vsSo2i)=D'-T&2I( e@b?Jm|BYDb9Ӑ@SD֎4sEFR8vOGeR¶F!TyUeD4zӮLkL$A ̊ t`{B!*hΪN&h̤7}MT'@JTZL*Q4Cy.p!_^VdG)2`P&LD+Vkc(BH$2]hehNWMKCqdN .vLʫCزaZV L"fMԞr4kŰئz=2X}4=?h2IBd0(VMcoO!B(00 4iMUdUR2GXbG,Sbؾԩ!G3 B*.~֛!ԫ=*i4*TDT Zl2I( e2ĠO>L<+fj},!P&#.??יՀTbz 4z/iǜ2Id0(D"YMob6e'B( e2IqaP&I8XeE6TpBL2($8LqaɈf& %BdP&Io0*~FgPe2rq9b&B #FG}S)1MgQ`7v d2ct"B 1 _S Tu}3(f{MaS\}dyߠL&9Vl00Mf !$Yo`23>A1ʒ{(0pQϰ2IDSs2m؊4Na".2h`Zs Dx YJ]FS䪗P&?XDư&?B7 uwQ/T ijn8F8_2Ibiԛb2&B1HuɅ6RF'o/O>ϠfAv?PdL+oxhe d.@sg(3$+-5Dá7)J b+Q/q()$B~GX*k׽N>,y4557WE<5H#'ğKN=Nj&2D$ EK{]`9L$Ƒ}CI$Q*z2a"=鵿رCU7z8%%5Oʩ7]!dY!4wFSe+^fy,c$rum 8w/mLA0WkӥB$ ^43S?,TVh.q+pJ?dc%mybU0A$0&%3XM:J!~jbK3䝗U_I@3* ꃞY]:o\xV#+pJ?aN9P"`KMhǡ &˓ 4j7BWj϶:..%GRS߭>>>{Zʯ8 M΢"3DswKǡt{c,.773Nqjᠿo^NC4Gn{dW驍=z7F>>sD {ŚB xhmd, N~Cr+<3.M>9dg}o[alđ*V{{^,pPvi)' }RI!tCفv!% tf8 C١q(>C,ò}s^Db4ǁW\Ba|*BH8~yPM3/}W$8Yٽ2Nr|nXbكuB!Pv8dR aP1NNMBI<5IGc~gɢY')Lb㺻dNv&!1{ʊ8enJ=F~/$dP~mͯdKo}On!Bǡ`qNqaq.6%~n'YbP~K6]cP8͌&ے:X-)G:KRRɆCoR?n 3B <8R.R.Kx0%<6NܰzƇ?[M[4#]!G:at:V:]3B R.yo_>oǢ{<(<;~N `E 6R] $!JX)x||' w' 'Kizk>>QsW[ۊeV4"IeB!?EFG*Ѽ$2I!tApLEB( $!B` P"vZBeA$Br+;B4$2I!}hL,zє$P&IB!vAG(ѧ$2I!PB( $!B?+fa!2ɠLB!D+!X$2I!' DKqj+JyHL2(B!Pd~Rg.B( $!BH{@Wf{(a!I$2I!-y$A$B!Z۬a!_oykgOy>b%y皧)B!\+XB \T~`q31e4g"N Y=W4LܔIB!Њ}8J<2P\q^o~{92J}6$eB! q#1+XP&.>vvRM/93ŬJ{e驚z[C׽ߪ{m2lk؟[4.؏gVWl[&zo8fj{jnK$BijZ%!!$GSX4!.W-fY/UeJJe!fvp㺻@X_yxiS&4'_IB!l* Tq,g./2i\L]&M/rXިjS~Ҋ zf[)&ׇe4 &2mb2mQG/",@ *-Ŏu^Qe@<؞O}Z_388fR& !BBkh)γߑb?4ihD%KkG& VLw#˶}'ېEdhsxTet{wlKLIӛpzMWdRI^[jʤ>f+* Y8dG?QR& !BBhYg^'< XGTu.T08QbE.ug8 {9NIIbwhvTC}S,j2PCILhgO)eB!{AVhhT=n7晙jvG<,xɀ$2) Yk|?g͗7h9;L$$HhMY@LfGy,j y>迩އTeBS\s{h22I!Lu$O؁BW \ZE&xlﰏe%|fo@`$ȤС٦.j.4D%K]IDɈSǶ aDP!_DYjS%͘e:*K+FQ2(Rg;gYIdfB!$|@*ђDA!B _2mEۓIH N dǽV yNSlGPWо"& bQ/xUAA L#kCT},I2dTmm1ת171f$TmuDHH#y+R}LB!Ga8Oq;Ad@45G`_eg3{CCV3w(B!]=T!^& ~@}&W`{~=Զq6ڏ}[[^y0m0up Y\N`$#62I!5܎tfo;}HRf9He9a1כnw\L^HPtYiG;OL2(B!1 D#9ɋؠRgq;=KFO>!<- QZ*"(KdP& !B܊ݢ%KސTR$ 𐵉8~*Vfe зv]_V$2I!v@zR}ԛB%c@yL2(B!dL JҥQd"6d0̎_8X׌2ɠLB!D#-yP ebCFnt"YauL2(B! qhiG7f]ﳈ&{"@>G$2I!BZ3ZG(xEG3etq 0eA$B!!28H4oω }/c9"ZƯeA$B! Ϧ{gQ墩 P&ml~fj)"P& $!BHA3M6YM8 'Zkg&ˍ}/V^%ňѩ|fh'$B!D3eihW ]fH ]a|`c2ՐZcq>y%ňѩ2=YB!$Yt̲E 3EKqZѵ hcȣ~ B ~JӦL1:hT!B""Co6 QWAJϢ@8f˵i7V1IVwq6uet4DS+ B!)f j?=AUgLH+˿Ū ;K+^WwB!Y3sXb"ĨBNZEKqV-M"){u)Jv/ewZB!$p7ߢ"L֦iRw:?<"tfIBh+Yf<3OB!t_!6ڴ>e1[1 ϾRgP"C&CУN⁃&B!1ݘoLS^N!f_?fU%KVy0)Н0[_c%*wRb'iݟܸBΘv~b~O z BiU@Q4ːEơbp zYCo!>TqYaq-OHgVWsc%4q2>3ǓII'{~c "Bb3dQقC(Ӧ1t<¿#?ԇBy@-!B!BB O0#VL!!B!"ϐ?g+0dQ. CP~m>8RCB!)\-0DPGk1b#DXN9 YQeq^lTB!$I! i(ay OY)D>hAÇBH(M}kESFBQn$!U e > T|D!]ˌGBL faǘ U_g]4y!Qh|s@3T}L,75昜hΨQ$,4}v<; na k7oEϡ6#RᙱR + [YCB!z%,Ц,u6W!cz3I<ҙ^R-> B 09 YD4? čm *xXH<-yP! [B *2ˌ}4M!#7I[l"FCB <\cB!aRChg@`T tsh)S) "BH b>UO4WPd|js'*J-yB!Q*Y=s<Ȣ^CFB@=!XlD&VT,%!Rh !Pj676ƍPS.<E%*γ_4U&$!F!aȟ9\e܄5nZcŗ&x`Re`rB!YD ,'$przhi`q|MBB1ՐE[6]+Xl)Viya&#I<$$H4! !De^dQ1WDI6]j#oZi<-Z!B!1 O97,<<4=w! !UqY<\BHR *ʍj, 0CC"-w% !AIUgJ !,C^oRm:IB z񦩂M %8֋fhECEq;#L>]TWrɂ7$0+Fuݧ<^_ըl&B!$8?CI*p6b%!΃mM&}BHxV/i'wQ拄BH0x¸Ao,gu ࡈD{D<8[qҊ DKv < *YFxfU܇Lmz<B7$R$`BH̀3rE:ߨ-HBH_IUB!QM!U³"0(P9WeE<[ﬤz~!Xfy̘`(7̄D;x|043#OHlDS-'b`WBHQd\lGaHT<μ,a" dqDSOB rD>S Yd%&7-hLZ U]4U_T7y'Z<{E`y $1L%OB 8yӛ;7u,Hlr#D}wy3Ԍ#坔Z+3"95wMƣTpP. ԐCXM&2 FQ4>&e>1PR6M7K7a0ZCnXdW\8ԾXߡEᵧJBiT3ӝ2DC>i OXwKx#FnW׍I%SAF2_ϣs&͝<] \ΟZz<)%%yB!3zM)H,\))O>,F)&{}+EBD2W8T?ҜLNMhVFo7(chzʳz-! dӒ9Ǖ$$m`1]%CHMLN6F] %&Dw{ئ/>e+Vs\:^,HoS)`M%E_2 H푂 ,UɄc3:d^g=#n>e82A 3=--ӺW(Lv|JblJ.i2m}/XtLq'`!%-0p oT>~\j?Ӿ)J  $11f>2ɠLd SNp&X2`P&w?}LLk\V솿PCl6$eK!/qBX#S^y%v?qNi5V'3zy o i'sJd0Cp+3vH?,>O:۞kHpeqn7eJZ6G%b&SSk?ypWz4"*? dN}iɵ YͼQ&8iICɁwx#3[4"ɎuG'pjlʐud϶p37ʈb]lk=DbZp͖4ǀ8pIu'tHŏϱgq^ϸmXx+y]sR\]M<eRj'Aj!b(GbmDFen^Gyd⟻;3YF v:Ս;; vS2_}n|G[3%eH?O;?;n{Ko%շ\vzX?nw|HIocqJdw$ MNsMz AQ7ӛ[=Wy7!.r]NOت:+ׯ5U]3qR [\w)zfF>/"ס#bJ< +E$y9 <eҿ>d-|t-u騉gxn ^ Rd"&9!kF[BS&7[8渁L)X5`|>kl^(Btq겨d^̙7 ƀ8I|'P )YùsT9X\5}9p8Vkpeb?>Rm? kOzc$\_fHIo.0D!5"FW2I(m$I)?7UIl7E(5o^pCL*͕:~OͦaL*5.B$n3j>7Tˤ0u7T%ڔP$[ @>^f2x{@5P]XE &%ǘ 1!3YXSFq=ׯKgC-CI( $LFLlYGe"V7Oʙ!L\A 7u ITKoLxFeI2i,FLx$& LP&U[P=zcoWy:o/J!g65U2uS2 GS0ThM4zd2܋:8ry IeP&( $Ne}Lz RJudʤ/SL8o$PBd1di~w"՝:7e2T2~ hk LzB]Lz6D$u FjVc_Q&(=Jt !X2ɠLd0eRU0_I"_L7AÍX{Fg yLe;+h΋5o"~{?ӛ2*DH_IuMeRz&5ص7t %A5oK&qNu[8}G& [>$2I( hV*bM*4fF[e¶DKDu3N>L# T3ێ$2Td$eKqs*ƊLoާ /<)I8 ɣ*@C[|dړI%mm2jx"[v\?$Wv^>ڪumx$~C|,:DZ̫L2(2M C!X9%sP*LB-uÇ' .̔IOITҧQDLo\cE&!=s3>M LQxM*\Ջu׷'8zc D]؝b^u۵-TV ǵ|m`[}!XD;Vdyq ZӢԊ :$2I( <W}P@< F YJd ͧASOFG؞h8 2xZd7[z S&q m5h6 U>a8N(ϫncE&q3ݼnR&&ajA8Ĺ^D+581ƁloTP8Xs^:NPF|ëdRuT}B XI\o]o 'Ie2 ߝ8l~,Hx~n:M jk_=XL1'e2q0H5W<Ǎn{ MJSBu}Q7jzrf fnؖ~ScmfQ&qɮ:}~V'E1Qbe? D6ۃ|_jHtx(\^qi `}k r>ݼx~ELBRg8L2(/@byIOCM\ќF _};qb1*NF~P&- (fG*z͈LCN2އ$2l S& Xjr+&&Vu-MA#dr\%NKBs2hRGBץ7迄vILR&#TEiг{$ID'er`XBdP&*8"BEKU\ZʝVpyrunf%XY:LF`'&MUѴ ֐LY*VaYI$e2CBGo7~V s2IeALz2/2NJus#.gˑBѺBEƓZC&ED6dReѷzB #բz%e2I@nH%!Dw_IB( ʤ2Y%RdqܢEhj֊m>ǗLNQQ rcR& $L2(] }$2Il ]&*G@q]-iG&P& $L2(AIbBdP&C wYVlw$Eʉfxv~L2IBdP&̕P&0-^bDVYHPVG&;˶8)PXtx9s;bJd0(2ɠLS&GJ@"F&VɆCod[k^sf^&_߯3]$N)ɱ[$^Xo+m||dSp)-UAhiҊ n#SYRk6r]S !t^AL%;?) eP&$Q^- _zy-k1 vŝ׷n]+gϕӦL_jK% ߫l^ɍ+`ڃGae01'sc9~yZ| fpELBIAPo`$\1Il1}5Tc TJ?d-%KWf8IM?Z? 9yq~ZRS1ԾICk]":mId*Z6A+{ݗ쮃fֲM&ss1~<|IyvA(l"sH%+&0Ƹ(UؐLIyCu==uF{~aa,&lzgl%3; aUbR";Xp-_HRS֗ d# 6$b2{Ĥ@ ,Aɖ}A+ZV[R \npC+&%d>t_T4Vw6ݽO9vl7 5ILCeII{x"׊T <;3,&?}/1jÖ^#{ 8tv lNj/lM׭_qսϔ7I1% }$b2OXf̦7)&1$d:h$bra D8XD @:y4y|cHHY7[>-`Ԅ7փ)jVŤ@@XpU+^O$StŤĨ9Y1xʳ/-OϭV$BJK,{bsŰ-?68_?3O@#B}]rKzשAa{I@Lm*pSezUUTTRRLmG_c&oy/u_-T:dvnZ$@I(A'aRK*)gRbII{\8O,SL*\4 H1)1%}wM moTzw}G7}_ bW0Zo`[95'!0,-j1ywWu>qgA=,UTbR}v;0(}39bRV>l5usupV7wfD W19r x ZLz]3E?doևTȘV|4SFL{ = TaW;k2bRi6}Ĥ3T)`Մ;᱌Vy^0?QK>ɌHLZ b$fIE}쏘R1QabRϥ%2*VWkx^i$b1ٿkZQwb2i19CL*tS ƹk,j{+&5O^I (7TkC% %΢DBGJz^=pm~ma1i|Qb ;;!QXtZIvWԽ==R1bU.%ɀR1й5F>ɵ;5K"U"&A!&E}P{dĤJVR!kĤ+$$D֐ %sڙI%)&vD̀㴏va_bҎϴk?8#1~ܰKXs*ZrD ;QyZu67~}KXj foCpu'G_IT*1NBGIg4c;#̂MQEy$HvQ3m_B7̈6; 8*nӌ2X )ӽfDRpݦrϩkQpZ"$\.lxv/$b2#Θ2%:+\GDTyWuwpkʋI ן'3^+{U:Hu!B^I(IC_%$$$V 3ӱWJ\{hWofW|jb phί]ԸQG)q1ʷcϣԸy+2='b1ΜfT'Jolze:3s-5ngNQ$7<]/2R|J Q oZlRq L"&3fZCMu"}VYr)}eQ9 xkr)w_oWu#}oM[.޾ ڽF)S[]o_Rn3J8b $|\S%$G_{DLbX1kbCLbYL2HAL"&1,Ť2G$$ ᇮ.X*E!&F+ &1 1 I 1k\m Vjr黲%R$bòLL &1$ &mQ1aKuC3ږOk%Mcz_1)/:aI.աJԱQR}FDy\1D7LFq=WVP &æ%IxYODV<ܦꙔXUDB:H߫KG+!^/5 =(3\_)zUDŽŸ1 D]0ұ>>a䥘Dovv^'/&ALXO5OO_!TKVL*ErELcN5uBÝ6qѝQ5u { _1{tΜ RU^[!l\1tu.g<{uWVTY#5]q3}$&mWHH\'p;W:㕉1STyDَc1-1hD.>-zo6d+>] $c&U?**˶L St 쇇>w/78Q(5g!K)^>¢1 yɨQ#~t%D$a[7ތCɡ0ub@_+ 6b3r>#0!GPXK4$d#Fݗ=O03@dLo% x4.&="oɋ:TtM޵p)HZ J$1)LHɓl,#Fx-W;N٭Ʋ46Qlaȑ#i}ǯzٝc?AX命OS~OW^菱gb\l=E&y Y˗-η}&-0bҲȑ{&oL3&[EMJ |=$Im2kA}Xnۈ#<=1[yZm950l }39ILzFDA1_DzJT &Xk;4eimML&g6DbRhBF+ 9 N ?-0ޒ+Iy$<:Nshɨ8@L Mss5QO¿cb#:IڇŬXSϢ:s Z*de9r|ClmUC&C z=>ZV!Ťu^Y%I,;DG`Cmx!&DuzP`nn!Y\VF2k k1#wڌ1⇋9E6V/5],zfϽ :ؠu_9jd'aZ}7bRTx]ʿ:Ν 5A>ǂdg6ObaAI6w% I1[uhrT)iQ?p˷|#lPU^ѾV"& |[Lg&zxZC$&-JZS\9ɤ`@M}0U]4?H+n0hYQz4nW¡pGM?bkYT~cv-!RF:^*m-,iGOIj $@ˊ¸%lLc*FFQIDAT i+<,+?^WRLMQרzQ#xrEIy%R 1 ЛgMmgIlM4)gO:؉kee%:^LȂQAB8/kzQEprN`8)5 W4*3KP* ;j%%ʵdH>A-"G )_!4y^l%RaQN,@9 A9)# h0$ ZfXuo6m5"@4PE/'.iU1b5g@\v"ȑFq8-qt̟/b:<;)KU4LVF= &#4l#JJ4z79b]:pW!I:z fE >GU;oܸ39Ġ#b%^Feh#(WPD/^հ{yT>fM&u"#EY˰ #G$5ס,.H֡{^U8W#ZRaO*Z&'סUσhYwy0˱GkօN=OP 'Xr턼bmW#GThh}5LHZRaHjb;RY\GHxfMGoPO:׭_W:@Cyu1b_rXh#Jy* KF-0סw^q󅇅TRd:gM(+/Y%Xr䶛6.{u(/yQ1W{]\Fc&A؁dXC)&ogStʲ%EEP6&*PT2fww.3PbɆڇ_:]#G4;LT,aCVkc#w]>l^hݕ:OuACYU3EQu-|U@^& ۈ#^=#ZMc['2Ay-IJ,.]_Eec^ټN>23j/Tpٱ9s_-_rqY OJgfnGlu§Fq)xdllQ^;bĈ7Kʊ^̦i)1r54y]I ~^4، x=a̋0Ph⏃FhR2nɰ-;oxXp~#Bv{]kS 7p;`fZnɰz+ #N#Jd=o#L2GhrSn6:⿊C]YA%Y*Gv@]4:bnd%kvdBAWq; [Yuy$^dСC" (z6#(M `PVe( m^:{Vz=a)a,RH^2r~0Gɝ^l6d@XL~m7BGc)e:gjnf-3urUP7G!˹%B]~XrF?aܨ[v˜^Tܻ^2,yMvQA4˼Pn#Du,zxʂ ]M^LG+B|M1oS(&3/:]"eK /X;dv/3Kd6i(AZ I}TY/$LŞ7%@25Tdn }Wf"*/zYp-^,a4Іou?Һֈh9V&{3 &DC"BboKw FnuƘ-zB\=mA:2]1Yd?-p^Gix][LZ{os\C?优uV f#(s[˽ x|[цɦ#&S .KX%6Ϛu3kܘaDgTĤv6%@f#d+ZC^gӊɆFf=K 9 ;4Bm} r#D%`G[l1uz&'A7!*L+z#l%]F~"-UKTa^:v? @vo쬩[5ytDm ? @bN;|vՁDᵻ͹5l,xP $vx=39%z=^<߬ 9CQ6{=.7:ua y:Y)~aszn;@nֈ=6%?f.?Yd) Xሾ6'η^ bܤ[b+c_89^[ 4%G#fo8 u+ Ȼ! mj<[d8J1!iHa߅q]@HZ* ÈrGH&mqUľ{=/ h HMه:nQh{N%yk摟9vփ'YyԊI坵V0w7\s'쁻?[M[`/k^oεOXgӾsuGL b6DhIm>c}¬%%c.2³ 1̦GMf֌'c«QFY3:-̿s+=6q{~q3ʔgl5`Łgy)ߪ;沲s<(Uͬ>gS&O89oLJ5bQ"[H̤3)ݸm8WǍ-}?r1?=}>gYah:G͘LqWwyǯ ~UּZ ? lx qoTNdo<~0\вg5k&sq3ߐQc 518Lݼuj '>gv҃3g8cmL"N6K1߅oH[0U| Ozk>ЎBՅWHRO^Ϩ| A @#HL^un-ͷl|@; 1cF؋#,]O<~ցr @NSXHv۽;liQ#FxtuWGa[iOPhr{e9B?b {ƍ-s?A aO'{s潅@#Sl񚭈5y*v4/jCa\7u(Z49EL<@Dvn ~f&2fOQ@a?{~YYI'E F1mط{ ,c‚7)Z4I$hn"˘~II-\:fi:+ӼtrM97b2dYͳP eg븢>n\nWw[Ax)#U~ _N>6Xx!ʯ:ɯ4U`Ǭ%cŬ3Kka̚[ӸtXܛ札ɼabayBe/s\'vt O#O #,e/$dNIgeXLtw9ju:xC||g_1Cy{\}C./j'$p5p][(-ss.u7;vA=:'ky:mޖt| Hu<4FI-y9i.USHd[x?%&:\^׫/1)e8Jgxyٗl mn;t6#eʨ=fW(]Vlwꀍ!Adʵ g=oi(P}4sӎ憘z T1Xc$֪q==b 7:.{|/!,bv5/N#ުMt-7n ;դs-3&sԙc5ou7thYQױj#z=T+h$֛{k;u <WL1t@̽t:L,wsnv_19tb7Y%&[xb}9/$3݈Kv}_dž>Ĥ}+Ma.S~ [~87b1+8ikաasOl$ĶP:y/sRh^I}Ϭp '?tOzm,19f̘h?cnj6dlb;1Qb2º9] ݫ1i-siřOfӑ>p='k Ƶ^ sm4LJgNJCe =Y+&gؗ%+&LWL6;B̲.ꏨ2s[z%&9ޔsе7Eǰaʟ:wWh8#&SwMe8fM.y_s~eeKL/wZL5 1\,[>CE $cWeYw9fsnXq~Yn:g2br[m$yNOUL6D~9fu+Q2\)&3$&K";)fSXD-qts1Q4DB%]obR'GLi"xn_saH$bhx釀̤q~8tdv]Sq: lO $iV.qW( ǽ|:vkKtĤ~HWL3۰NkS82]1)ѻψ>TŤ#oqFg/~8<04DDAHgE\WK.^[?yuRj$bhZL5C2b=^ 쏘3ËKALo[Ľߤ3Pb]nvʣj'TALfKƛxaHL 3 1CbrW2gv:brw$Ui}NtQ]k-&]OĄ_5R{ɘV(L~$D"&@# \;2bRl1;Fh{ _UȪS'l跀DL"&F A@ELF&ꑗRKX%q$jxk ;H8==pt"%$bhIQdLKh]BwI찚Gˏ|[YCwW޻>_ _y%S#&@#DLЎ"&V>sCཔ_I`X~Ij#sq'G>>*Ul(9b1 49=O9t<@;6Ѥu w%R0^0߿K;o> ^M)mZ$ITx‚57~o9Qwe &@#sl<@;NKQJ7-y$6.[=cz0v OPO56<}cTŨ&Zi˔WS&nT$IT^ FBQa6WyVLNDL =O߲鄹Ӌy*vVY1P%HIg ;׼p͊x&1hUz6m-s:Z'DL @Q1!cf\b1 @#0 Ztk籐[omhG1嫘o&ja<շ>|A˭}>| O+1ͯ]L~clq`V*e?SȶsxKY#w>ӂE~bhhGak &S9&$hhGbRe\??&e/U؝mݧG}9!&Fv1!&@>5.!g/@4Zw-_<ˎDL (bCLc1)Ϣ_*bRQ c<;}lZ>$Ў"&1p5]1ZX1"ub~bRAl.DE:<_?\KدY??oeY$`112+ΊAaLLP͍U{|QjxvbrĤK뻏g$ spwyݗB^yhy*v#&5kIiI0k+]sWAL pCaHYSbr Md:@#0LU ~ ~0dbhNLv6 ~ ~0$bho1IC˞>Q@LbI$b1 @;IA$bv1!&I*jcY,[b>fc6P+\:g}fp=n1є%=b^ò) + =̳q Vg>טs ]Fю"&1䠈I-ݱUIӻ!fZGlg:cNi6uݚEd[-sMY0ϸ}L]'a^_5$ȟPאּsQ_hG>Q$41-f6EtlSmeԜCLf2f쾴߈(/wC"=v?E(#? ]' ׮v1!&IT]a:_n l4;&ml0mξQb-||1qy^'vL C%&%) U8-NG3*&ݦiݼ 1{bRËjCbR!/:<4_y GLgنV_"זɊ YϔjZS̾QW 5=ELbXmMYP+*J*޷?֟0q?nXK_Yo|n>տr1c6揟k~IiI`g{{l}9A4MŔ E~?`TzOwZWtYΡ?b,0(g:a[6NMw}2n"ֆ"\wbtʜNN}әx3CP>X.1#&7tKG(2sܦ8߆>7!ԹEL榘L54Y1-HLe?1ψ2Naě*NoC`7"Iwyb>w\6hs _{}[lҋv1a9+&{>G^^IՓw|NJI9-PWR۴)m Fy'D=NK0FJILC}@#8T25C`vM!^"1"tCԊu_"1'ͦs[oaת9c֋ɢpJULΎ?1Y/w[c"#WW:uʎ>+CO$&J~$br]LgCR]g B+3)A*ah\GbMM[S!:1 4ك&3)+uN:-)ɖ8gKb2:kbv &[q_M!&sSLvFIhKCg Cb-NP/dl_oSD?wEL"&1,Ť<ٱ^tɅXJՍv 3Uxj"1T:ɊI*?X/bhŦUg<{MwIqI19 11Yic<f{kb-Aǽ*IM1XI ^d)7dL&>m̤ O 1v10;X_ךrztS:ƏϿ쒷#:=^>sC_|a-}Q \ھ秏o|֟{ A{b2b2 1 4CyqUJʽ z}䣺t5D b2Ĥ+N$CArSj^;.2(BELW1)~nq)}08N_pYoEjۡO|w`?ﴓjdJL *~ ~GQ"Rx T&AcشSBE4<JCi)[?y5b1Ah l0|B/.O}I .m_//dxyㅬ>t;?l镮K\}$`zJ =kvJʫ*o-X+ JW"&Io4^w$ &0{LݣNysPLZ}o͆&$1TXfn Iy(TUb=V/iŤLy0HtũpɆC^L$b1 4`x&c$b.5N=(brI 6mLɻPQJ*58F*C//cXLF Pk:^)VV5<ʛyKy&UT b1Am44lOЎr?rALJJU#O_"1)&av녔pa( sb~=V jf݉yC+b1 49ov3 Mz ~ g1)v՗4э;~i?y 1'&%?WQI UwU;BjxHtEBcHg UI;J|SEbU9I$ 2my&_ϳU( +1Y]qX,c+(LiS5QJ)US5Qcd9ʃf^FMT:Fu^_LG"V[*(<%F4Ry;Ӄ_DL"&F`{V{4( '1)6(OfGRM,J+2*((OnZQ%횑)Q)屮G5V+] A TV[ ^KklOkU`aI$bh4*ܭ1;ǵ_س7WOЎp3I$bhhGb1AQ$!&@#@;0$bhv1!&=y tyӥo??u'ӫY9}j)>K4I 1gbRQe':ұ/;vvgԟs}sκ3.݆1vj hhGb2yqv>'zKlY9d?k6Ǭ-=k8hG1!&N@xZqqk3|SN-vI@#BЮ屆3\glz,)-[{S&u1G.Jnb6u<@;.?_~زתfTlLɘwR 졑wşoQ!̜}-,~Sc!c"klP @nIF}09m2&OL 4I$(bòQL3{l_. wgTV1yǍwM,[ET+@#DLЎ"&1,dӛL򛢢1oc7+hx] e=*W>@L"&hG6|ĤKń ϘySf.oۂq?d?4N䊽7T!@#ŬXU}4ЙXҤ[ϋ1)SQJ9RH,zT#Ө'lï֢1K;İ,&LQGxr.|ښvA+ˮ_}@ >/k`_v㚏񖿕tߎqSM] @#d*AZ؊82[2| f+ U&y ICo-K!5gY=҇LuYi`%ZELbɁS,,,0|ǶO,=iʤg?3g}Je}"# &7|Y1N뎈횶 t稙 b2Gdcti>KDLb<았"fWxy+\k2܋zӮ-fzAf}was>KG.nǣcEr6CBwJiμ!n>WXlҘgj ]O]y1̴\r)7-Lz{zS]gzSnPLZͱkC/s,( m͋' M>:3L)kCe3\Wg9\sFU uι֙9״,н\elĹ;"H hGbT z=ᩯ [bGÊpn M10yZn:_Iw@6 |n5lS'J{Nd99~sg֙N+ԉ4!'IddS<#F8<N76n0P]fl#P2. ,5hʼ-'Sn=aS_4ϝF,W;׸zcc&efo5׬{unz~snꯝ8x{JxW&?ͽem9AS5;Dk(&s.N~!|ԃoR%(by+&u?fM'c" u5P'ٷ+$&;VGIynJQ[p;JL^ y{={Mg uNtd# ]igBys759]iISF2e`KP\<աoFйl+t-^OD~[Q&>Cu!}JVLw.4ŤEYyn^k_kKCϯ!N_e L)g@Lg5Qb2$>& 1ּ*QbrvbrED#&moY70c2I 1 Ťg:+҈Nb0>stxWwP,lO׆8ym@L"&kzj-j2jKE@ssM6&/;MցQ/юfۺ˔x!Ȫ?PDĸ4%w uY뙶~{M}eDahmg*0Bt3NhyLZ𹛼-ΜeL^&;BL懘\EFF,nzfkz֎#K`4z=H5,NR(st!TLc^z"EϽx95@#ş T~w/GOЎB,1b:iW])UY?7nZ\S~7YeR9f]nɯV1S6nlw+ε1cF9{[E]z;r;܇xθ K :o"̥BIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/DeploymentFull.png000066400000000000000000001532031462611535200303310ustar00rootroot00000000000000PNG  IHDR")IDATxxչO%  @AFKTDШQACE -Ԩ( h Z?MԂ6Woim* q~󝜓=M6&~}ٝݝݙ=ysDvٱɎ]O<$''1ߵ?˭v̳#_qB!BH(a&/+>O?΋V}; *潗m~Jեy?%wg=ǎ$~ !BKe;jdcSp7 &&AT>򣆃oR"_ xQ&)4e!;)P"CɒW*f c0[P23@ڢ<ϫn)-1BjI7 mLnI$fPֺ!Ak9w' II xכ .jnHH}AatW"EG23JL6 =B2z*f/JLI8LLR&I4p{((n%](7O`3ʫ?bI)H2z\( d+e2I nSn%%k`|*vޙFĸפ_PLR&I4nȎ]ܭxSsFr#bK/0zS$8+e2IF>9RPIϐ-yD$^>B'P~$ňէBQ^<,)I-2jn  |<..5S{<55/}O쨴0%%iii5cþ}p|cNY$FbՋ$zʙPfg^2Dll J$eD^^Ċ^vN۔F83[7tLB8iuV֐A2_1ԒV1tg>y/ sN .^:S}b„ ̜9Cr`=VOy=KIeD0)/pGvJ&1eYӀLk L ?!umL7 |:xqWr-/ZfG`ٳgOII j1y jP&I j(/p S&wPaf{o L̕D#%28vlu.OKKo/:*j/iݻ7Wn馿%$$`ʐe"PR&IBdDLrɫfO&}mƝUߣLSP R8F )MMMٻ\jUX$s<3ߏA /eQW37 :X/nyYn/vVm~l0Q_~ww+o`zh5weus9>eP&$ O/A̬K\b%&&6I$mP AtQ!}|Tκώ󥱽M6J[gEQϯ'GO9!:`],4$RzM`b [oœ_㝷mߛZ}IBd0B/2eNNnE9=fC,s?2db ݯlt>#0/+܇#2_0GmӦ9u䳜XueP&nIy(1m TM" !_}$V3ַ@n;/ze:KvrrN ׿>xiYgd7dGC"mݏRU7|a%%%8ېcpy,{2eP&nIP'y0K^{Y&IByg͚|E)e2e Ye8zZ&E;Ě>x|~d.y'8P& e9t,9z%m$eP&ÏS%229eXlW5߭pF }a03.u z(2`tL?eLl ]xx_&1v%w_~WeP&;@hSzA&mbffժUVffdt Ľ֞_l vW$L2='}YcKxx[&!))֘cxo$$ٛ`͹oZ^{o.̺jf}d/{aa9C62T/ۇkPNJYiMD/S IB!|o/F!v~iDBUO><ӭZ}БGU0nͲs]12I(]+O2+V(lYqv>a„YӪo+VZZLFLrשOXrjB Xr10͊.9Xl+׹9 !*?9iЮc|nG`C%9 muLbO n(ITΕ* mv,\h+9 Yv!~Is`Jk)2&~¬dp7o"LL2"T&( p^#H 5Z2G5 ;v<*nҖɫj1Ased/+)9]oILv#\zG^lf;^@Ge"QvVm3 qE )T蔻?]'3J$0O>&… Iewd2zI)vi%3z?Ɵ^Q'21i^snI Qd},\#V&Հ8RzYѰN0ѓ/3S@䆏8y#Av "B(q2M`geRb`d91%2I(aBJJxꩧ%( d$7sz]Rl N.D_22q_I h}H0e2)ͰW \&/TËSR& e2Lx<,s S&n@jE(Nl6ʆ!J`]KLr`|2Wd7`(G!#eDӀ8p=vV&{* (lYTsTR& e}?aU}֭[$22 a UZl2l UX5u@Tݛ96\K/KP&)_(k_"Tl'mpI2jI=I$e|:t(*yN=nldR&ɢ 2ˌ Gh#U62>1hxS]ݏ\1+F}]r$e+yQhT'3+e&@tӀ8Idbn2I(%&?zE}2YUUeeeeY/csss,8kW6ZO?nsk{2}2YadHR|6LWk֪]KZs۔EdDd$#Wl#J7j}[d2A$3f̘w>/kkk#^(;Yʎ<&..3{_Mxgpi29weN5 ky%d$1X.t"LO@(kKGvޣ2Y0y@qϳ7Q&C'.&d^>RE CLֹ<'|?O#^(!P߿?No@z٘ HPMYr{C#ALFLn РCiD}QVgd"q,yWI\XnGpC iDkkS| nl/n^-deh<6po-Me a/IBH;% .ɱΝkݻ7r͚5ִi]7H3_Nlٰ . ='r6VHNw_}bb⧲L'y_(<" kʏ$JV!fD 4LB$y2@VsJ]#Q&S?Yn*H<.+g33 2IH9h6}*w,eyo/͛WI||=i90߃U&-,4m +g,!WN+vK"uLUe*0Ǥ`I,kG"M#WfhL2($d?n+:Ϻbfuք㧟Ag;ҷ_O=II5宣F{WN&?+%%勘d6Zޞ8R&[Y`d &,C@1m75VЁ2($dAB2ɠLWŽgw-:HrJg ΀A[hˡY<h)aHXa +]*Im_hI w$Gy=j$zx~s5߭L~?YH$2I"B&ռmzaGmqs?}j IeU&!8} !B]&2h>S&!\x͐?sM? n"g Bl6#dEu!x-^kl w}ᐝL {Ȱ!|5l\s0Xy|돯;n.Nv4%%'p`ZÆ [g] Pa4hqU`/o߾B9ccc[4y͊2ɠL$2zQe >=%gS9՗QN/$H&KL2zQeB!f9vXA3,Lr"ѷbAM_R_zW,.'Ij# GK3śਬd(;00OGx-q!I _!P&IV;?`8 QB xm2Pd+!ke^d28B^r 43J~H?md2I:(v!1 iiicDkW_}Ճ6moD?Ǯ gFFg" L2(ĭ2"E?)vy͝S}1_h2TzYM m} \&7B˜?8hР/䒣<>N[7xけ#G^ܕxlذaӢqL~e[3viOLȐId fm Dg 3Yˎ>GGdR%U%$"D Sm;!8Z(!6ڱԎMZ,{ƍ ,h~29+ZwtY9}SusYݩm>E2I"C&72$Nf-cΎF k17AE(I>1~{0Ǹ y<BHܒD歷h?hy* ּRs\k]0,'VALdsVY@!dna;H,`%ڲBq'e111;zD)޽{ll/؏Ɍ2 4;\[ey"羍O듭:Б{(s eϔU-vnz$tqDOw]74&ːWlϣ睷uz$0kPPͫ:{_(^7փ :y3Nu֧L$کA H!s^!BLBrN*sQWBmkZep._LbH| <29ʋ|Sd2 1CTcOv2zX<]$Q& eAt< e,sEB!dBOv7%(5ևL"Sh 7mj)Vs;k[,v@(|xXg W]!Sˤ~)2ٽP1M0(pnf|XBe22W3,%w(uE+JODVS!ےIĞ_lr2:"Kmǚ2ڔIh1Wz(eҟR& e{y``7aDL2Ked-wh4(+B!I*B|RWdړI%;H(y~wrS2i&S&1f{2AvخPH$L2yHVj9vE!$je!X"Z;R抌 <I5@mn ăQ^2~"LkC$I2E?";iBTD9;JNQ̤m7MT'@JThDy.eW^> <ȎR& eLv3^);Vj(BdCB>Ui)2}BSB.jܑYAⰬV&g\(kŴئz<2X}4q?sTWeJ)2`P&{LV-co}\CF !P&{7 a4*PҪU" d\Om`jRr:B*.FWU4RY!Gs(!Uh*T`Xl2I( eAL}yvҖD&BL.P̟Y:Lq(v5 خ.eX%ݶ)I2I4 ;h˸dBeLR&). $i$vjK.!$2ICF8JtG/'|6)1dBeA$@cٛA3)#CɐM,{ !&i)FR&e/0)n^-k)tC$gpBH%hԔyE-Ue^?F85wNξ+$ӎuC>Y2K!f@fzsDܗi}ZMaSYr#I"a|;hUBԚs 2LKK}GPR詥0%Ї7ٛdx2I!ǎ mӚЖoLB!=ꤗׯP̚q'קGPTSs0!I(זR/EMb: m 1Ə޶RS?><d"xI$2@ev3dLBHTt%FPOp%F0q?,q%MwwHaF)=$c`uM2%ef{k!BLLO&O7 %C_Cbb[sy ((}}^ԷOP϶rN\n2I\J?2}˹!=pm5")SQGօ$''qCt)%+;/ZS=Mq88ed}e:Qq{gʻ7ôGxؒ(ӘӖ̲c+B- κw^~:pM eVZ?'/fxzEy((,9P"FHMCyW& SfJhFB T-?111l8XpW؟[}llQ;O~\ 'ّwP? B(Q~y(/-;7%]9 ~Ql;%w_tpVfC$} 8SSIm/9C>8\[t $B!{@r-Ar܅}w~mqʶ+IuFGw9oan#(=t`#ѕLB!=CE<5`(6. bc.b1!>o {Ll'=bj;vٱԎ\F2ɠLB!(-P&IB!`@f9 L2(B!AWID#$2I! vB4$2I!\;X4e&Urh\B( $!BHdJ,.!2ɠLB!rJZ]BeA$B $T^17c6w !IeB!$J쨓By؎%P&IB!`ȴchRn$2I!$ Dlkw+vsOI7}ǹWl[=_dmOpVj{=UnK$Bi*sB2BY(E )x1͞ql%%%8Q. 4{Go7W4i\PAI2^ZNmo[k(B!=٪5Ɉ v巛;Qʋ+GLA&VeJ !U;+6X6y[*")Qͷ)m+p9N[>mK&Yz&O _ V& fR}8)B!M<58d.Td{&?뗋y1u4w=On/0c5?ވjK[~ڎS̤@)&ׇLiTLd}\58fDRh PYL%mOUtCe5YIeI=#}}?(eB!g|--쓲02(=-ȶdrudЎ9l+;$s<׎ǵv<-{d[rgJ^1]I%ym.vd4WS&4Cm7XTYܑ|ާ2I!v 5!(EUI2-P`<%1GF5zcsL{TҬ8ӎ;|vqL >9Ӻ,j2TS`L#Ѣg9O)eB!wAVhhT=n5n33V)(K-4cf2 ;4ILG#E!G@|yi<øA&!GB+e9`NWeRe4<"E_oeTPσLb[(5a~IB!g<58OI I\iܶU.s͓ϵ܏L<ޡHi!dO﷚oH~>2zY|nIC٦.j.ub%K]IDؓǶsNܮx.p$F C3TI3n}jtZ5.ؾE$Y1HDId%яIB!DKfRݸmht?؞LBRP;}`nGe,3{!ZO8N܎.n"D&!M:75?>MWeBӟaYCFM璄+JFv1-Tӗ>jMy5ȶL_$rLB!hc`}pvSiDTjAIM:ړI!A{fHs̶>IZe||/ڢѫ"(TG: C +ho"3m} MR (Uσ)< bBI*F`znl~kǀ>yrT*^YxN6S=>=d5{$!BH(Fe ' Jgrh]sm6>׶@{m PbCgupņ,Ϡ;eA$B^)mG@V#,.62ҭR)劮 ~3$2I!@$I)~eK}jVy 22z&p >jpQ2ɠLB!Q܎]%K֐N$.kʥ*6Vde(зVq헕2ɠLB!) "ewd2ۍ% ~ox$2I!@Jp*K̡.x+f!sed+àL2(B! deh){E+;Zp#ʮ0d_#Vφ/'eA$B 0PKhgK3 Y(s3CICDR&IB!%EKrCJ ^^f q\mȢ>fAe4h#eA$Bq=158ɩ$ ]Ty,Lu!a<)~( $!BH$>28ϒ P!V,B i؍}'|GX]j ~( $!BHy\)Z^w,}QrǸ QSZJyL2(B!5vJs9"ZįeA$B! wPeii+P6ѥYxorA}( eB!A>;B+J8TۛJW ΃2pLq#_oF"Fgu$!BHr )Җ!{]'|F)T''I!eblmS{?jH"BH4i"0hˌ<~v1T@ZkPuN/ C1Wf6:fG߭1:ϭzD!HY m%hK I4k*Zfd!1~#FbԢwH̄B!gFjKBes D`ZBqy,:mXG ΃~ Γ)|>[-{J2xSԕѸk&$!$,GU$WX <*6iyʪ@7k.r㵙@{{3 zjh3xx9F γWYr4Ҋ9CqTWFq-'^WwBɔ'RWW_ jjMh¶9ZjZ$_E(g֟xM׻Z-h*Sۯg^($C!Fr/Z9bv"]w yC)JvϬ>ޔzb!$G6XJ5Q$-{YRШ-lW/aSPr_-]|n KlCtE&GpZ f;-epJH'NO~Bd_h~4>>2l؃s~)*kG6P6h2E G띆* %e}&֋֙eRV G+0ĦҸHsubNqŽcht] %J^هG]+ւ> w$BHec]R4ecY.!å/Df ܏אΥn.)ۈvIgXt|C;A|fU%KZt!C2MMi^/1m#wR'|OߚyEVZ#yFꁐḂbBA4e FSpa#%[4Ej'4!Y&/LG$>\D!g^J1 9wX}ǻ:e >S\@T} fI9i\\}3Ҿ:1ZbpVfcߌ>'=#lq<b@f>7>Fp B\+¸L!e W ,zJyI6X%#,CusG6+p ۭB #!+ϋdF;*;T-M6U7^!"IGv)Wv0%}j{ds]·O~"zc"YЃϏDKv. BkA)(KEFdxPzXAǾkčxH&q])&N bq`<֖s^*ZU;$|$BH6.llYlXoyq~h$Qs B Je1.`g 4ÐE=ӈs?,I#8kH]*#rXdD0D!oB˸*]G8Q6i',)dYQ,U |B,2 P,C,EH\b1{Sʯ bȶ ! eal 1#}(c/hHI^!N6~tU4qMB;c`# Y788.r]v%ff$E%o϶,9G !4*HB_RPڄtRd)drBiw@/sElTdLBzwvr)t9w0.Pgh%BQ] <˥PETm$2A$2eÔIx/$Atium iy G/ӟ%A-DKRx]'$zQenG4ew7G[-?Ldr,DL xlL6)$T{cڜ!!~7gIJ9{sl,ϒi}>h2cH2׏LNn6)$X!OBKbcd<^_(0!()<.!6/H C0J T?E5рFqB_s:6nL7]T4.Ɇ%י(?5u!B^opTExLyԈλ%}s"Ipp"S*KiNB!9] ( @$n"U 3r2AeczEMRvlhD ]"MNS"1W;Gfh wcV$'!5@sqH4e-ƛ*7 tB.LXbHC}iD~Rf6 cD,^28r*%I#t}xҤg MRC&d)Q1Q+Qg, KBʤ%s[!8,EGBzVt"bp$mӐ.ct^&E} qu@_9ta'! {|<Cz[)sZ";zY*yea;M γ "P Hj=W M-ϐCSF`7@J ߨ(᝹w$W[^"|3Yj5SK!B 9!!Yme+ IYن\rP~J2)<ҙ28 B@) $!AiCzfVm,f#wll๺B[r~$$xd;@ γtU;jC "nk *2KF^C0sE9dD|:}Wqd|pjD,&=Mp,evM!N܊G~ FJ8F>]Ì2Օ\r #:GbђddUBTV5j9BBdc%Mc\L|_I\)8іqѤGyoէRIT l#勄e2~ф9ϸChE]+U,$q|̎v|i%X~ r7dPGp"hXC0K[.G"$J ߾aH?Ij+B0C6*Gʌ j Wޟ7_f"Ir%SHDRe*yAB2+RCU)/cE`!3K‰M"pVRE`I "OT ?&M7c4r4 tf͑q;`]R"?w&}l%?I;K\ ]({5j S{95ȉ#:s Pƍ̴yM,wѱ4Fd2xR8dx0+pd[);P<!iPe> Ɓ^e,8yU?ҜLLNPV⡗nrNj FTYJ hju dSˤἒ[!z!Ur!ኚY$QF0hn;_f߀*6Fll]Y#S$=3nr>#ĕ`c~g52o5N0hx'2L[5D6.=)bg2)H$@M>O1A1ky0z;0ryZ?Nv-?U.ۘa"TYՄV51 7Bq>>#,ǵ+ qpL?ƎD Z;"pxvB:F\n}02>&]5//n*$o'3%iy3R vepܼt;*9굫֙%|n#,Wۄ`#12YtYLOH b^$,=gjA~__xuēdo'ܲ9} Rd yvȎ))IGcbbD!66 ?FNOje^&=Mj_j6"IX}'O)6T e$E2хκ+'ownC|#C%&ǣBpa1)G6rKO~AYd/'' 8doI1LB8$H$ $ZQF>.>(p;Dɜ'R$ˇ g&bf$~\W n|gقO,y%= ~4ފW,J&APzJJI<=##=~ێQQ||-<|eAl$&'|DD%v]Q&0I=d'8c\,f%]?QCl6$eKb]~SqX~yuխ8a gt5i(x+^`-2~rq5tt֡hcy9e2|Bp fqV,89)o5곝y^D[U+)%/gKq͖d|rrbmGoSɓ ߡRF$ZeهS_#: ?a#7iֿͯ1hIWsqy(n;('eR7VX nl\,cViC Y~ GY&q>OJMD<$X]ҷ)FN99F2t͝S[AM~O Q]0]I5Zy(G4 ̳GDIIecU0 *HC/sI7$K{C<7ňљM֨0"Ɏ hOz6ʐ KLNp f[h @QmF$kop6bh*{ͧLK&ձڞЩc#%뼞9?)19B`Võ @{8oP&$MB%Qh/y%k~Gxdݛ,# g۪4kUG@0=N)ᔒCu|2\+֑!S\X_h4"TLJշ'\vzX=xx?nIsE<i`d$ %x8ABAT덻檇JbBM׷dVq\?~ $꜉sz_8⼫L)33z~X">Qc<@VJ$Y <e2FY H*;=tCcFZO5ۓIssBְL/[h@So$x^H0ncl(J@!h<ⱺ,*ыƜ٠tL.TLMṢLL;R8pLAp68Fպ8o`0:pLc= h+ӈ{%8qm8O2׏ub}<뀔V k{V+ ]&qDeA&]Ժ`JP] $L-(U~\%6UIl"5(5/h#J(ѸRO5p, 3eR 9"ПLgU) nmf0@0u̵K0;Ns8`Zn :6g!,g=Tp> (ρO_`sLru!C)of֔Q[Ly( eA$IʤLA Y9s=d3Ih@ קvh!H&ѨR KkL{xРQY2LHID1y+cGHIm!Tp< <8ߘ멒`_*OU ^D%n3KMUY.uJ&!.!h &2Ns hJ&}P`~Cy(7/s'IeP&{]&U_2 V&e РTQJL?u)^+̔JHLv=A&edo<^\g~LL;ښ*(>m$,WVIۆJBאm/׶BC2tEOgu)'IeP&S&+ID44-5UUzWr2I/T ZdR?dĀ#EI,LLod:'2I=T |{ۀUy߫mҞiiJ()  `SI H 1J,ZJ-Zj)NVć)")e(Dd|cT  ~^9+{s]{+!Hc 7AGIyJ:njpOq\IyF4$EDXI؝ ha啙0hNTΜ;h0Hc0DaQ*<^Rtt\IKJ~eFdz,((^S =]y &5E;xDzWh;O<0 u'^Zj!^/L–Xg|* ]0I#LR Ith&C:rt*tT!@KwBtpte6LqJz >Da#;iv\s&[_L]*dLQeSBL֥z 8O{"uf8.3N{Ս`R{X1&uB Pc!q|sР+05ڒ,+HH;E&iI09^[p=&6ěotf:RRiGGO=S1h!9 <x[a-3 DaP}d`}>!Wg53 L^]X+&t`JI6=ϠY((9FQ(w 3@:'T2%e:D'׃^^łI=  LOl$'͠4dU:deSeɘlI=c鎞s˓:aXpH6WGG 6},tfgBpOx"t';j8NT8>:o8a_f 4f#LW4+˛8dL_?3!ʵ5<(HcO٣ 0~ o:\8tb{%^eˬuM($9k r}|+a/'QIar`ÊR Za+&LxK*"/›sG8x F Z!k<7-3a/-F߇4d0M6Lɛxt}5600@# Tzdl^)7gQEs0)syߜ#]ЊN/Ἴ:IdB( >$`RgFԄc&Q?&aIdbX0I#L&W Y,H**ҙPb#].*]oXNan'>#Lf!c0PUMJt چ$LP0,Ӊ0_glI"LI¤U{$޸o3/uw@,Sdžj,˭7}Lj"s0=[?wd=Da㨏o$L&SJyh\I=P1}H#LRaF&K=,ql7lPmq`rZX,5Η0I& ێH?aF LnR#w(0I#L& d$.#alSzdo l'Lh RȎJ$8SLC&i ۻ°8nk[U\K-τI0 BgIHHP}$0IQIa20a;<`[ g@޴w!] M|Ľ&*3nYIBML 4a2T(u+ɆJ$0&̀ɣ}]Od_:׮=_{+唟R.[T_u `cc;ȃ~Z09*`MNdWVHH+X-l6': IJN25YoI0*<*^aFpDeʠaɮ?/Y>pJ\5عuuߕtt߼v{s:E_OU0Ir&"s9nJ TBܬ^M׺łI]OԸv2Yc(Ƥc 9ơcLy8a502nBoGH*^\ߒuΤd Xve*Ga ц˜Phh#u=NLvM@sA=_L\0Rxz< F>z`+z MӀ x1pݸ~|Q+}M7G׉q9㘡0yL$EL!S :s Nc#lxevuaRov搌"ںvU=_!|p}QSLπy-M~.9$ ͥg02(oP3ʒ fyyf~#.·E`a6 [٠kDܔz} uޏq$E&ikI*`Fz.))S8ニc& 4Za&l,aF4$< KN,J|O$aF 8L"*x0I#LRImaRjm;U ># #L&i4RI0I&i1s ^/wfcJ I-`0ɩ@($0I&imȨ|öD;(Dd-\2wL%$ 63fNw=I$-xAHZ0?cڟÏ+a{6l"TRIa"Lhzz{ȊN̚; o$607#>$uaﰕSIa"Lh:q&EEUnb'LhSM})#JͰ!l,aF4Z@aRϯie+9aF 2eR]as6]R=Wos^}%|oq8&,1X׆ko~:e?1u"O}YsyM(7yw&=0f*Vx|=u"{b_޿y>dU +(8_ "$g}\#ʾYas]Q{]'u[ uz0I&i4$EL#Dn >vξc˰.:&tVuۉ9kn?lRyG :p0,ު;EsNcrS4^:&?~;\܎_'tpqO,=1A/txqϪ.;ۅLP@!@rZVhQQ>xZ3 u8 PP7,-ʐ]N5>0|FdMs-?p.a]_a9k5X2{7s# @}cŜ %'uMpI*5@ %k&ItyzXNZ|̺`x=7D{e%{8)oΗeD~NXoJdeM3LB&dcU@2+?+7+yxy80II[n#Z2v78{X\It%0d&Bd25<$IDATBG陜c1/&)*zrڴz_1H8%:^ha+xXlIt ӆNiwx 謡sf'"@w0{9:x4kDg VxApaf$c8KǘQ>a}tn_ ~cLzM}0 a~9>)d}0|Y 9׈/rPu$ M2#m4F8?nBZ^D+:Oi;PӼ/e\Ogq*`r2 tL!uAII%-'Ie LNE\J & hڴuW6@XEG~fYl( oeѲ>7N0vT" :U.kv)0IQɨ@I%Ϻ3`@p3j`۳BЪhsU\0x01Nh yIIx:|P/ײHO{1a=&YgYEH=Fz0IC9eʔ+9~3)M O:_h/F3߹Vΐ4:+)-P<ӓ& S>ԅmt€i}dӦM}.׮yʔ)@ ۑm5 d$ɳ/:#H0"mg2ėz?-HP0IQY1:EM13 ҂`[S?ƙMLB0,KRӖkZP ߔ+I jF~F&\'Wтcx35g`B"A95J^eg?-(?|%LRT|Zx;(j¥'hψ5LEtӉK3LB*_r|:u3z m8KN,J!I*2W o EM6+;5P&c>PdO"U8LB˘nQݜctm wr)a2H@%R FTv(hn_(H,E}pJ[νW1#wҚ2e6\"m2V/55*ٰwEM֪HJg͕cA0k8SM}梌5$TFB]u s's0x} o\&>x3R0IQW{T 9M'LX]4$3 m>xZgԩJQJdkh6|s_6!ə9{ԥ!eB P#܂^v l=T?kx# rʔc]15?m8  _ߖ i1s iޝe # c#1H"ac|[(\45PX$W4/}q]V9g$]%5La)0F2 ϟ:IQưP^y{_L"m:ަ#,+'w`1&ƙd'OrBq/99nbD=Ÿt Z/+CO7-_gU{vT@9ԜPV9ͅǩӦ25P,NCQ$t&^JP$l"O`{^?E=c#┊OY9%xEĎ,NJ! ]rb+3g !Eeް\TP.]7B^G ˏN6uPڀlRIغ({0UP"lRGaJ%$8lhܙe L*ky;(j4:- 00/f) PIL,-x$PSйU2}O}x4[.9&uA0\bPA(&΢Q LP/_s2,Hlv?Zj硼ꖵgK_/I Pf6y( b_|Y:em oeqa,@ؚ&M({PЬgks0%:!kkaiǶ|/_{GVu¡Ӧ"9́<ggF\4>2f#~Vʺ P-yy_8jTUIّ7 e Ϙ|^:uʫ9fM:o0v޹M*=jFV' .ozxq<%^b8$ /TpΚ7?ŊrKx$( _ UdAγoɟPNzf͙y4(6-ok}L欂ЋA:\373i>uԭCRTn H"xJLR#Q a5P)& `X`T܅%uBvoxt(BT$fSEePrPIQ9)y<*YP*yv 93ߒa)2LeTnW \H=뗲2Oe9泜}vPEeJU$U(* Q%T]#|3Nҿ^pe'Ƣp!le:T6[J- 7;LQuBŎ xkx(lHFX2C+dHFPfi8Ar v_AQBf}Rc\%È(* L/M+u\]*$l@JQ$K2XBc\,H^/$UDZ dv`mx;(rSy .j'Ǜ^xSEnm֤F#r_"PԨ TA7vPEQ26 ȠC۪2Ѫ&)FhN(  /#1&V IQ^jT#0G&+)@vvPEQ^B%#=b^ /Y::Zdm*X$c7ER6u&  p@: IQ:K{:"1G7߀(G ^KH2/=BHؓ@\E4C~+Nb* xVNI}j6Kg?ER<(eW]PLt]OE彧(jȵWӌlHR#4>jey,Ζt@>a!=u)%ºHἐ5~!JFY*- (q)OrD:̻H$C5>:LnX@I%쾝[&=<U'!`@RT[x7FC~REQiPёXpebVC*2&^>0YLy$@!|j* !~oxRCQi<U[V[az"c) dJv S赌%[9$ EQTɆKtآ7,}^dtD9ޗ2O[9>0TFNQEe*3Gv"oFKG%2y~/ϕ/-0Uѫ˽3SEQT 8Ceza^Hy^zsVKQ, CEʸը"ռEQT6&aLoW^׃ 8ZX瀢rRU*KEƛSުQnoEQk*@`!~H Fi0'߫Z~?wف)P Un6( "rr@@SjAXJd%@ /'g)oEQq U$ c^?{x;((*> XH"\NβOdb ¦JxRur?{vD|wf2អTJ&QH };SEQT Qkjn4q%oYmDx8l!xKzQ8-rz~V*R51z^VGb0EQEM6 LX5 5[GSomdYbc18Gn9#r=:u||)6ͲO!>2E0>6>SKQEQVml2@U ]K찀Z@{dm{si3`^οBqEQ *2Fcރ(((_[T*EQEQEQTj25:e~((Uo%Sp$h'f0 ̵f=-G8ḏ)@(((R,)ԡ[:}mxE-9 v&uAs(((R$a%;#BY`yשbY@& Td i)((+P^8UFU;e_*}'L ېl?)EQEQEQIk: O̭j$Oul20Y"?w(((xlSP4x|%dm]GTdD`^U=s EQEQEQ3Xs<e}~$@xjx65LV[Ve#SPEQEQEMv:,֚P1@ZOQY&8vXک"JQEQEQECPQ !W9c4U(((*6=&1u'((( *U$[*)sYif4EQEQEQ謩[2 Pl@m;((`N'WfWM^{X 2ǃREQEQPLUpL^漙EQEQEeBaPlui>^k@aIQEQEQEe1<"jCƣ&UEQEQEQ١m{fkXVgEXx<#*2zj((&Z _[d}$yL[LQEQEQ73lQ%2bl_ll{S步(((nU%7scmpYs{z%)((\.z@]f((( $7ձFٶ) Hj;bREQEQEQB$>jgKREQEQEe0$ krssKREQEQEA 0W.kuEQEQEQ'6JZvTH((W22QOѮ"EQEQEQ5Fz\%'mH1EQEQEQT֪D:lkoZϟQ˗w,/]~qe.9~I'--^Rs̟EE`W ^p`;؊Ғc?}-\_ή,o^~O\Q9g(((qu/n>{mK0X0{!go^T4謊s|`e+΍IQEQEQT~- .'?? @ eSq=DYoRy9ѥh4FK:ؕHe7%x!׆y,',qj[FU(*K[@QEQlG'Y ~}  ikwy N+^?㴓p;Wݥ-REQv4h hބg3$B EEX~[?ǟREQQ*QfϞySY/T_O;94I|Vt%xyT( EQE|?PhAr׺~;E_zխ,F(؎R^;gvUo~)ڨ!#stN;es9EREQQ fܿdJxٷ,=nћ9{,6F(؎Jf={{Lsњs3Og̯FrCv^p[y\Ƿl=_b;Jy4??{UB-$=:gYIKJb#HQY-V'Gm헷u;QlG)3_ԋ#Zv'?qڊB)b#HQY0La{l@)S|k Dڵ缳GY( EeMI(y[ hΫz =a6n}Y0\6-b#HQY_nKߺOv4qW6h;npqhQA*iiڭTPlGy?~wIwh)G9ԓȢEQl)*Dx\b;J h?'NAa-b#HQI$EnB-ev7Y( E& v4|DKfg֬,ZF0T$ $3HM=,[< >e(Yp](F-υ /G022Q7/v47ʳNq''k9K:E ʢEQlMNfm؆eۃڰ5w%qɨƸ7]ɜI@؎ sCUb s-kegE>7Ve+g~e(c%Xf /b8VGY,q=%lG3~?]7c+kq5~9-QH 9{$cqAc{`AF]x? nRzłIx$F)^m΀<=aNFX=>q ӵQ:uN pPT:ҙk`x]Q:k ]*erFqP^l#R*I^otDX%Vw:+x.:ho !n逆^ }r^Z&#Yc3hHv~ N|h/r@)P: 4T7,Aj{#!Ym{q.eY܎LŽu9{Su~sWhQ8}ц˜l`rƌo(El?dFlQ%T۟`' &˻fcCd_6=]4<Z.#up#s&и.`ܪVGܯcZ/ph4<,zz7& Ɍ3,^g0bc^ef@ -^xdܫʳ<0y@ʟaCJ/T8&Im޳ [xc;DxSv ߴ2 GZ֨T5's<'yٱ+m.;m:|:684Igv&tn[h[ix0bA`v ШPJJyn~IuHӛ`Uf:}% LV{7-ՊU<^0䐊de6mШZe~YNe"8`R{)o䇜έit{!L!QӍv;n8I$LRl'[~@&uwif& )]@`˧s8tdoО\gPMrݩ|)Il7{<]4o' #Y!Nn{,LzmDaRzWgڥ"8< ImH(7 i~Cse5.DT& Ɇɿ S !5v &L{`{C2&x ô&K4w[{qr~a20Y, qX E.TL I\k[&e&0i_{$aR' 2x`U|y֨?o֧Ǝd;0i'_s= bƊU$)=BwNf_I$F0 , YW)6d@NBx2x'9b=a2 50Ӣ3H{֧pb˫җR^+=?sʘFm`NXe80gz\^8a{Q4i= (o;nHm<0lG&M{\]z,|HLD8ܖ0I، ر>UGFw6cYEMUHG1Zdlg&*mQOyVqbDm >+l.Td|F=g< :5{,ߵ\x;O~[x\yF9`َf6Ljܔ,1R7ڿp~"ddζq$a0I2-0IQlG D=RbR{%_K|!v H8׋֜z\Z)& A"L&)(a2eE0/!Z:L7;,`o5. ϸ MwxH'L&)6E$LRQd a]?~otL{%-օL$# @dGʷza0Vܳs$ab#HQY?uiڭTPlGy?MpXx-:(^]u .lH^M)}aZ$"<aHj[lۈQvA$ab#HQY_nKXw؎~dtPl"\tR7 r'E(h% Hh\qL<@'皗7Eyc]3Ne$ab#HQT6"0_Vǧb;4!,c i0<|4ؙF5 }~bzߘn:j*IH$LRl)*g0Hb;Je#LIb#HQiQ{?5Ar헷u{(E*L 7oC=OͣۛjτI EeǷTm bmoY O\gsm;Pe?Wym~0CREQQ$a;ɅDIRl)((aFqqõysL矞`FqiΚw]}0I((Ia2aXt &@zc*ze?pQ_<{,=0I((Ia2aEsD`Ј=LS.8;|cX͆&)6EQv0I#Lf:L"k0g\@,Z\䆺D?&L&aI E~3=Lo=W7Gvg꺜.N B4dac aJRT+ T+ÕmwOj|{2> Q0I#L&1^0=>&)6pEۗ Z>Q0DfYg`O(wio}0IL< iSA r&imEѕLRl)*Cd"d' (F#LRl)*}09Ćf؎~~hI$F0Ɇf=b;J&iI$F($a؎&iI$E($a؎&iI$ab#( Y,kwIcV6`ü a20^al p<+rn ƹTy*sMµߥ< (aF[LRn-lGv0la;SmttOH]+hG 6L4$8>)S8XgضbmWZӑ4X:Y^ۛv0I#LNLsK&)666xtm>x*<)7f:YXk,U/dq4T$ ۮ(aF$L&)6RI&| |vGPu֫j 35{r1%T{{xrtG@QkuY}i7:^W,m߭r!ϓ- 38C`XB & Yᴥ>WcA.Ek|ʬYj\+֫KٮuȨʌ{v0IΙZ(4Dڽ9ܙ`3w窆+}X~o.L/]\x3c װLv9{3k,pW\Syׅn4M"wk/3-o{}w8 T<`x*tضVYj6ضGx켬ǧh57K,=;}]13 OwFۤ{P4FW:ۃy:6t@ce=]!z\vɵ \ C:]6-]d$J&bg2?lyP2lZyu8mP `Ov =hٿ9ֳK; eOΥI}7y\_Q1:sIZz&7mWy8Ey 2w$u 7opNG |)ewnwuocq|n.Gw[0IMV#Cy1u(0>Zr y}љ7kCZt7-9!kn_FV[[dvd!(N44@~20'Pf ޔUK[ʍ/ԱGﺤ\V[1ռ<9^.0^z%LhY ιo?C৿W$w4Lj&"Xu. 0;uz;` sLCuWtJxXWY`Gn˳ & /d=fZHu~`Zsg_ת%h10)QX?TcKu.{XV:6LꚐ@ky|l)u0IѲ&!.3Pcc&Cۆcak¤/s߀O6؎0I 6| >WhuN: dٞ$L3M<GC:Lv>(GN$^J$KL u˝!P  /k}v:߽H$LhBkxc)Q7걎:`ˣ}8I|0/ab# Ig9<]R ~8`?L:GQ/˻ɞ(8a00yPO;wg4&U `2$ ۥ>(3ɦ0Y籬Og*aN0I .[zUus˧uu%pK ms'?|w3&O:ys]@$>|xJ!BY1N2L3< y (L"IG/øJ%!I+$p_y'#RtYԫa 3m<:E>as?;;dw0y@@ %Lf?L6)?_HJ&|dgw&{=IM(gJ,/Lzk{ ɠݏ6\|]sPhS~J3hBöիNhk׿/mg7[*2,z{.;~g멌~/I&aNi/ab#80_ Tq|oU/XdgSMS(@s,g &wkcwOE=*"&L.L4L Gfrx`T`aR>7(}8aY(cz % i/? /o S8r\0IDxA'/o Cd3PuTPXLt؟^3lSPl:83tMTG -N!Lj/>*ޝy4(}DɐtPk;cEʸ_z܅I%.e.? L6IYP JʡzTgO&QnZ~ XSEq6WFSyA} x %BWu+<p frCbY^wv,t qPWeE +G}b[o~I˟ K_\vIt:O}߹ݫq,,?\ڿ|>p|3dab#81*lOȸ4C;oau츱B5vD(yUjS& &M8Y5βR@'Ci,Rb,/OñCRC |(a2Sa~Q}mN׏pwO;\PDx, ?nwmst!UL Sv .8"FæL>|q & I Z;6FyפAI*sC7[/#G,I/ i'ρ7/d{ߙYb9ಯ'`R{=~?g> ^Kx;yg@i-a0IՉW/I& T:O>B^E }xЦC^6<+EV&EA*`<6Vq^yS$ 0M8 N6{x2 I$Fxr$a!v;UlG 6,PC(BYqLjpUpea @_b{lXj(#,<87y%< űl#L& AaC3؎~dLf ]gzx$ C// z0Um<6\5LHޣaP6k&ɄW$ab#HQY=LG+ (G&$t!э9~a=x 1I3 P53?!1҄@j$3@UqPqTXűq $a0I Vs*\qm ê)TF$2» XfS#` O8°1c- !Hڣ3b#d9I2b #`Kbq\Lx= 9)F0vњs\$L&)65IG924i؎R<}^:dGІpWxrFBFDxaGs9#P (Xfg_  jű=fb9W% N} z?g0IR${Hukd~;pwJ>Q*`a0IREQQ$0I$LRl)((aF#L&)6EQv0I& A(؎&iɌvstּBܷTT,-9K/~uvgXQl)((aF18i93g|}/U_ noʰVjb#HQEQlG 4d'uq9C̷=|a+d5Ey׫]Gzr?=| (aFp;GGQêb#HQI$E%Lhϟߴl ܍]p;q_<;/Xw$)ZT=s̛pLV!PeغJ|Y/7fTLJ[,Kl)/K!$"@ʞߚD=6lGߘI¶(aF L?-'狖,z>z'wWҸZg˝]@|;:;5i맜+?t+{_g͞5ȿǗdS~$E Rw^ !YޞkX%|R;~VX&'&QN寗5&mN2 uLCa/UKyH&3[Q#^v4Lzc96A;G-&&S;^cD/0I2&)FJ؞*9om~˴%lsM;_;eY;_P:{xZ֭ ahH.P#as@Ⱦ嬴]*Z;t헎VF'E;cWN&Չk4Ώ0IL5LVeH E.\VsO6ynG(fQvId0i|E> ,ة"^7jĆe?u{q6^u~4ίWꯃ8c=zN9VJ}b<]>X1zi$/ (aF#LRTdFװL:5*I` ڥƆKg$V6udCLx9x ;(͋ dG(lQ{Q$0IQ$GZ)GGgtԔO$E0yP%*[3IL7L|1m}k <|h Lo]=Fr,oN1L_Yi+l&c;J&)b#0CSۧStNbbt˹U ȶ.8,:;Lkleg]*VOWܫNE@suV96>LʹϬQ/~voCZ#:'E٤JĘ?d&@'zvI[zS__hq֙ dyUdnd_v}u"*ZHgQKvD ܬ"a^G`qxLR#*2D2(+*S0zߛ\YOx13xN}8N-C5uRQr~*]>" ߠff w<(]\w_k^5vd9 wևTٗ?s {X(0IeձY'plS v4;T=¯Y'kG 潬 MQA* oaR5N0]pl vS]!։چ~:X( *tĸ$a؎ܳ֏w^%k_§^=;l)( BEwUu =mϥ-m][^ʥCPt[E79oC yIX6usp,sfKq 'ʆݲW-͡ܯR:a( ή|?M{z==vEG~=ـ:#C }EwmaDJC5r|/}|o"}KA rO>hruѡ%Q=,@<} 9{V,[i~ٹ1ZtT*hREQތ0aMfЗ*F;4^(;>-_zm򒣩TboNNAPG &s{VVfGUEy^O>!{LgE_7/*).(6'9UXbt֬8(hSUJviT:kڎݻskwm]ܱwꝷ-͌Gʊ<^ԵGPu,^.,/-^_R\ؙJ&>eRO,`O6v=Hi cFCXW{D'/7y⪑PQe-8}$Vt)"""ryG~+`NdzT1er /vrҥuWhvt͋{0uso gX%g ǵUQS Qb(;;gTyƺ/Lmn|dJsnS\錯[KnkPꪮѣJ8F*ʻgNoZgM/|t UMY_׆/o ۢ!O<ї=x{%&6=єI QeEynE±4ԍKĺߥe[=jgԩpʖ)sr}[e_3k[g<2u3-uֿ뒗C&4v;Ĩ%!yܝ0*W~>CE?g^k:aתux~ژGמyȫgDHًgގGІ[W= "x0.?E]~?iOFfq(@ PFjJ P{?ׁ0b% YC߼ S೯ 9jc#% ygAs`< iy`71{.|ծCo-A&}'{5WY>!oP~A ruBs@@"[!Z{]vַ-Di[#y4-Kk9w 2fF A9rʮB P(@ PFvƔNyzH~`ɸֲz[evN+Ծ7 W%S$g{]b *$Vl{ z=H (bˀ^Xwp^~!܋a-G.m0@B@uS5S%y4Nݓ|_(@ P(`)7`jV8 2 ڛL}jJU:J[[ dؓN~ R)o-hY3^E:B6 v|r|x08)#{n`-딇 /EЄ;W}N(>u` (@ P(@ P].@@ P(@ P|(@ P(@.`@Y (@ P(@ P888aj(@ P(@ t@+FٕuaYؾ}{`r P(@ PB p(@ P,,;0FR(@ PQw8 MZvvvv Ӣ:nԋQGTUTWs ~}Ϙ^Ãh֡e\-3&K3S+]„(@ P(%î%Gq]oŏ!b/$%GK/BtCYh􏀿 :GFZʇ);G EKA^Uo/3I_ =(@ P(@ &zN0~/jJ:=e!ܽy}w< /gܸTKvv7pǫ/O_~O@ܲ/o0{P(@ Pf 4/y@0h- 'P} }M*%'G>E8|ɸb,lMaHx!J3$ 1,"BU<62.1 G"[-llB`CaNCt r  U |lDᣰjS1*B ~ uG/9h cʓ`L8h>ÿ15lo`@[gh P(@ P&0C@Ap?z@X_]/‘"( 9(+&n5E8/ P_Z 2$ C ' OEI6T 4_|E-(9vO@y@a[痣_s>φ߃}%8o1M(ßꃒ;Xgd1a>6^`@$fB P(@ P[ 8 0ѬR|;)DRZ (,!OG P(@ PV`.VLI P(@ P `@;U(@ P(`a,π1z|(@ P@h3 нi_I֐(@ PR*ŀaNLE P(@ P `@;U(@ P(`aND˝R]SV_ aqxN9.j4Sǁe-و=?22hEiq9:*g?\$PK̀Y0(@ P(@D@3/wq$mV<2cB9p7a7-qQ34_\IW %) ~ LSuh"8lMiv- mDe*syҦ:.V(@ PU t" OO@OҒP5dbyhkQ(DOG?q\?X:f a >i"`1`:MCOɛ:-GE @RGQ3]7,}GrQs_^u8-*Dޘѥl$FTAd)UHBtJt+HY!ZW_w4 P\4I@^nw 174&|y'EiyeZ.HIE`/ŏtQ_ s-.NG+ʶ~/)Ʒg"jO\12;Ȁy)ɘ?:(@ P0@'Ird 0 X*-{0Tg(a_u8q=N6o Tw|vY~\ 3&SXz=Fʀ+PR~9}}Ge1sQTs2nSZP/U# P!W)9Ut _ʕbolW(.NG}^<35*w`2d~0 ?c;"H P(`N~QHעNļi'/Ǧ5[Q-L.l |r>k5?aKn|@@ȷj:|d3ֿ>@E >ӗ`rUF( ` ߹ 7g#fr!X{@Ӽ;,aDM_{{ f=Py9nee4nk/܊20Y2p2H!S8o}ae:a 3| ث(AYI%2P5*KK i_{@U6QS] oocf}wSuZ8ASt;fL<(@ P@cB\FT`kRʕj.#g .Y))_\!H@ԀR<2q1p 6&$|0gtx7";}3WjJk;+l{),CEe<=\P.뒯Z)^vc~ftʱ^8oGCG ٔ2Xڿl[.fyp:cV=FUl"ff> xIJ"|ZYmT0(@ P(p 0"Po SI\gG4*̬ ziu%*j74=:ž;8yO T]@s8>reըTiu~lj_ q뎐  jh  ,!]Ǿ?vWւ#P>6Sۑ1#dvݩFٿn)6iX˶p{l ]!QoKnX# P(@kڀ5bY[,> a[.A|)qZ ؙɲqc݁ujvUM 69vaҏ\t~r#g6c6A^j mG+zjV89jg+28F[g#Fyv: ץX Xy jm}N5Z+$*Vn &d.{gXs@l^\y.k$ _qFv`dJ?J3@>Ol(@ P`@TRfǔ 7le&1 4&uVZ2ݓ}}\QrC&"K>܏ԧX9 oe1sX/!1x&ԧڮ?FRd^i.bR&ڇJ _[UjEOxm3uoxv$c[$'RQ*, ev45u&j2$hn hG#ScP-sרm]]٥ex?d:U>x-%mMϻŽ0P s`=t`y++ ]m%'*7 GiCoPn0O P(@lx XS@@s)­%nwB{ߙl/ {o;SP:hP?>P=3~! t9 ?F8,I:i}>N4yŨ>{'{}["WĪy*qTV>W`x0]LՂ&AďT)*nhX0s(@ PaG` ix l_d2a3h7mLIztt߉åm8lMSɚa& s"|Pc==2sQxp/H)R|S$dW*C/O:m#5cPGx (osWlؿ[6( I8vS{%Qz0[DX`a@ɝ͹7TN|+ @6[&3b= .ۖY뵌eTWKPo}w.&>ԜT2'KS~[?gHnI01n fe\@@>WwCaA˕ۇ-Dڥ2]͠IbՉYQ(@ ئ@ ?lUpp^&'6%je.LȏB0="P#p\1 Ǖ5 *n 1bLLܲsSL~ [ń_qs āo8#:6&wz& S3Jb<Z5wdڑm|DF/W Y15^|$.^CW"%l,CbLl*]'Xge1<:}%2S !33}X)L;%wCZU(aol/h%݉Jy V(1z}|F(@ PV 退b¼;O!?s kş59H|L~%'<ʟ1c$4?2N&F ")IM±:8C ;to5PL}p.k+2~.Ly!`Zl^!@|\I[ki(gg9HXFY 4HP)רOkN6~s\2ԑIkGҶ ,9+f-~O =. ?s;R3[MwqV*z^VXZLf_VOԿhMtC"hy¾Qʒظ<5$vKr"qjԫj iڥVʃ55QOT5ǨtT=Z0 *P(@ ذ[sိI[ Sfm# 55=8#֎m%1owoxI/D }VwTɑU=5}XZoW+#r[?`v'Gjv(34h΢L?6(@ P@QX4{@/hيyW17ZTu phcJP﵊7\Le &dƽ&Y!s;9W _R.]Bbl ѸjP?V o9_@`+Zme:|r7[)HZMEڙm G+&TKۇ{E t;cFUҬk1HszhkGdž3SS(@ P@Cs Ď ˳4S\:p=2֡CΎIP Db8lDD8c_ !1Ds/*Teг'{٘X'-|QծӜ,ט ȣvJνJ\m-c^36M-* p$]ך=+"Zρ~J)>L٫f'X2?3Bsfkʧ/)oڿH?t1Hxe=}ꖭF됹tl+-5`4?iL4Wτ٘7TCR\ǮШbb P(@n.`@75kM0kM ; UU nn>!BCWC-ښF)++[YoKUaWWOxnQK*+QYY[2nn'{Y^ ܯ2օS(@ ض6 p(@ P,,;0FR(@ P p3 `ÝǪS(@ Pcg@=>K P(@ P{ 0 `π wN P(@ PFoe[p"8: !I`@k_{(@ P(`~UQ%/Pz yAZdlLA:V`߼Bnjz;(Ff'6 8 EiXXQJc!Yq@Q IDAT0vx YPR͈FbHĽt~"}y QAd+XD7L+(@ P(@ ܿ pߚ2 =6a/-AL\85he#f2/+>x{^bC+Db>~s6wSbz 4W)(:l"f g#ȹz[=lXu P(@ P`@`L& ;>{ -A_rcgc櫫u=|7R;P 8?ֲ"@*ᔥHVs;_mPob٘f=Rs!1z|(@ P@`@ߤ!BoC&+"VdzhSg@Po^ui>xbPbTh 7db2y 6g#(9 2=ѫaE26L/ e)@ P(@ 0 `>t LHe#Ff^8d$LǻcsL\܌ <#aipBXcñ>HI%\Ejv: NB :1 ;̓Xڀ.m KW< kb28Q)@ P(@* q;_*n.hS3@ >Cz0f,CW˰Y/D_bmၱXm%&z;lPhI 앵:=l j ;ːVPV(@ P@ 0 Ѕئ.ʘuޑʓR]woo/I`/| K[0y(@ P(@MlxDtE@yXu P(@ PhC p(@ P,,;0FR(@ P p3 `ÝǪS(@ Pcg@=>K P(@ P{ 0 `π wN P(@ P X)c,(@ P(@- ?6y:(@ P(@ 0 `0x(@ P6 p(@ P,,;0FR(@ P p3 `ÝǪS(@ Pc֭[\1cSexȑ#m)1x-cЖz+ٯ*A[-ѳ]8vC@+ lAK p ZX> ǀ8cpq(|$0 `@;U(@ P}*0ٰk´at̠ulxt0 `cDà~6<"0OYsD<-f&1hm=1Z1hm=cc0ݿ?Sxw F?%6y:(@ P:!~xZ#: .=;qA/%-фˀ[sD<-f&1hm=1Z1hm=ҽwpd! x~Gv  s=x6mJ3 `ÝǪS(@ XC!Cf߼? BcpooYb~m 0 `[Q[yVi@Z0C3a :W :SV=!f p 'ߛ[c8:Ӹ -F{;,f,ɸ9Ӷ#NMc)@ P+1CΦҮQLY[A m0 ` p(@ P ۮ|̐its^fL(a:Lf=0 ``DA3fo5 XMWt" t܌OP(@c=7TYMhh]<٬e1s P>d@<Ljy\[1y8\ 1(,^>[_߅1h1nB`~gcQfKAkNցN1 P(I'K{dV's0cMGn-][sl^ByǕ.1hSGc۶mÈ#0d 669pIFl߈)+1h^_K~2~ot5Z-|&V"r-ٺlx4X: p%HRdgg_O_~%z-<z*8yO>o;;;l߾Æ  (@ P} W;XҰQ>7LN P`@bV_{={l.`y@@a?̙3C"((Ȋ4[ˆUvK`nl,ǠUv*x3GF'%X?Y3O $A>`@;+iii5j}Azz:?s<0|y8tvBee% ^zI@=j[^e i\\\id2|Ti|PqeFϭ[ꊪ|Tiݻ^znnn&K#Y_YB}3!a>iz!_i!qttDMM]444 7oޔTi|:+KP/!PB:!>4B/<ݻrWS_pWzY˜ƑF3K4˜ƞ0N*al cBva SޯM>e ۷o;'Y_ W|TiæJn,S|^|^c_s0gJg>/ /k?>Sq&|^>LyqqY=3C"ܻ=sAG˼i  xK^eC?/:7/0 }y!/wп/Ly)h#>/v1|^ ޗߏ|Oٚ=|gJ= 4>}|!qW_W_O/^(RݻwW4h|!#LT'B&4.K;MNN"""puLD^P,& #<7V|iVw6`Uy*vP2Ui%[-7 pSa&Y)k(/C'FhRVi$=ңifQnj+X oG5s0s^\WWxw;73߹gF+, mNk#@|`7FȥታTLNqnPw?Bwjx,6OG|p)~)CBsm88͵qwAv|ᜏ1; >!i% uq|GՂ 0fDDD&*8i$J1uTqHHZ }?p~܈YlLHH7h*h* Vh<oDc@X9ZE/j0jSWZSN..Riˠ/"p×q8Sfگl4t\su]:zt]g-K.OFs5hf+c ! e  51B :)T1!#/mpx C(@4t, N$@{6_oóLoIHH@M4ԤrloփgƮQP C#pE`@eZak߮ı;}Je /Z֠_}˛ g1^(G4 ֬XC0 j^Y7|t տOU&  @ @C@U!Tbql"6_ہSPT0>`o#㝢N BxZ^x]Bؑ:ԛ#e rA :s ;k%v~lAIe B"oÃdgX LHZ!=!C[9$  cbXs <QX;!Q/@iTEԠܫ8 ϼ;-OppӴሿdF^!y+Ԡ𸩮U'CcƠ}p[?zz-_%IHcG=VCUHTtZ0. :.W / ɿك&-y{8|+J轂7ݪ=qmƿj:?&΃֞=g @C@u[Ko??lFc&XK k{]{t 0ea@-K$@$8h^1Y i#QL!3tuSԩVRHC0CC>ְ5y5TQc]?f 4#Clxu{ ̈́HH+BgW$@$@$'4zjlX?{?܌0ԥdvృiLC7=m=%`z5\\!)Hn΃^ @ z"C ]I:I 1?Ah(;?eăv oб$: xL`l|4n{.q<   .ڭMyfX9#(Ń1X @xu t[LT07K'LY潈PAu<غ뫇Qzsޠ{f"ܺqa.ڃx%vB}nIH@CKy~(   U P=1W ny3G(X;zXCe P-3b 8VM:adhAJAW _VO TĄ|dmDo*NO߱j* Ɛ$@$mƼ5\!21;   ^ J>| cnDOk+<=A-{ l*>2*9w%΃]-B/eӪdOZʳT @4T*Ò .58ӡ5"_&I$@$@$>^1ZE t.O x `_Mo5v8v0zjP U` @CUꃆJ`)t^ IDATH@K`o=8l  4t\ko?xm0";"S(7 puKW%t[:'qW8PHK~kZ2[K%9hC2v7Oyh0C5\rF$ |u9:ɑG/Qs KG!`=q`ZI_`a; E?oyQ! U-وK59Sxd;Wc4# Z%507o!M>C`>~ :c/bu2&ךkH <:է5=jSS4##   -7 cz/,{aI P[ S/k_M=-ɓl~LZwz 0t3׃-I[xU[$atxF Q-M$F L5;PjPab^&@C@}#C3_Xg<($@4|YCE$X̳3к!XUhIHH p q=3b6בQa$ujRbM/7WS5;PjP Ĩ)fJ j4Lhx/ydXK0=n vJ&@/>Cd_$@^ 'x!:!~8].#ww/u:aT   PHBPZl!pv/O؋SxˀSqkQ큕5~UݲꂫH-?.LiN^<K zΐ<#@ zƏ[{N!# }I2K /8 g Ba XLeY9<ϑ ~4t\c XQYy|uLW%/)%ף:X{+#{4-A@f)]+4|EZ~<3%8 6 ٸӨxm$%Ea͍ :5 uGGX5sKS[R. @<j0@ç!bq: *EipW>HHHZ :$ uGGX5sW.U5G-Apf/AAW _V*@eH ]p|IHH|D@ 5tʨ PY9عz"hPؒ59CF5?n4S&q: *W1. U2( @+#@C@!NÕQrn)o=~nԀ p{ >j0%0h4t\kwnVGtǞ=h]S'jT ug_5}Ԡ츥wP('@C@5!66.Z… 7o Μ9'ko5j֯_n 1eN$L$peLjz̋HHH4CfJz"0-Z8TUU!22R|Fŋ۷OGee%1rHqL={CTT׿5?j?=z4j# /_N:7t8PGׯ~+:nu>ΝCXXlقaÆ}ɵ9r87𬅈F.]PSS 1'!7!Go!yYiFeE`K͉'DmӰrQ+ jsuVAB-q8ѣGU:>%[>v1tMܯզ{w{V: =; Dt|o@铸}G\| jkş6m:˗ŕE!Smv'NF .mF*Dv┣kNm."chP:43_,LЛ0xk4/Rs͛Cye.P2xk.PDZ s0 ܤd.V%Ko)|!ey&44T qY6x}6 A<9pws0_KMq,B1ܱp<8Np#5c%sAKm>cqBX[s8rsjB1\ bco_G/?![#OЯ_?$C8hN$D.T狷 ,_sv؁xGl#|!? FpF^8 ;m0vYj\|5+6B k GKm;9_8zk#Sr}OI q6sTu ӗ='˰\I_Jڴ4_FKmJ&ʖ榖-\ 0i) o@Km|+ x "} ұڴ :[Mjc~O:./ ?wuWJՄ<6m& VxIDZ0N*^ˇR7O-kMq/Qus(gZo|dZ`&`O L5;PjPab^&@C@}Ο0ί 7nܻwo$''WYe> 蟀_KHHHhX6t aPkwV5HCahYQMwؿiт!°H@^Cj? ׈ R4`;:\U9-ko/_UWӌ Ŗ"e $u56:n%Ԡ@2 |!1u)!q1i6 hX4)au2rZ usK-kP\rԠrVljP=4WP ID`V p2 ?kޞ:\U9-kIǑ51wީ|Pl+Z֠@2Y Pn^"@ z $h ͗H>A:.S'xyRu@t: M$@$@$@$ !cPxtʨ hYCU> -kPw0[Aq#/4M|r4t\88s&@ R&@ Wh P*C 8^4mZN ( @C@$6!NeÕQx.c!:zl#2\a7uqΗ86"@ R&@ Wh P*C τWpg]?gIHHHZ3:. uGGX3ee' \A?φAAW _V*@eH3`$@$@$@$@h4)au22Yc_ك?o*ۀH@Uʐ.]*Uʐ$@C@eQ e؊DDzOOEc$@$@$@$S4tZ8!m:\Uj⥢6`+PA2KAp A2& dY%EC@'"= ca緞S̕HHHH@hhiS<:peTeCJ%ozClE*<Tt5.6V5T$,h(V$#L{+R0I$@$@$@:'@C@!NÕQ[&Opfnm[6p@B-"˸J PJIZA2ZE\ȇ ؔt@Mq/qN3t1S$   3: uGGX2!elA*<XULT U"@ a5GJBjPjQ +BZ70}%vJ$@$@$@$@Z!@C@+p#n@S atD/u*V65؊ӡP:-\+JlEP%@C@!1uЋ!8HHHH qiS<:pBᅦA΁ kaԠ9P]@= WBWp<~Ɂ VJ%ȃlBGX$6 WU0w9a͎li&1j0`JفR-M$F L~4t,:.S !q1d_r`$@$@$@$@$4R 7!4VIMbP:X+JlEPAM br(!cq_̚ KHHHHBV*F4܀`: ɫOCx$wBQP-M$F L5;PjP Ĩ)u%@C@c~!0WNIHHHH@Khh.BCE` VJbP:W+KlepAL r8hX4t\V6ώ!t B@saB$@$@$@$@~ @Cн% ol\W=۵ńg{?#R^PnƍH"Lr56nC4tX4G4t\V%pR&*Mܘ _r_4 :\U9jP9+T5WFUNTΊ-!@ ÕQGj8#Q! : uGGX5[CT+*'@ *gŖʨ#@C@{5Q ŨؐHHHHHH K:ţ#WFUNTΊ-!@ ÕQbKuPpeT!(ΈbTlH$@$@$@$@$@$D%AC@V+*'@ *gŖʨ PY:Au2^MgDC@1*6$     p"@C@ǒ!NÕQbKuPpeTARԠ:\U{hh&3! 8!cIPxtʨ PY:Au2rԠrVljP=4WP IuZX8 }@ v̚HH$б,hS<9Gzl/c N׌J"&<;?^}6@}c`tg"4œZ;GggB M:&8P]c8R*hz l;cح4 |oBG[{¥Zw*H@4T꫐4!ذrzۿ錴1}iQ5xF ؖ"GN\MѨCYgOGm5_,OPugT^!P]wѧY>+U 4]w9oɃ| |CggX0o gUz! V'"g-NC]?v]d1Q>q8`8R\"'HN-U`فH _f~w 318e8f=?$_4[;PxrڋiCQ`7iw5?8Ui lYT6l+D@X"xdg U>6=quXwN>2)qjp`ٓ& klϲGWjր}Co8ҋ *8vǣm۶U Aih aہn6v0nh8n"I|nE-Br*_Y&կ{Wg>X0tVD5Bnȟv֞; xH? ˗/#''Э[7YO$^+A< 7aY 572vyik>ŶH&>2w6ܟ(T"E+jo^ ߼m㱑j!T"˰J 8kwv x|@֞kqqo>ME)S&ڬiV^z mڴѣG' 55Uwk+>*1]`Sw>]xۀKתr;[mp>(UUWw9:lgr:O{c4t_ׯG>}DJ]v>q}L0A4[Ǐ׽!P\\+5 ?9 mxUV%>8; m~8vb1W ]ok+ڠ Ws>Wjϼ!cEXt)n&5\.]?p? IDAT:u 6m˜1c]PP\D: L൬gXE قHc>q '@C@!  <#?.8q":*c=뎀s,t7&[ԠnKj׺-jXs ۋx4@֘8 dzS!1N$@$@$@$@$@$ :  XI+ ^ Cɧn븼LHH qixLHtL;;{נ>}#yxtLHH ךIH58G صg[((N\JDb(m߉rcޘiE37b~HHH|I/i{/^p$@$@.GX`מk16Ǯ? qcc6M > 0ō@43g0v=(.gc   O q{~ϮIH`!q50XL؃߆_ؓ=XG4tq,~x<"Wam|eE$@$@$@C /e8  4g'k3vbEeaXԜ:_w nKo%lL$@$@!)A?nOC5 2`@#C9 w{ D86c:{ʊHH|I/i{/^p$@$@.qSU`T {h-b$L^W/a3ޟ5a%lL$@$@!1B!?HH VXa !HHHu4\g-hhLHHHHHtGJV0  s<ヒ<\pT- D 885jK۱r4\K_3!     )yFICнե/ o8$@$@$@$@$@$2"PIhh,LHHHHHhxS(U= N+3G^p'M!=pFDѱ1b(F2 B/˧ە0+8ضT5 r.ZV.u-ysu_pu\jz\xo\iԤY+PJkƛ)31(FӜg gW%(Ln oRǦUr]Ej.V]՞yz}[+& H8jqԶqp{tU3j3&='|6oDz![# 9.vU6KqY\S;l"e#0oV!A;b2/y4Ya/(JBŖ,,ƈwf"̀)`$3;&#Bڊ-Kz v2Vi8+rLlJBH]_VX̉Xs &f7J,xnHA^AwmP% #l#mӑ-`"}_O<6 CUMXk/?5ǰ=ɑldjj35tV2i/e ր̐S^îjҵ}AeɾhF&A._!iBr>1Phs"c٫J Ȥ,91٘Iyg"Ftx7f?L^[VJ 9ek/Sg&2#}+ е`kmO8mt?fAw>˹GqMT/.̏FHעXdD۵a*ߟMG ?R5;&hVde_߽6ELMEJ 79rTi u];mo&A^ڨ=? \n7$9-1k/=_Zk=e j$Uf\W6&ZzlU/Cঐӈop4ҊXgacQ ,x5xlBo<sb:,=khd.zI#.  7fO#{`m#]9 9aSsc(łi L]}T+;i4'~X& $I-"}*'֚j5(0gC1C*t&H'3~䞤墳=I8x}_ˤjWsx1A0'S 1Hv#d [։hI"O~5#C/\жܸlg}=ضQ\-Ly)¿ ŒI3 1yH`3C %k#bPl홍Q1^zF72aT#*6V.KF\l= Vꝉayi鐆t,tw3 f%Ғk'Ked{Kd|^@j\1Syi>!y;jFH$$FtK"$Oͦɋ6bds9$\U{, N:Imq# tHF3IX$>TIZV=,s#ao<1v:R O1*G.3Dm;s{dzI9]r}VuU/B閦&#.r^2 ΖDIwmk>&ݯZ2CI.032t5O54θw .φhVȺ0&Qd"⿍]aF ~UIr2ʀn0WoM}@Zç- ŭj:V}YE&`SR_$auQ&`LÊ>49!e? ć//A1aZ ʂype37*֭ax2Bw,p``cQ*7Lǂ>sWd3D*o&;Aq?lMl!+\,yW [E5z=MR5} ~23lLi F~srlGDňԒFl!S;3ևMR\WZ3hr?"mˍVfu=.υҚIRI PV^uqt )E֛9(z&Q`HxBX-` P4-;PcȌ{_`z  Cs26.-fe&v cA #طax`d?H:%'-Hx ;E0v0t0ȟ4 5(1ʊ,خmƄej"O:IERhjN̝l;њAlFhc3Lo|ۆvqD>k-YSI ]Ђ ӬP䊿pno\Ff6%JkF^BM]Zrֆi=q7}kHk&0螙(\v_[l_xq!q',zH\bAx"qԂŹ V+A(^42VEdq-ů嵡uOI+p|7ub7J~o,qqՒ`S( ,1R8qkpR+\`l4YBE ceoXw,uKl˧%46 [F"ɰ 408R6[ֆyI6GN3Bj9if6ݧDsLrqu\npE (dp\R +ƻ5Ej$ZN&s{_jU >SGDU=. Dc[  A8Ю_+H{]<$p:л(q:w!)fUR |G161 f`ظ,YgsPv2VW&H3D2!0 Wu]4bM(2Bguwr7ɿs$I̿Q?.*5C HϒQF{G`t<'^qts:v؛7 ђ~5ӌ&]\vǵ073\[! N<'Ě6|1irW.^f,/^`A|pO+[.Q5y J[&  %6śbF>(՟ԭFx<ek8id!O!4,ᖦX:<,Up8FG {J\>'=DŽܰ ۮ\Gϲ]}w! Q!#Kʜ  :ֈwC|u;2'- ho3pzhzd6e`xk@ rfV[w֨|uF>3FO[(8_g!W193bD w>LEin"\|}! x[l>]'I/\#~o;vFahR#.`: 6 '-;Ec)M&2dޔc+"%vuC7`mq߈ჷl 9TgW!ЌfUb$M4NWmuxdAk`>z@MW>"?F?|kWW"Xjl' n5_=w|)$Hqb|^?|5^}E F7޶ܷDΠ^W >[ٱd.݈^!8dYTmua$ZRca,V#[_m򵐨i7y>r&k.OImqɭWfr06G.Zn7ʌ׼Efޓю$ kbgqGgƻhxgh4czdev{aGF.]uZޢ!hD#Pq2 rpCmrZG`9aIlUal,Ftnd<0n@K}}Pv:Yc7]׆$}" d%0g5N.$l͐_P2NKK78tM{DLE|B:aiJ(Uf״4pRۇوj/+.%aRleM4`X-e#dT kFNf/ EڐֶܸXb2 %eٳO>ӱє G5㪑0-Ds|t*9Hy)/Y4یÓAH#%]r(>t>9lN^Z(dZb_Qap0Ä0?, N~8ʹշ ؖ7+$B;;s[ɾRˉ@Y}Jn\s-rI-1c1l!GZnБ.hMtI S3O54mKwyPO#{jl{c.3WeNHHhN+3x< =NZoO8U(e[jlH|3M6G#'v% =.b. 6Uu]ըFc {B}MWCZy2{M $[![Z2fӂ& "\ֶ*oz sap.7i^6Wjѡr?:T "UZ [Z4-J=B|śmAl=֣Xx _HAwNjXj!R{ sޚ'>%~N"+9r6^* qI.OC"KSeP cd(ݲ/q=J /SRmOюsX=wdw#uV.\ r Lס-YH} &wf"d5% dFpM4"\2zƒ졛+X|t/f$@$@$@$@$Dz![#{fGnLwvG&Xv|N_nj}{+pm8BQ+YhBHO?FLb'-^8hi!{UR L—&OWe$bE/"w`=sb*, C,łie3T,(B !kԅwKljQ(XwT !of.gv@$@$@$@$@h' U=8+\5}PbXф";^KlaUlM˲2xw58b'3 "2 IDAT^ Sp6U$ 1PhCe@tJk +22D$.,Y cX-L\Q‰y"0ȥ͎+QɌ,l5i.rBqU`ߦxlQ&+ Uš4l,JA:3%1. y>,0ŎW7 uNHHHHtOz%![#{f(KOꡂk+ !nVYF0'@cDw_ 0ǵDS`3L*hm5n1(1ʊȕ`zƮr1` W"    !=ΑhV045_K6"fbGu~ۜ@\op}r>BF! 3y0 q@ ~G2\zˀ\L_ɽ/4!9EۇښöNވ1V?\q[@㳑Rb Xl,Ftnd<0n@K]޶"V u +"fxhb6GŠxy:X2qmJQ`7?ljANa66u!LtTs܊}3" 4O+S ت_Nbh+Mx meNuK[Qn2(@̸y7vybOcRDZ\H B$9=C@ւq?iL5͎l Er{k \kq}feLjw7[xhTՈ2z.+b%ؓ+Y@X}7glYR{Hɛ(L(jNAE5P\!FI$@$@>!@C'uCJQ=vW[3P''q3"պڧ QXWwa|o+JI3{ ص'g@ٚ10*>m,l:0=z, qĮ-#pEH7Ƶmۭ6bfk# 3k\RCwDqelڈIJ->jl/WzkrXC#Pr-&kF0 wB%*_+08 -$@$@%@C@Q놀W+޺^I8Rc\M(\-džy&^1Ӧx) +FJᘱt5RbmE%1m #ՙvxboOb Qw,l-)y>?;,Xv83FFkŸ-@S!)"ʽxXd/-n6\0vXv;e0^ǞGV0'ؼJX9\;n?e‘I+PA|IbLma~,>{6`ڤ 7}$f:bfF036Jl΄0|,}ql`pX˱!^ Æ0CpgX9/(1xBt^O4r,C(B؈aeUl ɶ߉D?:>Bu{91~RLIcgu_ yYvmӺs/ ֢:8cjiCzb$L*{͸|3&#F*uA?g6nܷ/T)YK1>wsZ*߳^kia6]g`"9NdA'lDxpU)xI k,f ,XeJnh-PPSk V~!54KtV } դV_Yu {|ν/p.=syc>|8E˙kct0k}'m5H֢p̞ 7oDo*mܒ&qmLtN),;FS^u׎hݘsi`aCڗ:);uTY9!O_Wץ#  #UCe. ar剉a R3Rxc,bc|J԰- i/NA;98!{bSX4H7D ʊW#%1r3; {-{ :ɏ`I۰Qrj *A IܪEts{z"wgcaL 2W[B a&obz\$Bf!;dvۏG6aӕX^y̝!K^،\d'G,d6W#r_:R^S&tyV!D (edfG<6ME &#BD6W#M|ԭ|o\rSLǍTKE }H]s ˿Hp4ޮyy@n\ď~$l޹4-Q2yXs rV YYh،yÜi% meM|sU.>R1)Z8>KCn9I@LbP,86 fY1g[>!ͫwu .79F#6ZQzaQdaN`HlyQT=B!p\֪b2d;=7 c8V"g#vYBCwziT"75 &;RQf<^AX#V~L5&ۄuH?*CŒ,RsO4_ۈOUw4ĕH|:-/C"=j=J7qUJ4M HsxD4JR0#:5b1v%Y9(LE:V*L{~: pz58ƈi\'"ei.bVBQ\\z$VNHCΈ i+ ^ڌuv1gy- 01a Z {3 XT^HK_{ۑ0oSKppV>y9HGac ؞x&XQ@'nFcp7+RL 9C[< b(a\"I+A̭\Y{1. C`6UuGJF\j(p/Zt*3HHB@@Ӿg(} JOĔ{3b9 |y,20rd☴TƣM1;׀4y"˳Q}S"gϛ E6j8v8 8e5#/†6ďZm8&ib2yjr71#ak:ш|e5Hmc29]wňڰ Oe9~i3'"[HDWqwQL7b| TSW nExgSLBsYcuK;J'IFJ7SǤW+D}6bכ~BW#oRBW#=/ 4HC]K}zv].V'9ŏ.+FNFI uKf[w!mX) 4E":"jjP#l 0qiuĻ+b&;NCٍ\&4n! &+=- 8.pIA !઩ڂd.+SXݾV4EUl-ea();=N]uNTN"nblb]F2v.&\cVnFxXBfGd!eľ}s#zo2 (G%ۖEŘn 6y# 7Dv+XplΧ׹rw7VqtzvBdd!KcTuH%@z=GzbF\Mz~%A1>uO|ΚwxG4iX+^@ueEʦ԰צ!DV|?Em1!FZԎ1vC#-Qv cb,3&fHSnůujCXqMt8^-S6qtP]}yk+L|IH$4L,Նl(Y4oG BL ؚPx6UZY}wSQHs[=wS'Se,܋׆˱6v'=w7oׄ n?e hXEnG($ q$y" n\]OJMA͖$8U!-,޹w,lGJ0u&YjaCM潫0& 禡dLj:yxzc O"kV&$/W`Ӏ*]`dю]!MlkZďkBkQ۹j=Qgdl޳ cÛQljDcO֔!=sDž8 E]#!03 &zVNJ:ۂs|ų8"wGX: Ǯ>Qv!Gi/m9±*3YAecR좔u=^p|3C,b>$u.d:=M\$CܠB"aFȏ8Ƿ;^/~6I,c}Zz@ Z_ay~l.XgEz v9^Ș'LPGlYr2ºs7djF+_"yV.MdUyTms{e@Ze#e`b ]E"Q=|*wt]:X݂G@nU- 7[Ppw2u h8&o:w181#˄PZƖ;iX^A8&EI:79_HHHh,ʒ!j !|ܔ=%hrW ޺ znȶE[@>/GPb{ηSK}@ΕB;8dE8{Gq ΚbZl͆i_I6Du޽zuXWrx+b&Y׽G;.fJIw.ܡkz`O#۵9b!9v'tYNR]$=\bhBYADL '|Kl_EO6&R=jaԗ:uѱWK>*!  _!K~nK! Wյ q m@z$|}#ǒau>eֱ+[-#bXT6K~Wf ը[{#6S7R'Yn |SB$@$ 4ElHHHHHHxh&3!IHHHHH!`bI0q:  @K4$@$@$@$@$@$п 0qixLHHHHHL@ {ZX     h4L\;cΜ9p477Kt9{,va@;wK̷m|ᇸˤ/.1~) ٘6\tEx7pwK1!oFOvc0`٘K/6 Kɓ'%E=FK}8qBNo1 BHHZ[[{ӧO#44K̿o)W/-zՎa0p pYl_1lhXg̙08p g:03$ _}mahsA(lv ӎ! :7v\0(|BDcM7v9c0>$ 1BC%Yoδv <v,B!sv|چ!~ˠ1MʘumgP@|-b>"^n~9^8ƯEȀPxS.J m<9BxcKߧX||L;.Sj=/<eA~*،: c0كnIt hAAL6X%%%{ɞ`IzJ2Ĥ[1qfAo}byI\"F ¸1DGsw/ #&b.SET#&^xa3コqCpY1I3 &g::,!"!HPrv 8,BCB䳘PioǠ[0KI|9 ݜiGhh4y U Iqƈɀ0D;^c;v: 2l$s]ML{T1s3hbLt^箾4F/Ę"L+_$hES)I'cJ5Ʃ/qXC9~qGf,16y/xS0\0@Ǹα1Eѽ %){ߏ=Spb_20~j[zw^|+&S7.S4V=c6`ΙmqW^*[6[<<İyzKF4L\I菆%IHH ..OD@[8#AGH蟹PF:kjH蟹P\!hS"C?4t&IHHHBaZ$`b4L\<>\٪rԠrVԇ5W5բO$5Wj<4WP$@$@$@$`z\!`Hph$!H:jh1VԠT٦ԠZՃ5(OjokX4_S44$@$@$@$`*4LU.&K @CeO>ţ#W5#!@ Õ*'@ ʳa9r˛DZpUwuG %[26ƮO0q: $@C@%0 x%@C+"Ч6tVb>A}UAyV4kHk$5 7 fL4L\Õ*'@ *gH}Ppe P\mʵHjx0[!`1u   PIJ` 'JWD !OmÕ*'@ *gH}Ppe Ph(אHjP+Ao4R)a}UAjPlU9jP #sDԠJ 4L\E&.S'   hp hxEdԆ>\٪rԠrVԇ5W5(ϊr if!@C,ɓIHHH@%*1H+^7>#W5#!@ Õ*'@ ʳ!\CZ#AyY0Kd!`1u   PIJ` 'JWD !OmÕ*'@ *gH}Ppe Ph(אHjP+Ao4R)\٪rԠ<+55JǛ TJ&O&.S'   hp hxEdԆ>\٪rԠrVԇ5W5(ϊr if!@C,ɓIHHH@%*1H+^7>#W5#!@ Õ*'@ ʳ!\CZ#AyY0Kd!`1u   PIJ` 'JWD !OmÕ*'@ *gH}Ppe Ph(אHjP+Ao4R)\٪rԠ<+55JǛ TJ&O&.S'   hp hxEdԆ>\٪rԠrVԇ5W5(ϊr if!@C,@ZZ7CgŜE8z-vm&IHHH 8:,HHh*sd6`Q |cFbXxZDjl] xLgaBd P>GUUc PHi\jǚ= ԻCRړ#xmjqt; M$@$@$@$4|M t!@CĂbvcU)1M%x yL+ĒRH|e/ I B4A/HR ICǚ= ԻO 7flj FcaU1bOC@SQx0 DN`, c4L\|_#8<S7韨[G#|ćOC/ I B4A/HRB R]/4EZ~|a3ͳ7#ZA}UAyV.CV% BcL %8A% hzv}~&S'  b?MW]b3 Cp @Pդo ;6d[iyWV&I_I_#/OAj#@WS!5B Ϙ= cԡOYߎ;rb-‘E+qb韆@HHHH@g\!3`6O ',8<E㫑0ӂT1Z*\]{~ ?3'.~ a_mh!@ jc}AE5ZcA_Pdf @C U/ ۑ̬@հEzn*hbI0u   &A\\  E0`oDzB n_;u#K=o7# u -A-x/P6m*;>?MW]nrвaЅ ]QUc0E$SARH5hIԠ|!iOԠX!XzdqW#2C$ h& nhX0lG 0j ʗwWúoJ#I+ùB`&&&.^N I!M| WZ`+EHЏ/ ׭5xxHd{ *:M f\!!  рO GظG4++@gb|*aZk`Vs)aބoAHHjCIljE;=Ѯ8V!W(x GL[XihGPV@Z|D#t//*G\Uq-k#QO1_}.b)F\6 \ptm/,, E"[ r9)1xNJb~/RO $7|S:^q8//\c/ q'ㅷ@)} x؈+Rx!7ŋcwaԅg;S}>^t0K?/^Eo p/*++S}ẗXD Q~#@C@?"yb) +{$YU(q|@wC@؉wq /n` ^!p7'1ɺ+Šd\7"} gbLƂDq/W!ȻbM~wc9@} Dn&vMɾkЛ!$FXp3OŸ&.4컌 %1rƂ\|>wcL ɾXpn>OEr9_ˈT:up`.#c5P:up}S|9^ 1Ece */b|9^tOrx2/\κC;Z?=d޲2鏂6#Θ?rG|Ԡ?rWjS` ,Mk1pqP^.Y4ay̩*G|/8DLw3gJ˙J1+W^bZL>s$@$@$@$g钀ddEK`]; c",}r3tF>V'Ji{9˝]:}c|I%M`__izokX4_S 6f!3j^"t<HHHH@1Q1H@! A1L!vRbC$ܿ?t5ؿmijX5(_oAfO%@C 5KCS"TL$@$@$@:!X6K >ţ#W5#!@ Õ*'@ ʳ!\CZ#AyY0Kd!`1u   PIJ` 'JWD !OmÕ*'@ *gH}Ppe Ph(אHjP+Ao4R)\٪rԠ<+55JǛ TJ&O&.S'   hp hxEdԆ>\٪rԠrVԇ5W5(ϊr if!@C,ɓIHHH@%*1H+^7>#W5#!@ Õ*'@ ʳ!\CZ#AyY0Kd!`1u   PIJ` 'JWD !OmÕ*'@ *gH}Ppe Ph(אHjP+Ao4R)A}UAyV4kHk$5 7 fL4L\ K_0LKiKЧxtVb>A}UAs&CT~2h~4L\Ї+[UNTΊVYP!ԠV<,hR2y0q: $@C@%0 x%@C+"Ч6tVb>A}UAyV4kHk$5 7 fL4L\A}UAyV4kHk$5 7 fL4L\A}UAyV4kHk$5 7 fL4L\A}UAyV4kHk$5 7 fL4L\A}UAyV4kHk$5 7 fL4L\L$@$@$@"@CTb$` 4LQ&$iS<:pe PY1RԠ>\٪rԠ<+55JǛ TJ&O&.S'   nvrJwƢ UIA@ O>٭:pe PY1RԠ>\٪rԠ<+[<<5ze 0qhxLHHHT!I!qhS:pe PY1RԠ>\٪r W(W>Ԡ>\٪0^MgDC@1* pK !`(OrVj"X=PzPejPjh1VԠg=:]y1 ^SC@>L!@C }) pd+$`@HieAH%) 3U 0~b/X蟊Ћڥ!.NEIaD,z={aų(C'] :J*" ԇHd$JELPN$=[ucu5."lxD.Ę\MC[O5cH$-xV`"鞭 [#}0'#&sJZC<ߟ>a@eϰ5`!@C?u酆.X=7zϖ !g΍ɿbxNƨ ?hE[Ü~\]'V>>c>ۏD̹+& i|fh-1 }!N9adC%@C }iu7 }Uw4N)^=dǨp>j# Za;y 9⍰~E-wɿmG駷KxV3O1aq8p 0>ɦTXs86 Me<$&t piߊ?m#~7Ј+"`G}sύi(σ:l`C8; S %x{/6H$@C}W ggŝwމQFaΝ馛p7,W9{JǓw^BVLFF w)t`a+*5 FI-$@$@$@$@$@$@!@C7 `g$@$ ؛qa,ahѣe8ns/ &  4"k V} f|"¾-cuQώ @!@Cĵ!`1u lGL jR_ekB-h _l@ 8>UhB#\|cIHHHh!clHH@{ۑ0##H(G=G03V bJ-^(mAVB lr0Tvd#* &  hh%i>& v$r3޳bAe&n@x߳"aKꞘ, Ng!"ь*ĒeE$@$@$@C} es$@$@fX PEC޵ U+y0pw*ހ* &  hh%i>& @WVt1H2JYa 1*3@Y ? 'mEC@ *%H5{gEFe 'IoucP% "f` 3HH4!!       !`1c      LflH PJJJ5Ձ@jjVYUL]z ovH?!Пs'›o9`g׿U;9h=yC s/Vx0^zQ%B1/8WI$@rhP$@% Qn(З@ןcy4WfD$_4˛ @'Fׂ(@C@KyYz3^h&̈Hh7{#!arQi; /FыY 8=DC@lHLhZ̕H  6F,xE/4W~HJQ+üH!NP"5(腆O IIDAT i  ׈ x @C@48A(腆 i  ׈ !p4Nw9G 䗺s Qi; eGvdh>_PoFы? ?۷oǻヒ6`޼yhhh@TTbccZU2PHhLHoc :a^оaQu ʿ/|gX,T {v]9{,Ν;E碕N 5^\ñ;z<{Ϥ4ʑ\ ;2݆dcWva[!^k}9]o~ڊ7x'3`ܸq]Nv [czK-xm?OWcʤx;/͖_mÔh+oJR*\ &B4<%/FDZgqp~C]_k֬}pW_5AqO/Kс,Z ÇWt.Z)A_ckq$fI}~% 8I HkG0o4djo~0鷫W1bĈ>+'|Yf!%%/ƀ|$@oMX~J,*Ƙ"az$f` .ַS&QFť`XxC g<߮C폟+b{|^$@AN@GL  <>'bh d895Y>7}јxBjA426A}uBkVC?ǒ%K`$#̙3/~J8p@C!p_(YFcKhb=[vG%џY?C2^!@'|ܹs%JF\_>06oތ4 P[[+o#V 6Z#OQ;(KFӆIv=wXs`݆x9*r W zl AW 苂x @0!L乐@?# @kR{{sЈ?O'^d'7j*p6hiiX&T/233ɋHma0ƧQ< LZ16 9)ߋa0f^6b_x衇ַ%=|˖-W_ rz0\+DLd|0{7G7"yF~0^:K#ףd4o0Vv9h0VEk?D!@%7nT?&("i9##~t;ωzCo1ԟ!=鵀"~7z{q-=kh(n\לX)0j(%Muz{Go>EEEӟ`V9v$*cZ% ԥbG:KGB"j g}VjTغ#/Uu` @x<H _~%<&-&-R|R0zt&)kL^[[0^K^V[`_}{I/7j/Rxǰ{l{b\<_DZ[DoGqbG{c/[wN4Iҵ?-[_ǎ;#5'OSgvl;Dž!Є4-gָg9Rv%fK42f:&amGo6w'N .ĕW^ejjxD?9|M/+mn+ ]\>%8ݸlR,`|!;%PdwC@\#8*買/TכX 6뮻 U /t E hzZPŇ`s鈲*0qf&VlĜ.g4!nQ3j+f0}᭦% -hhcIJ@ !h,~ /* 'ɒ㴴"WB7brAAtj4䯔fÕ32 n^a&?_dl;>C/2p@r<^l*([|椰LP2IuoL_;ݴo9l+`  K&FH_Tsv @͎TUDm;'o+@_x'cIH@ ZX  /n8݈9Y(?.E@|sXAxK}/=^[U;uBpUxqTEO)׮]Çw~]mSOӆ7[Q^-b0 fƍG>ˊKĻҕQbMЃ*$0fs%BvApQi; /F !/ !`0/ hxE5z 0 ?mgE(z!@C_g?$@'@C5b$@. NP14 ?mgE(z!@C_g?$@'@C5b$@4t'("`!/ͳ  ^nIENDB`google-certificate-transparency-go-2308f62/trillian/docs/images/PrometheusTargets.png000066400000000000000000003471051462611535200310610ustar00rootroot00000000000000PNG  IHDRh< IDATx^TWwRE Mwc%~bKLbXcGM`]Q-lX X@~gfeT=2sl{Q(@ P(@ P $ h(@ P(@Q'(@ P(@<"G:ՠ(@ P(s(@ P@`@#jP(@ P`@9@ P(@ P 0呎`5(@ P(@ 0(@ P(GHG(@ PxP(@ P# hy#X P(@ P h<(@ P(@ <(@ P(@4(@ P@unEk3d="443y5,*B1=xOR9`V&v޳3XyV11*Txca Pťyl;x,1C Tqŭq5$z8i W [WCuSY]y] Obere$-a-8Fl]^ֺ"؄ݷߡ;F@3F!=o T+%s>]ׂ &O볟H|KV(Կ'$|!sǒ3 ރ-Ѵb1D ݼUhRZ*‚qb~< V|~7\rdN (:|xp]S}.߰ :53#XVTc~*bΰ40GRD\ڃ)x _i Tr|=ڋpE%%x|;]Znyx#NWN1(ukDLl{J:O8 wGydV &}`њR J+l"Ȏh ?yӪuQP 1Wu8XzC1SE|z퉿@G҂pݪ΁8Psm^%ojm)m⇕KOq<'{ocDBq6ж;GnPڹ ,4V >rQnkp'DyA4GKʼn(x?VU۶MG{]}^O#6: Ċ5) t%sf)R#m1rBg*K=[WX/~*GH9؊_b.bɎU_5 }' Z :DjEY ޼ P?ؼpN٥*d4*Dx'\[- gfТc+z88A)7n?Bi)WO{h0l Ip7D6luzmjR-Y X$0)d}H%J0^ՖCH~h_< 6DQR+NЯz}/N DN]QJn[{Eڊicpؿ+j U8I'mJO!zEjb¨<}O3(ihվ/p 6T ;8 Ѯ#ـUѮ]z`Pk(;`b@K7GW.}޸qc4iDks<<G;4^4bTP(P`><(@Q]ؖ ⊘#|I\^yw:zzέp[Yy _1CX*E];Ls]sl5D B(wc,{n崼f'e??;CGE=wJ\|-~jVq8 5Ǵ_~Ls(@ P _d9g៳Tv>Ʋi #l>\v-@UpML1ő&}cb߰/pcY)Cљ46ag@϶&T+" hJp*"!=`[5M[*A;8{/ĠD֨RLO.2J}&8z?{o KXl%1eЗ(}͋BL{Є/CNJ#KI_NawK AabPNb0O;Raݻp_9u4\w\Duߡ e[C.mh[/:=Djm1+Y }zrzPUyF%V7 +߳^=Ѥ ˁf %MT#f8jkAzVC~SL41t-+&߈Cw"0hĜ,޻b@[L\2!-7Yb@˗h?i )Mʑ}lI:189q(@ ,׏ԣ(K^^ 4aj2$.tnՊMw]" &/s(_/dJH)x_܎+H))GWΨhqfJ7Ll4Z:6E}zqwGwrU*O\r ѩ#0V& l :O؝ПʀvDg-*Ba+SsN7!u# gtn* #GmĶ(}('x8wT he<$E0Ǒy*}54+ߤ1}v)hW:BL90T9{|߉Z/bd~7's 2BH^99QzKshrJB4FЂs!7[2C PȇYhIx<Gk,v)yJG@0&MB1}ߪy%L {>TtڋR1/cɶ f&Liۻh-('sXŶ[U ߵX20R"+(s{"Z;'N@Na&h?xj%f(,~p AaI9M yzv;v[AY<AaVB<E0SLzm(@ P(8Yhʇke1*N% FkXOT,cWzVbYt`eR mc4MQ:w'o1+#5Wk ~pǕ|VyV7i3vGUffAY>(@ P^ MW-~F&u𫿕0Hƴ-110K0E 2q23>`90K ylqܯ` 6hQq`dj P@ z@˶` P(@ PL-uK P(@ \۷l(@ P(Y(@ P@`@+}˖Q(@ PL-uK P(@ \۷l(@ P(Y(@ P@`@+}˖Q(@ PL-uK P(@ \۷l(@ P(Y(@ P@`@+}˖Q(@ PL-uK P(@ \۷l(@ P($&&&|VgV(@ P@rٵl(@ P([(@ P@`@+]ˆQ(@ PM-K P(@ X۵l(@ P([(@ P@`@+]ˆQ(@ PM-K P(@ X۵l(@ P([(@ P@`@+]ˆQ(@ PM-K P(@ X۵l(@ P([(@ P@`@+]ˆQ(@ PM-K P(@ X۵l(@ P([(@ P@`@+]ˆQ(@ PM-K P(@ X۵l(@ P([(@ P@`@+]ˆQ(@ PM S-\y+ GL? Tw(@ P(2ma+o.cqnPItR\.$>Ump([FI<(@ P(Ln?b38Lʛם GТC$6I|agB P(@ }L4]X\HhW#7pp4e@kH P(@d*E'$&>1gd?4+䕖nZKa@ǝǪS(@ P@G h;%F5xf3>Wp&16@_nZ ?#1 iFݾ_Gp\1^3y[lGc֘ؠf9)Lw0k,W.0sF {u}Gluq{z~#MӼ'LI#ŽĔ&_BTUIa!(]X<WU49x~S. \f9$Amv,a抉55{qqzN^qÌ6er\)@ P(@4r- _n>Xct釿<ЫROA_TM12 \L=S wRI#tLz,.y[vk4gĀY;NP~%1;mps[e/o >LISG72Aa F/ߏo,«(@ P[_o*א!*Ut IDATvLl5aڂu'Ÿjh_Gs{aEQ:cݠ~{IbJn#d-hg0*塞zj0Nմkܹ F9P(@ PȽ CЇ 釀Wqv\{y0vikͲ,9-ʑ3.o oRi:]f<'C/~׬Zנ3Z7 uk@Uj(YRkM(@ P(@&<ā{? < #URZlDX䛖C;&NQ{z )Fк u-NqLک1qFhо-J+CSZBi ;7qe=o͸8raO,U5ϖ)ҷX7}&n ሰhEZ_|VЄѭuaܱ8E394=]Uo#gB P(@ (K-~yBTJleʂ=1(ܔƫ2o$-0\m?\!>gѫߏɦ 4Hlh!([(Y;L1j h;/qڦU^㚕N1itN!q(@ PЀdStա>,L 3Ow̞PJCQAK-AsMMT*oi9"^bJ5&{076F8(i[Cmz1W-D uj(m^>j1b#. ǽF )Q(@ Pȵh3,I+FJmVT2RK3#E@RrbF%yM{kˋ+c OظǼ}ѾL(@ P@ dK@ԴJXO~6[qZlF`i48g$SC~16_rXgp߄S5UK-zk,8iY6v]tyXLXw=\5](@ P4a$hj3i^B[-e zIt5槡Nn_/ LJٓ8|0HQ j4iCUՒH9hIMkWb۩Z{RB9ljеAKLJR,Z4/yk%SUboqɻϜzbAhRd]<*(@ P(! =aXG@PHL-P=[HmWx E}QX$ܛecSTjx e`7PW `D"6>FƐXǘ0PF(Z(+ Sp P(@ P 䍀I'+GiՊf4 +5(@ P(@E h>ѤLm9a~Pc+&L=#T ډ>(@ PrP hŜڗoҪ2"O{&TDCQ(@ PS hdAad+8&nppQV[`l(@ P([e@e^`ת߰R't3V(@ Pg" ~xAAaVy`io/*WCŲZ}39oL P(@ P > ,(@ P@2Ɲ(@ P(@ ^M,(@ P@2Ɲ(@ P(@ ^M,(@ P@2Ɲ(@ P(@ ^M,(@ P@2ufNekdg+q(@ PY hfy1(ReO~H\0c])@ P @9$ nrrG3e2 (@ P(c,4SK@i+'\< A-'Z&(@ Pt,fhn r6%((™\&C\x0Z&OLq7 P(@ X KȢdM5L3- hn(@ Pб@eQ@z(ZNoLAdLOA h<92 (@ P(c,4[H!8=h!2)-'Z&(@ Pt,fe Z@q*.! #h hiqwq? P(@ V W-Drp;j!V_[V3:`㡧x Ŋ++"X`x u#vcaeJWB:904]I P(@ dM GZ=ꉪm|X{-q*vQ;~a̋ K DOhֶWLέ @_z;k=ܻKY0Ū8<{gRTWMW,(@ PYрЪpLer1y VTf[@]Kt*q3/ڈUJ' hfքǷh.׃;h`f_6z8vK?T1q,~Bm ξW-Sfu?锓Q(@ P 9ޥT8+kb<`k{ Ȥ86k3k># o<>"^'UBUkc0w1Yk%0vMR_ hahPoO/xNvH_SgXkZǓ0kXO׉.&nD P(@lȱkT75ɫkxobl@s 1 NE(!_a/QP/S%pkԷ@*և"qrhi臄vlG;'s<]r1]66hd  \ k{~Dժj :?uQI =~qo]jgZ_g<(@ P%cǐ~ߴ8oNJy0kX5sS' #h?@䦈9cL "f.ww5qroZmJHǰoU > lѡv n oWB5qY΍-kcrcx̘yQӡ>}e%N s6s8ҧ_}aG#jy1GP(@ P r$ 4/VLS= 癀hms_jXH XDuڜ'F״mmH)>;͗ v߆Y/ЦƟUprhIs;M~9ՄEN-O/6.iUA.Zz(@ PW GZ-qfdD<]?3DvUa-LEB VLg^xv8>JĆaϲ>>EQj₁xbu}9 87<3N1QKhG\'.B@ɭѤP4^c-C\ܘ(@ P&#c`y-Y^RUcM1U,q_?oT6GWqջ~Qa^ٸᝏ/o Rc@_}MfǨXw 5veQF? nwЧx>feP/*CEqv!th&89e%#hB@զ64-*V2au|SS 1TlÎiP*yJ`@˞^`(@ PȊ@4y$Zx+S ?i<%H ]iק.Gxi}j֪C{{abB-P,y ?;QoĖck*٫*om03>qp[:Θ)"2)@ P( á?c^0cs[,4RQfZ#hp0Q + ?uKxbq L?OU$GKHc4/ -N}Wy x$fp h ,(@ PZ@Gy+DPYbLdoYk Rڙ! i;c 1qcl ]0%lcm*~E,ؿ=kQ߶+{Ab-< 鑚z/{&1}MhT^h]2F=naHaaC+֞ݴs +mn7p9X%mP"k݃p3N=}= YûWVM;=_mSp,`@5)@ P(mh`&V S}lg*rF,z}Oh]#<%,|2 ԃUF@| 6/㱀٩#\c d|7ƮxJD*fzZ=oc =o^<0p/sbxo[ 5'+SokNX~ h]z~ihm2b88&n?ĀtZ>Wq)' (@ PAL4 a`Z Z!yfݏ/©ɽ@\{cpJ jUW[a~{{>}ُeS"l/`=Ũެ 7>R Ea͗h_RXXce,æ&ya] cjɺH}4{ h  P<"T7muv׬P[$mjh4^}Mv z)πSNF P( hBC刖vLiVFD:WA'! `pyDŽ:Z sZ쯸gmǤ/kE±4%j>jo}t@"MФ/ZPD,QAH@KtpQLOm亐bRt+I:ַg=e#ɥ8|w+j!, $GE`+KG@s{c #° -M@ױc=;]/`s[ BŰ}FЄ``n 36%S4U ,m_6”P}!k\2 h9 CQ(@ T s-%F/HW)'P0=+$ 20nj-Ky*qqEl֊ zn#x¨yM`e፜hI") -^'T4E }j s,{,ǒ!6)Lp7}4ǔ y>]3~qG1-Owq-\לÚTR #0{쾪Q5,pti{W`cO੃3ZYF`ܫrhSs̼(އb@g (@ PA4zDWR9f̴N𾋦SF˒ #KZ-i>,}Aũ I|>/)S0{q0)hcb()Ig=-MgS뢶B#( ?Daf5tuY\e(Z@=i½j3kaW3S_iM⮺c~;m_}A1]bعas @S4Nm ]8ZڛX'>W(tB= 2U cƘ>݉CDzQUdCG= rF`[l Cå9_\"NX9~X j7 #jeS %wǯzzkԋ;EQ)" qì{r`@O(@ P@ h*sqF۫bMeq! UKbrp JRQ6Nuhye8cNw'beqS-ϟB2>ܾ;K}[m,Uң6ֶZ|bh%+նO?Ǯ"'by jY*Kq,qb#Ÿ䔭Sdqy`V$Ɏ!l=Kl<4t (@ P,BC}c(TZHK TxPFXl<``+k @Tp0bYa*EK%wL}&LS_?=e. Q."i4]["LB[-< ,Lq1z6SG=$5%3=NWF hySX% P(@t $HQ=4 ֢o/0]\Q:$]V_52qhֈ51Q€NnF P(@l4Fq9iy%y2&0•ىρӨ<kfx3\ZזZyV(@ P3|pYlEBbb[왲PT{alZVS8 !o+L̋2 hS,(@ PYZ֨ h? P(@!V01í`@0w(@ P@ 0e;q< yl(@ P@`@+fQ(@ PO-kL P(@ Pڱl(@ P(_(@ P@`@+fQ(@ PO-kL P(@ Pڱl(@ P(_(@ P@`@+fQ(@ PO 75(@ P(=K-s<4)vMu(kJ=u(s3凞d? \ISb( 5ӀdiCO@z.B1Yt~3J,y^ ==ZFVsHX誝4Օ$ˡBהnz֓Q ?g@=:~`? 6:dQ`@9(@ y\ hyYE =bvTW,AˎsQ١2)s3>d>#\NJS0 xMdn=Y@z{pzCbd'qEPl "m2桢?߻h.[%L%7@ v~xO^!(j_ի:0T9%-d e2Fs}<) ~3(C8 j3eaoq>FĦ\Lc橻R9H@H=h2x֪Y Ǖ`l$>-h\74C&͍1j;|zCZFیV`P&Ⱥ V&w 0ětӳPT'W`ܚeb4/jE1/1j~ݴVd3HBo+ )RnZ-wOVDy0s6K<!3ߥtSSB J A4+ A!}PXƢcQܥ z6+Id/1qoܯ9/!~/j4D©!TĭX7J̍(Um?_> ϰb/)G1gXU?/'k'Q2ڈ`h(a-}-kOc/K\ ۛ0`nU/핥p.eIY@ LjV@q# Ŏaנ' NMż/U,~ى)_avA1g4=E~"¾3l/6:.z)?b(f<12Ϊ0и ,ţx*7pYs\"kv?F - r7炸Y6q(7yao 㡨gEku&yh j_'9K-+08y)f;"m\!fLK"{t=kQ;ׯĊt5P+;؆<{)ZaY_J1sve5|e2C:R=+RL뻔_hU. +äơD^Q6/\n\O &٠92! ÿ:/3nS;VoA Q3ȵ&4PhSNk~G9zO6Ue~8r CylvFoPmܶ[4; 0凱8q,\w{/>p MoۑcCP 8u(^) :]Tph%wRq!Ul. r|.5vGTm]E(SX+nyyKҽ[b| + 3#6vK-N-ae8'^7e#n`i/XvSs;>_&XݤV?a:$$V :ݺ Q e8?9N_+mvt#f#| H3x6v&״жlT i^SpvXr+ F24kKC8'C^45mfķ.o oaipز`}Z>f[[`ұ,N.ƉSJPM/Ԉ,~~:rwH.A{t-{#cSAKw[-~XusgR:~hcp+ V=gЫ57TKc\O.o 뮣ĭkA `5]o1KOz_.MŇz@> Yui]&@޿;wh.,_ըGvaJg0mx*. qF%%a0tNCI O $_&_E@k=}+~]Kh{W߇!ǎ%^u\TK, H v؉ֵ5] "tJ/9gedyį̮_?~]_lK嶖b50/mD]TMu!z@.݄SX1A@q=O@Sǩq޳ދ3:7͜ٿ`?1Md&WѼX)g'["qaa'\a'ך^ 4cB/'떚G%8:(; /%PMEG6-iŽ{Sa61KVk0NO OAf@|%L T 9%ϱFnڎg]?.@&-,ZH=d><g.NzmֺZʣx6BI}+Pi!Xآ' ؇Y1ITRM@;vմ3f<td6+ qd?+0xy0<& `Զb<;גG餟Y8JyJ˭{ؙ}qG;LD9{=Pɸ`Bk.Yb]+AOG=3!v膃bT=holxϵ%],  QNtCk#$B^.簠Doп8s?S|T(ΰo{,*7h[]AݤoKb*3؛`v.Dx][1vc"=}U-TfOa8 RNHUW3bX\kVqEO[$WlήvioF0xТ biBks= w/hT ǚf9)}&w-!2lF{;|gonb ,w]DgM^0ۻafN;H8}<]6l#bTGvfɧ^>S·qK!O$W7b*&=huӖz ؐeKzNhag}+qs~=2}8O3'N͞ 5;AfnĎ)GJamGGGbژ~$ϔe%z>x$)mS𡹻ĩ94fOǠICs}7dm㺰{:uD8leкHŷ;\`qV uquo͍Kf P蘺W>{VKa5BǾGo(˚ĥ&e>:mT!NCՕݣ^fuعs0y1YU3؅QW7 nbw>oH0O͝[zI~Q {ɑ0re(1]PRzs:}M >ױ6Y چs3dD0@+C#Z($ืnz$mτ5psu׼X~Ed$ ar Um9_M6R6΃ĵ>PB$ dC'Ӏ}we-ٛiiq^⬎W0nRÅ'1uq9C4{-뙶Jǘ$u;r埬̑_al\8y| lm3'0`ݾ}/ bnB \3}%.' K;E^X6onH笡7cj[,msK;x5h qĴ[:[|8u.6[x^]냣=cr.ӢXT=n`b:EG-#q]4۴fL((Ok &]װU&L^Vj@qN/SJͼ>4RDؤ$Ɓ/PjFR%Ł&u#%.|%hk\("PD̸Hj{YD|>DAqWDm-bb99 PuqƖ"*GW\V\"_E5R`sff%ˮO&񑐖U L&fS([9=^qqqr>ZomʿM*N&D39"QK X|~ /`.mF ly *1{L!ĴRe@YS=o Sa "P gܗƸ|SRd2ȝ@~,1,bZ0^t6ȋ@YS"~2?WOLp iD*)yuU  Y%s2/JE`(CO%='5A*De05R`W PęIvlC0ZƇ 4L T E|> WUW;+`hf mGųhk҉/@y ' T J¿_C]mW B1i{t;zA5G#+msM-w\پbJsV땇(!۷})~w4pv l7=TJƝu+Gk;]i_ޣCU IDATT3/٩_|0F*6rQ|73\QW/G`UhsTՑS+Eaec磖(К,S*2Z1,ih) QLuz sf?8{i3Vw)QSCH򿆶CЭGK_I&h%>"x.>G89*)e]qfSpC2pWRq$<"fS{t!2 ^+|}opo=}>jx8`~XY/T |kj!}"뀚ɸr-V~o1UMl*)'V0^5<j<;y_Tz:x891>olRMFчXZB_ -N&rmbH(4CG[P4 ɏ@CJ \6ihc!2gsoIAQ \A;묈j4y6!(2G\d$qDs{ȔHứ-}!UÉk`EpMW"Pd#Tp;6wms褅U>ru lo /Fk-aٰrp>Oَ0`´DI8aJsL';1~: (_'SEMXax{+oAw:vk`rjPw= ? nSUt4Ef _%>!sFzٰA[o'[ǃKhÄc bp{^cYNWë1RRipcKQ689NZWb=Z Y {+Y`MWK7b+d{b![~y>Yu+lZ.Ngwj+B9fpHg&y:B'$?|J[ hx23TVáQHzKprV}x@jy?5J*Tۛ0I[/wm:hfxQ^dT %\R@'oA;ui 眱k#N tM41Sۢ9}+/[54o<.[DZG^O#H%.TAoè eN6҂^^F1h/)æ5c~!hcp2/ƿs#Feh,Sԁ2K>H B86&C4\ Lpm^ƣ?Sx=HRWJ/I]u$-M|-c@\Ls!lpDWӳ~mF9@I=ha޷1w|x\#UVs,/CӸ͘;**fLYɊO=061¤v ݫأ ÝhZ~cɃV&&x_~`XUql0jgJЈ lpr *{ 4b#J8,pmD s85 ,4D]?prvl+)KZg04zӡc mS `J/qTPe(Eaĕ08ֳƚjqhy"[c--<46/S#Bs??5Es=}jl`| _e`TSxeZ;h-֖Qn>8-\bC::FGag.Yqy\<ѩ5u 6`J|Q zZ{άsU+gǞ.Gc^ǣ@;em-5̇VE=lXYAt)m,ڌs_q*E燙3s^bs7sԬm;^bc419]k8S7>Z^-S*%Wc{2n:;J=i*R1^]ףcM6Arn` qTMph"4',m`Oāg⅗C}+4*H}:-@~ή9]JAa8*B;1 zpoLӨty1e\#nxinùJ6~)WBrl<!N~Z@j2>M0]Fap8Y*e_̃/p)~I${pXF9DV堚^*1FmC޽]M(DF:VՁWCe.\X 18Fâ>! EbՈ$ЊxKPMKxA'dFDnS1U[n!;[%WZ?FrWcv|swÎ+`'^+E;`?^y٨w`噷?y0/9==w3z-yW!t &Mo:\@I+}Ѫ֨8r{6Do; iī~Ts45oNGc~x|mFⲳ#٧pBs}-\졊'BŞ$Cq(]гw׮B<{&- Jc?ó!LmkH{-=ثcjp_r+fD3JhO`z,ޘ{Mqט~/i"T=K=hz)h{/H,ЪplKķ񎆐ӡ`ÅdI>/c'OA|EX=MH2.nz?m{mBmܜnQg9\j&gxQc0<޵1%LT9 KhVIC-~bfs(X`YiD{вK'E%4N<_pk2CDKz`pV5cQYB9#;)27‚<^`w-4>\H Edz_Ъf&sE8j`q]G*Þ4FO,eMvʭ_%c̷/„{&FK'YmY-Qz0!*0NoROF%N.x{=m[r 6  /~B5fKo >mGXkbl8m|eSۢ8VOhdìqOH4EOy w'am-dDm%;v[eZBqX:#*!(0=\x>E0B*~F.@vYfO(+Z@]XCWcùhb"^C|C ̋#D' 0WKV(uckC[].e?K$H˓"cwh=1ԙ8L 4n$מak\+h򒚈R"p崟4Q\7 %ޑhјq; (&pDfMOqՒ Ug}rόyqn%lVV^$!R_n#als41Qfe7 [NjɄ r6ο1z Z gp2ڗ7Y ߝe=VI -%{odhr0!h$!Lb[8݃VL?af̥,HeEcC(, p}y^{B`^Typ9xb+.vvĠ0JƜ&ڨedj#8`0,a Axlա<}/kw[?l2g2D|Z*/s$!Ը+>xB)l8X!ǵBW3e\4VT4nk{.$ \FbpsW vj8)@zO"X('c0^T l#Vɘ|E"4i[ZCccIGw<0vL&^'ño#쵸rƚk?$ }cB 7%( +D a o:ƙ;Ĭy@{V/ǝz=fcɌ0΅|x\;.#Uؽ]!kލ?!umtg\i7 e$*rm'Vی&5)tz9AKhϙE{J"=ޠlܐ}ڜ@=^0pIsNdž2'F1!fqbO[q*ފJ'#>}!Bl{݇U0m3%_bւ}nR L6m غCp_b& 5nE1s"jLF??Nn0gb3l &L- x!15҄盌UuX 6{LLe=UWb2 <33]:*,r6FrXoO,NYr2Y#x{TMتc\-;0y/ -\ڟY-ù[U0O?ܢ=x:ټL;O9Ԋl29б>"BX:QY-eG~#^/n 'W6BRN܁G7g84?ғd( 4o _ Y\F? X,s Ƅ1a‡p83GPQŴzgJ..t0 4I @;k'ZUb1G%h$v&+BN[[ W_Sh#IlƊC"И=[jUؕϨh\ =Yf3*꬇o=1 @~f1&QFc4>N WοEε`ΏtVHo\-J7aHPT4uJ 1?Bq) X_ml?cks|haGث%c@tiJ8 ]u@ˋP%( A|îūm>7'z{0d o;yM'_kx>X8x2 biD!>nF,9x*pul=Zk1 ^]~dK yaط?bWWu׻Yޢ(#!g4ݽ6<.PٗTL39&ǪuE~^v1 1[=c}Tp{=pɉ~0 SgjdNvhƾl8=NXoB8ڸ;&.m{-|mܚb|6et\h}eaE|eB3UdYE8|{ӳG2"Fx>Ќ,PT9K}aC"$CFaW,B_c;8]H_n+HSi!<33!L?DfxQtPX"T$XrC'eCnSEdh1Yۧo|=nc+D#bi-9$!ɂΆ!,8`frSivBhCe0B0 ]Q-;`>mu&w6c2Gxma+382-4Aڬ1o=%'X@?=YQRT4lYaà *w*e_~ hUA^?;=868 ܰ&iQwL_94Inp|2VO;FMi?;=#Fu[2@J8~a IK10;(J CpC堆P>Ƹ0r:6BFH~66&}9gU8ߴ(lY2 f{N^< [t5 %@ @} )iiiI.c̩(Z)DNTU l:>4qg,yZ(ܻȘ62+xr#P>RӠO/d\/D$EWZkI1A3OYRK8)}MؠVaa-{EIF:aMUBߩ7l!HLy!{ `!g Ƽ*%ܿ/"%2Tpd:GGJ2HF FcBcZPU/\\Zy.@&PZH~2ݑA@XO()CI)SBCR!$RN*c*σVR'OZᘖ5A}bbﺸ{?|xp|{r#^xAT 4aLI@9G7P+@JBLJ(4*q*H1Lg`)<Z)"6ZEA$y D@b ƹx B,,:J@;D B jFL $CÃo-T "IŔ&Z86ޡ4\l%A.!I@Zi%j z~+~$DȈٝP5:~SJNuS<4"P@+ D6hEvRe)v"@ DEZ2"@ DLZY}j; D"@ % d  D"@ e  IDATv"@ D"@Jh%;"@ D"@2heD"@ D($JTw1IΔY]mpI"@ D K1F)klzj\$H]K"D"@~s$ B}C\PV]]]Nzs^G4 G,!(CMS%Gs}nĭ/)(WC|<(D</"`R}ZZZ "*"@ DH/.&szb!#Jؖ"ud#r1"T+qO(n9ܛ(`K\vZlȖXf#JۺX#ݽw0kۆsڡn6H &VD7K D"HC'Jѵ5NY0dc8 ta mNFWx?oL9"+J.hK& q!nh`,-S ~'^pXퟌVm-D)Qxx'<hq &qKB@p`Vp` p6{%""Xh(cйiQ"{dM c6Xc# ZvGJ 1%`̖]¡*`u7WQ czVr{z.&ׯmh4?"@ D4,2+$i+].r:E]l쨊ek}0"[bʘݥ{%T@.c=@ݱ5 ]~05OGree5@27ŽC}$~w.6ڕIԈx?x81 LzՉ^8z[=QVHX 6q`r#A.#)R~F (h}=o`kf/h`#g3LGS=Iv n`U2mx].Ӆ\#_ZIcy_P3(c`ѩD"@(A&І5FS# ; R.W6lPM%^:sEBXQ8#ZZ8BjGV1 d< uigAX̖(6,)*>kg A'S9*a4cvWTa2ᔒ=a IK֘~лlyL9^"eXt<9eڃ&~؝")NaDh"dTyU*I~n0NŌ?FLV"S*d%^ZyP+f[$h·N $ #D"@@P@]}{{ł'}SՇMa˃(& )i#ͺ}Z}bI:dC!#)#ec7u,[>SYPEW02bb =e1m$hty F1ޠ0}=z.ϊ=YѲ-60%f**@U>G+ɽy_~aOĢW-&ۯr)0{%(AMЩX`&>Q7 !pܒdhz;`>qxVsm C<DI x N`ㄺhmڑPg+Uf]XD'\agCwgu-FAcڰ'ac3!X=NfMIUyL( k>"_" )e@WՆN` Q75?`2 hh*ӔzM9M-} ƅ;kccmQ?+*E!}oWmq=\W+0ig B@)&{b12 W<"4W5LQ/ilRk| y#zj6HIJp{L5 bC""@ D(0}-LjU5L֣݃9($^x&z ju c)ղJ%ɊLsNI`_TIqx| _ą%k f dDj/|#PFK$wk˜lCN qd5$ŃZ'N3: #М@*lqO]b'qFy{vOkbʕFMGFhfzZAI?JI=h|4E[ ֲ{cllznII0P‰䨅TUpD^3YT$.Ň7~u`UVT#mMѦjC'λRVh '0"@ D4Fmo9u{j=Awg6Cdڗ/cyo~zǀZmkk`QmT1´VG` A'`ѷ1ښjj㊸Yf?sŌI]gOЖ:ZpXIXMgdzLFkJ;#x>%l`slKXU /Ǘ eR{wZ/˧ ,d5Zje ~_1:=INMEGڢ\r,v*ڥ-vAVi~8hag ӫ*G*:[&I8J=-KQ`[6yr4ּ@ލ^¸(_˪B# ;)}sG/@]O [ᵪRs~Am[]xsA [^äq\j˧ocیWC@+9*"D"@B (HɷILB6zV?rlfܜ0WKRNN@6$:B%KN2#.)>sF$vIOyem]҄3]{/?V&{1M'x7huJ lM4I{$,9XYPΉw!?c/V ^=VܧyzUV_C1q2 S}@y.fՀx/(- <^Mmqu5?|F~; u9?kL~j8B )"`{4aѰwxx `"}[RD$̙$rK"@ DX h\nbd^xŲ^uѹ}7?l?Ek !u0oMBT3[cm`컰dd2?eJ.6I=be$5qA0)E)qoܲGs}\>%y50ӃV2V2J ~w\*7>Ke~dq26/tMY&v/_,f\SO1+]qL݋L}\>1:UWt| 6EUCF,exQgfϒ%yiEm8TAWTB^UaG)d7U0 VsnjXDdN(%cqe]< Gm#D"@oIo٭("@ D"@J#hf"@ D"@~K$~nF"@ D"P @+F6"@ D"[ [v+5"@ DHZi5"@ Dߒ ߲[QD"@ DF$JcD"@ DHJ"D"@ D4ePK6"@ D"@3ֹ](WYb"P 0=hL)㈩byRiDƔbXT( 3I$ 2B$ IE4{O҈@i qO4$X&g  l$1U L*@S=@s‘RDϸ'V⻑ ,+3` E*$ChL)N I@ ?ZiILπ- Hb@T {(#@'qOw#XVgj'1UI*(*IJ6{h%ɺ2D ? PHSB0R!D@JƔboXT( 3+J>|*β| U%G~ŗdYڡvD 6`߻BMP5ޤR_\T,+<& 8T tr %1Uf BQP7Mڰ0P+lv}n<*PED@ D3h1|\Rɝ,6ſG6#`6ύ,еYpbMU.W|Ioch7w 6t (<V3b냸L:%0VL{֣zRa i:'EE8shK>DWKu0sGI~K@E;i?>sHTl"+=r!bQn[{:O.\GRiV1R!Oƿ};tYy8Oġ⺔J `Ȇ'P'm cn0irK46ol1qrn F$b/Pms7W2%{W`}o=.{Www _YL&Gx7T Wl'#zIXr87Ү X/Kĸ^\3u 24#DeǞe֮|>*ɬ2@@cJ፵܃,=ê`b8Ď;F1xa0VN`%lU;G,9: \'vFș]xP~n;{aygcvPv]70ai]K\}9@P%㚘=zW3ԗ * ]Ջpa0їpv 7,^ØI=uf q2j\46#ù8vQGt16&Ͽ4>Gv۽;8^vʼn / 5ZtE`4%',5GO7.m񭰭}/\秲2%N%k cGr1w!d=!ҿQJYWWv?d"+`Q/[s Ğ#7Qw)z 5aX; YzEW}X (vLnj?UZAd/p܅\~_E~֡5ZշaxXLK;VCW;ϗK2?f[b3E@08wĵMq'$ in0wꃺI H}fnΰf}:]\=hXKI~4gV;G֙c,^Yvj0uv#cu^.'AB<-m Ho5y0R]q<oNy7ѧV9Hx5Fj0^pV0us8N;x{YVo6[o\Na.e3!Yˆp8 5`~}t lkG7(9 ؘOy:\dǀU'17}tӫ .ᮨ8O-0aóq>HM71FBj+HIsǿ$h5! N3T.ªr|شa8Ȇ|"{٘M+ nY IDATbL`fS%1u{/o>q&GRxtL`] j߮[,Ǎ۱u\TY3 C(v]ڵkݭkw]kk"9030y#b.8yMsc[ 7Khl,-z[0o|δn0h6u4>u $,î_,;5xLL]eD^-PgҦ|g O:6."P\|WN)f<ڭ l u,qd&d#$1x=ō\i1SckYY8yЊP1>aEչ%JBI[旚lCe 9uuOpJgx;NE { 6t8\q}h5xs~vh>&qi}hy-~d)Yb"Pndcgјi8 r6u7^Ţ|H<7!YTOt)ʻAGϾ2JLcS/D/c $d\X81܈Ex  /o3`?,ɕDݯ8~p 㯝TF0Oo^E/zӖf }/oWs?lSR4Ua<ݯupJ8<ɒ~ԱM"ߊwhȄ)^Ts]݉SOfÍ+r#ٮq7a ͧ3^SXJ/C-#I1 AV$_GՄ a]pyvܛu[KS{(?]ppV0,^?ׇ1~ivN1?e%b |$hs& O~*Ro ;g%OqM޴GFɑL1{:z쇘 Xo&|I3=VQRMs7P6]zË3^|k}0|gێÂt? ƀUg0uqL SWOm=&DT:CX){TKC0aNeR&-wsIV=jz5bB[s[w.;6˪4CBuO6y%{oE&BU=;204~ѫӻ=K{à ggݜt~y^Khmҡ5UW[{-`"Vf05((RIq}U0EY|τ5px(,='(Cz"IAcݜ?=Ӥ-Yz?s1 b&dh)hgP2Z 7va/PJKA+;·2lEa0 E@N٭kL{,Lw::?a}[1@rc6]3)ٲgD>dKQ}s}Q7A לz7{݅9yKYY ",,߶ g#>$qvH 떱Oe!U=clڽ`Ÿzcdaq'9JN~{Vy0m%H-Dc9A%X˞sbuƚY͖<;U4e6nJY.A1kv2:g;s+.ƹlufL/Ok)@kKpJZvTn@nSbRWie^Qhuqd1"ؤ&C$HGQ\DqYxt !dR[g9R!H_zW"ׄeEW=͒DHHkYWj뷪6/9١aQ3T)t=*eߊ]Kkͩ$D=Kf33Rc~ 'HEHLOQwlK/ևxqs::9O^qT!(ԙhEH D"@(Bʕ+5hEd (>}SCbYT91@<5˓j#Ł3I"0 gJ5l44S<6"P@+D6 C@L5ϔj,hNifyRmD8 Vzl$$оWCKP44S<6"P@+D6<ZTJ0R!kOcKj΢X-~bMř),O$ЊC/D ƈPhk@KK[c4acC4ZhlPED%@sJxj'FhšF"@{--=3pFp2ŏ,;Є԰YbϮVƔ BSQxj'Fo.ФX|HEyo3A]w ώ$mQXKH"*aۙx7m3 ALH8DІ9|&JT@/h9Dܾzd*8Ӭ2\C(gcկUT.: t& }20)/MCh;TΞq! R6J9@T@rjMkCӾ88Q!#(Q+s 6:@/I(U*~?qRQ"łg㷁Zd@KF=)\ 2@=a˪hZ(炀bkmq5d9` tV+ki듷1K<6 *^~sRk!۷X-$f&46<NJHIsR(h9YEi_et2/ >t1 V^\ ఽ . t+HŲ:M]}cy;߮LY@b0'Jy<`*˕DVb9>h;f**$(qɩx ]f ˜#$ ܱVTPΣEyu6\'n}f F*e?u(geQZ102h7ho_e.E8xj{Efas[ :őd;[kcEWG b.Ob.nM H$?\.Y,,pԋ(\Y)XQ ?'$έ ݈]+A߹4 ty"|'"bܭlFh[ъ50#~C>߃t &LK#KK}Wnd( -C eǿFe1UY)\x6 XζnUm0;s̼Ml]]aB\ZVl(wJb</(UuK DZjn/{Nz(kGPpNE*v0JJ@9e<[,֞qz6F-`&N-Tԫ`/+ W0w6V29H}回JA!M!#2h[ڃp~[g'1@ףX4h'fa>Z. 4GW=ݲ_90 M[Uk 6:xŇRRb4m +䡏qXjK{sĔ!PيesF@{-HPOU .-1] Km o.:pGaU,ɑh k~x qBҗP=M 2+CZ 8R51m_R)mPJ^&s0ZnVy1bH0H,6t 4GK,S6A7%c(SFDp>3ڟp3#%EUJw-wfJnwZʰJJE.ۑD[R Yr,<G> 0w=K: 0a \py wSTub<\ YQrfر3a95m?Vw=Fň6sU4.n#"\bG+[9ڢ.?E1K(]&~W*yE߭θxqLu4 uF#--cP߆['| |ve`) DN-LR݅ػt;VG\|R .|/F^Dкsvϔ#}{*ę@SD!ܵg(骲@SGcu\; (;mlqlߖGW9'3dˇ}0˗ dٱ"('bZ4<_^LX4‰qWEWA 䂔lwF\ۛ:x1zLQ -?lZ W>BL<ÄP^ª1HeV3ϟ@$ a #=hGsg#6(e_ |*IL?5 > b UZW(ĖDE`+Nt{/_ džvH D#L_ lbZ"ԅi* ÜNP^&x$0GRZ8-&UYJ Q9s:qTZC?'-% dZaڛ矠UμL{ 4E{m?5qv̓!kኚ|04*R)8%FF)X= gTa$yETCEx'gg]F!zZL6F8<bls>!p54=R!Bx\6Es%?fS~'aXyIt ߄u{!7eo_U"Lr[B*"=ţ{;F௽68}qװ|<\}˅<u@~'{1mvyi;{nq{ζXrmۨ,OXr#aE5EH 4x23)| Nfh۟JåuاY?+ o_a_bFC0.@ȼAwr  IDATZ`ȓtQn h̙Mi Y+ h  Pn )(T4ƃ&oP /HN)?kW ݙJm^D>WƇtc;CwbxLUPGI>aΚotͽZ v( \=W_#ql `/_t=ˉYMmPֈ{DLH(a@{ $w ؐR4&D>>uWcqIdNeq-zUl`PulibOp7tMWU/Օ_!$T?h$´NCٳ,Z^x]?fTUL̄rtC)#-4pyuat0Xf ѣW}LlAƨ>rH ] w.85^Ҥm=m\Dߊ\cШa쾠ΘEɒ('I9CObHfE{0i߽c؝XM.94+m=쁴 j ]I/ 4if7뉨?6bIu_m'69* OdXɃV@pTcERp064g6ٿ{P%cɮobkkXTYCچiYR|f^qI-Ƕh Zar[{D Ġ+1hYha ?Z`o7?_P]?bs vHE !xykڴ:lGߙƟR_ Uys0<@U@R< ݣ" =`pMM8kbkBhL? =>X;/7 ,}7D.4oGb{H̎ `!_hvq6*xɱE Rd~&s{rbv#zjDN^y+$aͫ@_9#IHL*x@S-?{_j*6 ``o"apA+?dmMdC&n  ߗcW2OŕDHtqA B<OglLb0V)2xpaw|:]::efzoڜ6j#@~h,!`KčmԳJԺ*Z oTjWx?:V  ZEhUuE<ލmno ,o̿99tJI)-ܹ(HŢ7qIA-/B>rkNއ?mG'\߉҃^8kL;NEBv?e/~i#q<5N_ +Lm 5 ;ǴvV ncpeUy4m5\< >`1}'(M2ι>kk=C`r{gcv_ޠіϙeו-st%6KASCd@720tu۷HswD Ě`%G"R!`2gcZ=_e|Dž8Fsot8MkbȈvR(vŖw1Ⱥ2[2b!5diٽ7PE*|Y({22DnRj/нuC]q!8q`5G*(|ӫZ T$X*CG`C_ Ev$b±RqxhyhdB' o! qd`];{6-s}Au]3\E tt&*A!Cclnz c,QmObȈ #qc;,{Rc1c}?*sY$,^p7*b7G!lxN|PZr9?T$(=:YYquܮ!IJ(5V]'姜$. C3 q0mͩ)~R΢uZ4%IHI6 t?<#,gHע8Ue!.XJiaF*"J5_i9s8&d}y]Iߺ?5!NJ[9s34(mW}1*3aLdʙkDgjc^%ܿKP&2˩6w1 2R].dt-M 8x){5Bӂ0ecsa,s(ˇ*O͋gaD=J%`NQED@TP=U(4p2V躊jU. V8?D"@ D"EZ2"@ DLZI}j; D"@ E "d  D"@ % v"@ D"@hE;"@ D"@J2h%D"@ D)$ЊTw1D"@ Dd$JrSۉ D"@(RH c D"@(Hާ"@ D"P@+RA"@ D"P @+ɽOm'D"@ DH V!D!Pm>?tqD"@@ @+8;*IW"4m#T6->$оoՉ DeTI606ne`$!gA>!KiR@[.\Qm(f9YY0"{qзηm,lkmDxv)ĦCCTRJd5XϕG>fvVk$IփU:IY$EFQ4ǪIi:C D7h"x]~ htг[mOx MyVwƺ.xu:dHS?b| y  D@Ylݝ[YɊEB^qK&iQBM x,IfI`̙I$rBIwWhUbA2/53S?bV4=r'/Ǿ-"x3>~ߤp3QW+pe @x?bW Զ,&· b?ɠQ]Ay3r~hړI+uUK DyhcWhbXqL#> t@sKV.}3įw96@cuI s_F<> {_+r'W_pNLo^ ʫ$G" 6-АsrBFߖeq?^'p$Zvp. 4N 3'O8,%f;%>A r80Jr$ 98 D"Pl H%Ɔcp$A `=c@ #0=ܑk5ö\cFhYZ>d.!ܗ ֮#zY'a<=XrlCTrmҡNQ(Eqo` {օ9O1fVVe똩j&o+>ڶ6B\:': R0xP=p"<1q6Pu3n`U366) Ka#LBY*G-!M'"@ ?( b7'P2&KphfnU.h_%@s}KY zr:?[mF(X.= @Ǖ~4A ]kذ- L iG _/ߡXu0cӚ{f!#Ǿ1XS^7q!y101L>`7QѤzY>>eZN|!9FƇIg^biccnksw:)Mc8+xDY"|P;H2C*a)s#mOm.l+oS|c]mg…f*+duZn1l .a+ {y=jX8~T"@N@-:0z$'bi(h2bJvԴŠZd DuBW7^د4tŒeɧ"X${:o;=Mѣ,nGCT%dLhVQ-x8t9טִGXQM\ x.&3++k+5A`~%Opi~cP=G)3>ݏ$w,-ƬLYT"9wx00t՞[7pTxܴ4KDF̏';a2_yH,~Soɓ8ۘcC?wi>dUV qF8Nxw{k=SUmm[djb:1ݱ׵n4 Y]f _i MߪfN<3W)Tf\3;7*1;'F3~&6w}\)>)h 3b7 ǐG D(@cWO-NJ%k[)wh7& FU`CG1CpO7八r2-Q~x%<-EHToned9bCwǗweك&cdݛuFMh7!c2p Xw4dX ۪.p/Aư!4;y=WY =X.T.N!x&){P/!O"ۃUoXrɲ8g%̝)h2ሓ9YR|-)j paNCV**KTʾ$etɉa޲?RVOC)Üo݈=h 4a!av۲Pl,* 4)Dk{=xyI`fU}$@FɉX.chf˽΁9t*p hvi=jUƉN5lHOP7pmv}ym'{oTnߩ壻zv&Ls㣜_8E [wmNCEcb.3 (C==t CSs~ 5rVكu*,V…c}uqd3`Wji0;b/e .Pw]ܹ3Q&ё8jQ{[U嵁}s"F9w&,l6C]D 6 *bXu#eEt`4 &Ax(`@^"@ D 7_U9rҜ@sĵ01@id=sm܏/@k]t¥W%_SFxIZ: 䳇R|hYB3]Y .Ŵ T*Д@NB#@cR%[\rt-n<-6<1F6!4J2K\z.-}eycdд@SRr+K,3/8&goVᚹ 47(_a\FygԱ|n!lsUUjU:0I)<>Ap-jvYU<+oAxkZraUUC'`x3c:wsTûG!j\y / c٤),6F3ٴx8fMZ|F''8'ƥEB%hL DF@c4.;"0ISmE[l\u-}8wE){agxTْHrfb(ӢTefQ"]ʡ{i%3 IDATek&W pAb<DB( 8D}XrIA&\-ٌ{dۗu__^!1= O_'@ܠAv.DݝjQ@Sߦ,N}%q_Pے CU B8*fzY_6ctr^qo5l=hYǰr(b [~@ӊժL{=h&ζZePLϟ6{mxk ֤S=scl7D$0 E 1Ħyf=Q70Лիl@(Ƕeϱ}Yva9-oh_O|8~KEѷx|{,% 1yV)@a(y˸P םL0Wg>`5~g|2`,ire{jfqָĉ]Â'iX?<đXN7SG@n脁BֆaG?8DcC=ZFu272Gpl_q«T}.ƙl#IA>;c+ha¬,$G"@* p ŊdIٯh?w1lnʡiG@v+[_1ב4jyД߲~RNbcBZLB mY+g>5Eކ9m&ȗ@˩}S[롊\|)hl1t1䧇n`wrb3/ h[7ˑ@u=9J>K@Sur뽽2oRKAͰ~tMԷCWiy˱TlP 5dls[hkyW縂Zmd(c,9" d6}q wūC&밣/ DxqdExB{l^z] D^gn$#+ qd,aSgYr36rX\xܞ4A?GOU9 q)R3ejcJ?6Q{pt#B6#=8coз=FKh{۩ mF)gSwZѧnx0@ۥlGƹ`j.~ֽP9RiK?SJ/Gg,+kn,},ДmSrjP7FEV<'И%7Yzz5*Xlc‹ghz{gXny?#HY%UMXڪ 6YqDqƿ#s0LQ[̜̔j[>N^˼$Q2tX>ؗ+˚PE֜6V<}~^ծR0<}lbNzTˋlmHE'D"P JIEK!HXX$7b#*O\*lDi:el\Q ZҡgawѱHR /C"4K$H)&SIb"D)l2 ]|m@9OoY-eO$ƃ">L Ƣ1!t@`+yĖT"ijaJl<3! al9=0jAw 'd DD@#(5l!ߜw+}`٦lY-yԿ`Nᣪj Fs8rKhBM}t& D"c c'{&AmܝgH*3@fR҄ R䶷MِKz-hҧ D G=߅@rxӤ7i!jX&BD|LL3Q;"B̗h^R0rvH05|wN4 D"] @D"@ D"PR @+=O&E "9d D"U @xr"@ Bh?ᵶϏ "*pTύR߬9t!"@ Zҫ#h<`JҌ~ݫ`m$Mx"~8nYxmȝ0Y]QrbkTF t (<Vw. mSgAk0j[ܼ mr5D GLIX4/T1ݧZ~T0y{O0~%>”U@:|eSoqq0[=Ķ._fύ+d-K"@4E 4hg{RRS`]55-Vۤb4#55F?} y/¡'Ms_%e_oel@a Q*] ]J֌bW{s7##5쎷cpkWTa-ʫ\do\݉r 4Go{OKuKDb@\B?$lD 4=r!bi6tnb\ kYlÜP/aSiS3!7]KiMT #DX=/yz?{pb *6e }iNo\;cXhBH3USƼ{_K(n2D’,= 0m,X=8f ѧ;۱bL1pTOXk'4['vwf)z{>#!F,5Wkf>3Ga1~`Kh}ʮbMl`WagH8K қ xzyW]z{/`ClH^I&!4z E3ߙogߢ֬O.rŹb14VƤYsСM"/ޗkfXF/cM.=M6h?KVm$4F¸^ЄņS {nEQt &И74?5F.c'i(}ʾlja`nBǮEʩ)5Xѧ8$k(37~ v:[#7 [ ao5;G}8}>v^@+kwX>{Ю5 ;Qж}/ R?z TMXl7AvfLviM|$ocpM$ܼ۠mn2Sxb#爿{q $)MCau8'`}:Ff6~z1)IZ*189V2Gm|$@4j5g7ƙyH,{_a`QO\S8O 0߂G_,]&Lg@ T@ ,;v_Q"<DwP w{8{XB qxm[4FNnmQb_K[l~C7`yo|.3cy3ah6 _qv\`Cfl|:=~atpeAv=¬OKӃVs %*60r˦cfx{9j#6}qsbZ' G! 춱{6`h^'΄Fk=Nsi Wɘ:W)Yxhz>&]YGm>nm6܃ן`+ZͿ$Ӷc݌sC< r8ĥm tE{ 4"C6ac84\Ɏ8gw2`ch$@#|7ZЍ~9=I.O'C2aqȐY@?)Fw,ΩKpyhslx-cDwF%l޿Œ$ߵ+ `ht;ر.ߪ3e77cg9f6ѠhדG=0)_q3w$_no< t$ [bm(b/Mp/oϮOU"?Zy3ati#_/1Xw3\ [ k h?1qrɉ㦣`nƫF ?&И_WݬpM:2Ց?a613VmBZ|?X>ϯ#1`/s{Q-Ew DdM~$a}V;;ֶCU"cxf݃:%`Xw|-׎ؿqM(=,"c9 LcZkW[,I0.k 3mDz{5YtTa >f iӗ ]c7naީ~8hx浖zZa(!$_˹_Oa}umPg.Cq{ɬP$K*c"Ņ*v_Wì>vCA-~2?^B!@{<>2cx#u#W@̔0,Q~OS2F2;sNX!o!)7aVu`*Lh |h8oS.[&غo2[}[lpvgq_<\;G'i }'aIzycW`# @ߦJl`|Y"g@˼s&)l kppب\>;fO5?CZ{:jt$:k=eÅ{08?Mŷ ou17ߏCo)hPa&{KΩKkpv㗣k:V&Kt }ubI8fLDS oS.I7 GMzGki5 fK߃ōeU4qL\Ξ:k]o}`́콕'n֘w.b ֝TR%Ѐӄ5UGڳɮjS3+Z Ԣ@H 4&0EY|τ5)8;>c J$1:0=݄Mz>=cX. ڲr*|w^Ds썶ބXյ2Jz)Hxߞp!`7( m>?ؤǂɴ8{lG.2`&;=mF??2`)L^z9&KE].2F"aEm8-^s.~|w^Br'ė67CtZg 8!Nc0`O\}/ bAms81ƴ=QNwS5a֤1&]~3{Ft$bql VO-oaڹ\p9+k ~8jz6[|lWɄ]4fJ#:J i{1KvE n3ŴѾ9Ar,6ͿhLf3&~T@k*9F,9$7jX6#Jid((OMM]lS!iP;HBĉi@ImHOLPC z ׫8*|'MXf^ =^,PLI;om}bc9= 14sqƖ.6GWŽGqz@A |9"glfQbJ:xAz8Uth*PeELYxɶ>$&&,e}ίYbcTp U6L@ d$K Y|A /dy.IznM#wh,Ud9Ud !.]" 9% R (kFjE1Δp&h">.&PD[bąGAMB|׉HO>Ȯ$ Xש{ڙf/.чwT:(tMLe 48zֶ0e_0hZD+='Cq!&JY@_K1p2QTUL{oOLGH=v0Z'#",]抒9\ ++a,oC^QY,¦iD|%.В^@4vkgl@[àYU4r0F"0|K^-\ N6r:t屛䧅kk#M z5mPqMŇ8#)?ۨVaTw-=lۦ< l[Q]F׵¡h=z *)($88T[+,8|/9q {H% =~n"V{#'Zۗh75FEMut,,.<臏H.'Za̢7쁶q䯃S<t6X ӯK)I@|VEp>J¾I& ڰӍÿ_`i6.zs1S L =äZ89fR]70ONtu$3xkk)/ #C{@ҀPHAC0gM\r#6/Ɲ=$оCQŃ o'"3nCN,XÍ(r1%:t;431Y7k2Ffg0ܿY& SDZ8¥p!SUie_#`fجRSaC`^VS~])>ϽLʼnI*..Bb ]JZ̫,9kܓ!uЯ( ?mZ`DWhA+O{=]7ާ":xYBYŠGihk.LpB۪h_4 2xttC&SS{^b޻4Rv=²/7Kt-pP;W#on ">Ơ-}-{}ҋ9M!-ٖS Dv8 ke8q$I^)O/KGc;G6qFOԻ1lg{"í4cSVȀF˳/<ۉl E]Eh3ACMpD]/bOl+4yqxj82,[#Ds'G y -܉քV,jafmy'Cpv-2bUqQhW)n0)1i`=tv"CB/_=ŅGHm`bkB>ki6}ph n_wW/0q )7k%4+fC7cL<!+0'L`yuo_W of ql 6,7CŴ~~Ԫ@HWH?h]=lb1z< D}( :С-><|D@- z_0tq>Dki7.EExm52hʚ`cr} %OW^_ =,H? X蚈{0ZV1kA" KpGԴ3ńep+S!ct>ϊxp!KDX== GBQGp!Ξ_b}jhJf8.$WgyX`i8!8X`kWvF?|b1gT_ I!êyO;8tz 3NۺXՕ`{' jW0}'h{.#أ5t=kPLxmmPj[D/Qm[usٮ)S-8}8kb4q RR&:ef ZV6biQm <,ۋ}C1$Wn۩|jho0vDHλ"TɜHÙ럐PޕhxDLL$~;{ %9ReJRS'Y 0OGE q(LD9d !:;Tu6CjLqI]~́:I=h F{$[ XP-sQ۶pgC/٫Nc[8!ZL\kO߄BKZz5~))/cД~yǣjiܥ_yQcfn$V5Q1h tX ,P!t Ŋ&6n7#> yhԙ kcCj Q΅8><7K6@=.盏R}O/ğqf8?ȕ=WxМ_ac1Nz]quMFx{m7B[z9 ˡw_n;HR #]%610BFp{"c\-m|MF 7p$QX2*kXA#!*cc@l;+8I* Хn ¼w4W`_:xY0k/DD ^WYC$?@R7 *Y?m.F|y{MjMq`Co.P B]Q wZij!.$ws*V$ Geѽ=,S.'{h~@>%`يx*VW(aҠqlN{вÛN R8/YI ’ga ?0]Y;kf0=`% ?”&=x pBJXZzFVJJa~ф fI't]n]8|cBG'xXTdY%'z#+xo `js)oojᖇ(~LRM5?l]waY[{vldh*0!Wz+Dm n}r?zN)/2IB >V^hvZcL&J[&8J"hǯᗆV$Ơl^Q,6C<<1јBl/?c]+Fb!&-x|66Dy !zG#^oA*"ڲhY‰nkH#kKCu oNSx!cxTڿ fAfD IU࢓~"tnhN3 +iU0bj0FrHfB׀8Tu4}{G3-%oÆ'$`d Q\ :-y83"㾠҇`)63CPx:\}$w7OP|@b=U^W`xRz}#V`ި\zط`֜ 0 tK4_++X`6.Bc\x/ݘ2oy)[zo*OV<\>0Wnyrmś,cϡI=$7$"(ͨcmemYQr 蠍!1\|fh* àG6dy{Jm1U<85)V܊KxoľI^x^.yY 6}Œ&2ǩ\6\ x8<.fAޯЙɺ fWLfꣁU4!F'^"&6 m(.3 MJ\{/aC E{u[ƎY{Tq=h-/Zư+&QbG{O+\ lH)qb"he:f`$1G'}2l`NW_@ n&Qq8Rw2{8 * ?x¦fxWkC:]pt/+ 4UO<s2~^G#0~Xt`pr@2S1ka Lsy\8<DSġ^زBFYtq1$] CM7ԁ 6>Ƃ4f܀QO1,4'ZQ<{ Ԡo-롒ejd k7S3nUE}5qmW ܿ+'<*YC"Lƻg;j>FoNYRNBkhf d|€&=vy8?]gcb nj3d=o۰qTFہV.aǹp!ڷ 6@{b͜`d(f=hAl(m7J¢pWFV[Wŕuw SXW*sVR &ı^(C=sLli/σ1R~*)40bc_ !1ۇUCٴ/ع= Cp>~ĕ),Zpa gh vz- m+I}iC3Y%c=xrxWiB;>e&A-Le :̾^ghx=f<$apNysF&6O=gilU ZMǑa=}'cց0[%8k {0 ]m);µg-|x#CqEY@%ِ ±h#q1^J<.%룹9Qӌ?C[lu XKMA #hٯW0=,xɸ:ر%}R*&b΁m58#g?ѭ1>~+hap5A8nq&&vhiRd} h^{ ;rzs$оƨN&Id%-`Oع?B|Qk^)ܾY?:,´v!}Fl6l($a4 3Er"\6g6|t(M{NE0~d\S|z^<tbZ`2M0ߑ(~#-_3tpUd]>qPO`:7ٻtO}`0yX(5l(W~лO\[T*neU̫|0B"$߯tÃ1mG\a袇oSjJ { FYF@7?\Fn^֐$a!|Dž8×hs,_יЬTPT9zd!ҴXqɈR!k8 51ݯtCT2ϵ a1m~GrB8GdyQ٣̵k*~0hÚ~GZ\Vc\1#zKǁFCތ[28& "#~-3osmu/ŝ* 8 2G=(7*2~kخ66e-ǚ_;=x3ܱK&2 PSCփ~Xg`*^Vq5a$(& 񳟡i.cFFCsnY+hSsř8vfvM7yKe~I}b&?AzT$$w@ټRtZobBxɉq~jH| }l7ں(U)Qͣ{cSnˁO}zaŬg\5O߇MͩcBr,b匁SW~b"=-SO/hY ɒ$ %__th!<5ldɔ׼.>SWY2TQžȳFKu8.9.˯>fϗ?՝L!JɄXB~E)D<˕Ot=ށ'_$#&A-]z< MhIC$URLǾ _оG du IDAT.1&7rsvcw׏9Uؕ緍!P̪A|M"P~ ]hh$Xp2 ޸Z.,Fgb*IH1jH}&(кݟH,[ :-x%hCgKF]M~6JBqI25,/:C ChjU1,C 0ձi;O۵_0h_ 4pܹyoGC [ԕ'+6%2@q'PZqMo%‡i Q1th0.S7GpDHR1ʼncYZ8\l-{vtl"P4 @+BVZCSqXr 4.%2l|+T5)vu>"I@Zq%H 1U?S*d9'I@Zq%@nc? -\B МRoO҈@q @8HH}1@ .hN㉧zyRiD88@  D"@ Ec$Kv"@ D"@J2h%D"@ D)$ЊTw1D"@ Dd$JrSۉ D"@(RH c D"@(Hާ"@ D"P@+RA"@ D"P @+ɽOm'D"@ DH V!D"@ D$ V{N D"@@"@HuC D"@@I&@$>"@ D"EZ2"@ DLZI}j; D"@ E "d  D"@ % v"@ D"@hE;"@ D"@J2h%D"@ D)$ЊTw1D6rO7,[K D"mH}?8@44)۝$~ʮF"@(6H"C@ @hYA D~@$Gmp2>,*Px#s_7!4uRݑe`jgF?ڄc5534[τ#O<1ҒShBG_%ou, û*$D"@JohB]|X!uK[ bKzFy;aMg8|ݴs-NBnf|+S lWs%81w\ rQ8BbPDwUTX`Xv c1))M`ec?xc]+RQV^ǜLKO%u{"y1(݅8hw>kCZtW@+X:"@ $MtL<3v)գY-b,&ӓt]L*#5r^~!,f<稳1=Ne0B+_bP~>dP!1my;_ʫOz#wpsI,t(3g>IZƥpuT,7 D"'hb܌g* [[Wb=z^ah4hn}AvzU1YMk/p^s<썴ѯ-4tR*W`~{#=ooHʀ#z6e7N|>eJ`#@-[XrS>a8Q(m&:԰F_*=?Ž)CNָ+,JCGإI)̀ Ǟ-&ჯo}=IanMм k$={Fh&ս+ Xq"9Z5OξNJPNxYaxpыY= ]woD9_4n0àh{KelHAŅY0'c;0\KU 49!] K*<~JE/dcawԶ5'b`I2){}4Z8bX皰25/bؿ7qثRfV ':bCu F5E=I4WhVT D"J=Mc~Ńws;YXAB|~&/] ~εzep}x ( N돦e'>MϊbMKX.TLYs_!d}yH`τ&`G#su+2Vp(}Qmճ(ër/Rںy9c|k'H> S}$׽e'+F'ڱ!~OG3gVn&*92B6#쾁QoQUd.U-#ꜾuѲt/Bx,??Xb{'41X6pMyN}9kTS":'@+():"@^JEp,^ y9m{<,ͱF}\h/']Ӥ¨~sLk/o1x4Xf1'7%{jZ{nJ k:cYeoWvZȥhKַf2^*G{V0ǘ ٶuw8[VMlv9qqg,n!_L3^MmlNHCo^Y)y)eY&,|7Ehc۸fR`?4˓0MldrD,+&lFa?U OSaKbv۽ Eɘ1wX`ץZ43SiGzP2O+3À3Rs'9p=h\n7LדOƢ@S5Fs-z}!J.+TŞvaZ:nXblNtBB5q&Μ~;_O \{p)O3L7S(pf?)OPONXQEr$WL|N+^)+BTg-͌1r=);0 2"0HjihwKmcg{>-QP;0 P zҬF}v#OqSƻ!0aP-k=#s`vU9쿈yYph?'#D"@j&I(_YT@/pq4n|JauL:⎻6-RFYTBRnlKg0,8~ VyS`K3#zf7I*L8/QHm٣M}^ qqr|8vCձ(#?5#^pƳu tTM:|L4 qeF]+[L=ލJ& 2gOTjٶ:zfe_K}y-saf !xT$ Dl~r K.F F?'="(,מ`%ƞu.T]O4,fDRu-; W8r\Ղj`VJs) L*rerf)47@H2zЈS1jW8v%ܱ9$O7}Ґ& "@ ?wh8cfA 4;\zピELSM܍ˣ(ЦvD*K@z" ~¦W.T7]LTs3 9߉i E;`f\te,6l)S]xg\)?w(Ixz̘`G l VZi)duEH LDž8jZlbm)в}dy:J3B&X[{U9fM믳y& ceRƒbLE:&]K4q5C5KC D}!]`쬛k۩.&8hgx_  Aֈ^;*5ShE&MENL0 9A7R)G#Gv7ƫ[o0|BTA %\V&BB |23R){$Xޣ"%1?LC@cXUKpk,&ّy;5?9\NŰ"@] !5 5 <>:Hu3R5\Eq>X>Ûe,c͕a5 =a m15 oa  ;M̜n-lJI DhP ̖f{ Mr/$X8EkiXlY}Vn'peu Uyc,pg5yeeeO1eJR~Jn~5 ?}w];9 ]sLVdUN-yzr ST)$qصǫ%&7å[ѬL?-kܣ8%L:܋ڳ<~'Da+{}Q@Q"@ D 7$dJAyɋtz~`¥S:Rt&yX.M I(B%&sց扅HO0#\S" -m#)@ Ljx^6֊19O.й=k:HgB35VB']zFYY0sW"CTD @ Nj߰a`Sn [^*Ғ$512)u$ޓT  D"@"Јg>_vBjӃSFgB3T0 7Ë25D&0n%ExOcEQoi;@U !Vd !D"@~B$~X q xٲ kifMIH1F4a0e8|sa{'%[x̦ǻ/a+Mz2 'N"@ D {Z8DŤI_T`~$$DEsuKð!A !4`nBCc;-6 4uQ{TD;tI D)h4(hE'"@ DPH L 4D"@~H}?T2) 0g>VGm#D"@8hE<"@ D"@Jh%D"@ Dq$ЊxyD"@ D$JN_SK D"@(H" D"@(9H"@ D"P @+D"@ D"Pr@+9}M-%D"@ D V;#D"@ Dj\r(QK D"@ j g)$+\ SD`zМRowS҈)^T( 2I$KLB$jIE4; zyRiD8(ȼ'Vzl, 2aK56&EH} =JH@"P dޓ@+H%ILE!S S<4"Pdޓ@+=I6%IL"$>vT ( 2In$K LؒB]$"I}1@A$E@A= ݇d] "P [pT-" '@sJx'F{ 4Q|OK+!24b_܄_qrUi_>}DL]PNWH;t" 5aC^>OȀ&J[:ZGh~} *JSp*THv0-}ABM'QSLqb8>y4> IDATɭ*YEqs %("ba_kwvw+vVNP)΃v (=w;3ϼggo><үVSvk?!@hrj娏1P-6}D6Cf<+SقF cv`- ._;3q?^A-LKB:V#ݰAjxJp"t}3&nhn\!p,1IfaX^Ic*O qTez-1p)X鯔-DB7*ܒ~eBU<3 T_Oi-ZJ6ZB@V@ 9},t΂+z^ xi,~8ƴppo_ZL&}_aR fZ/2pAXT6_% m&< ױ W PlWcV~w~xKKƍi17f7Ǽwi 'n F$MԨ'Mx5J+O1t=c>^,퀓 OX 9Chó`.^s-ڊ˽E`qN/KPvCJj H''a'Pj/n}Ʊo`Ф'87fzK7,{7nơo9z7u)zYKEHyp ga2Ơs98epwӁdVT~]Ebب+4FZ'`!@K;ae~Xj*4%liGG˦^A|UNq*Y)~?VX>te  rOa =.8 $- 鸹=D׭hygS]Y~K`N>L}{ 6'Y:p3Ӈ\nH4@դm[w, 0xlC9ژ{Udzԗj-"[^ԕ paNry%±;^{ፅk7"8z`@'ȃ+`*]kض>ً<st' 4\ja'q7 sp"N1 9o}?]\(6C8}|^ 't6#ܛ_&,&ݻvE60œS^8~®ty#p03?f)ؽdBYl:"jutni-.=pC8sR ݗy1)Hö!qy>ݺ ezq9|Ƶq.u*pK#;8􃍅!GA9-c\+ ;LľȌK,kX@)zł4<kO@)wDž Xq^454ҕCսXu=O[ c|Tk==q)<Z՛Z- ǖMŽT[ /Hþi:Uː'0^Ldk1[%Jhŭ:vC& q;`xc}?DnӐ nI8n;y+ j ?A?hӯWvVYZqqrt{5;{b?XM309L=h@.,Ikh4c͖YR;..I͵5PQc{rtcHڨ<Mظ/S8  7,ՠl½{-P{pE1!-yh;L4oS0lݓB)/Q}tʾDMtaǠbCۺbR} N!{PO\o|`vϡCAWS/dhkc˴`2ڥ ңk24a/xs ۰fN~]'40U&)6,@G$Hv% ޽ǍS6p{M%,n-:BJVxPј߲;mZQeIT^Mb!PQ-/تf<p~Mz8RM}2 X~T2qF.0Qt/x*ʥwYxrq[;lf`soyA\Y@sjӺ?ɹon]p ƒAK; KCۗPE㖍-WާB g1oe4Bٶ8rnw`LM\|ɨްro/{Ht2+Q86D|[{!9}s~jnkOOn(Q$*ʱzXv?Byt4P|#=`#@r%. r`vC&x_Lz+5z|~z-ʃ?M[s6Zh!%&އBŨ6jy9aԵXŬ?K~i36eL^ۤ_" 4ir^H8~O= =7jG=ѿ2YUłf fo8.7E{םNDSl~E[)sY(=j0lw,zN4|YN孩z &[pvZCqhhJQ 8m(Lϸੰ3R9{ $!'aO!9]gGg!0}*zADTоMsPK-ʠq6\7fbst(d%'h.`:V.bC'PԄpu/,ړoiqLVG=l1};ۋ!ˇ0KZHҞiKh SQWD9ԾݻaykQ[ŏ;dy*zA76J ~Wl>QxֵxaHub\(ıaX?/_ŠN@sE=̙A٢{֠1%CeT| -c'FؔJǮ趿>86JU6p]p j X4e,,8p ;iG)[1ΘXQVRb&ZMrL\{Fd@s~Vh_>5hb .O*E" ⧦*u#;5\9HqB7(iR" (H%pHio럪$EQ~ŝ%ubgt(R{t'!PZkNegpEYBy /n;Ezn6UPjyT]Bx >JP,b}R͟YϣTzT79 L4%՗2VY邴f(Œ, ?=M!oK*y'7WV2H*"* #(Wx8j04ШAL&'P KaZ6^lB$uN iI[]ZΩP'"6m#r,D0)Gx I )uaFf#7}{,E]6 H]ιW"A p9 ZT"ӫVjQ ˂Ap#0mu\{9oA\~~J7`TtvaZn8nUtZ{zKt__`4 aGcn$'&5rqPDH_ฆ|2Nw5gGbb5\m=*\$'=Z6$ʈAjh*JJ#А <ѬZ |:3S?haHh6NREn4Nmۋgnq^lgG7 O݃iOW#}| #=`;p5SEyY8;#b|$'qn8N@>µB. wnq+4h+(#p'KY:aŚOC]]'w0Ǽ< zNfX2,~Asqqt0-A&Ny6 1fu: @=9SM-0łBq"y*׳&vL53x.:l\L`RWVq ]Ág;1yIr#΢˘kc[>5pk-n{:846(0[7Gځ[ O.{g,͔.ggr+]z\d^ⱩVޞz J"&Nos4 3)`.R !433Xo ogo0p'f4cWI/'EjOS;;F~^c̓T]uܺ?5v(@[Rs0oBV~uNg1PEON`_e*Zb_?C>DiC>ܵ 9(i<ō_KjTL*ZKCMo~N>[A#;F9Û-wafo|[!5zX@An&.\Hmb 5Gax&6L\O<1 SC)*T@}jZn<` ?oˈ%~9Cѻ8EC0-tnwuس{=]+hǔra:;cO`->-@˄Q1X:w& kW{hba1{#ms̛k6i9->u0H LO spQ$]yRKNگ]4!P gc$cpGHgT[B%5Z3F;(`g?+$qH5v g̦0Sa;̖7 NUcbGT}5DG`t jcy{xubX8շ*T8Øf`Kz*AZ`V[}>'ʂfzn$Ӣw=5uQhoZ=e^ , p }ƿz.W1B92Hxc#K,^1̡ѐWW1ag.Ԍpnbg>NlUr#ژ1 gd 6@o$f.&]*nl{I[#i&jW5ܥgxQ($Ђ/д2DjJNg*I\ µHnK 4^|2b55P5!#\. Rv *Wzi9PE.#&,yec̄dP@cce|z5D=ŢyKg$j::T3L8+oz% #CT>t?<`8 Z筗ѯ>#žiǕp^d&Dc{\kb?CQrlرg ~0!3[!6qۮ>DKV(yXֵ2>`ļQzfN^ N~h0q/Ų}p[vz+ZW̖xr!@T(@;4/ U/*Qqdwo6*GLxd4of&LPG~']꾧gaJ.nϕ_'ІFHfV,hݯGv3 k#)݌6pi`D$ʩ¨:B.67b&>'ae.TM_hG߸ ̢_xXf3bj=h/ Odo?W0`W([q‚4. l[}X+}/Afpqh5i&qx2#Xp!#*|A+#M )y8p~`i0Aڱ*XL%{$e9}u~z FNƎ&"fFy%(gOߔ DLSP~^3SSV&DPPRCLx .A gn^,?%l ePuEf l f-mauwR1wH-(e`dъ ~<n% &`uX@ƅﵭycc3{Bn bplif\YX=j,[aŽWCJ`VFN+y & AzX<(![

    䳰q,f.$ouQK> Dj˖:$Ա}!K]ddD.^Rz}p/nLwLLȍApnlVeIe"~V&Ш0U#GrWWSV qr'| 1 n_<cE>^ۊ'0V mx!8&d?@pAúK̄6uG^ŻG1s(A u`{D'\m ;#c_n͠cDeFJ5r)4z4Fʃ'Є =3Br!F}bny46_RU_ES1-T +w< ۯp;GmyQ:^P34e>*DAU-S,ɂ ldEZ´O^ \ u=ht_l](ׁ}J,Ɩi<ύbqr=^>*FSw!?a xzҟɦX "(|@Ob`J_?tsdu^TM f:D \ ؚkO1y*k7*g;J#߰x']VWCqs ݊V}6&|)<4JAgὰ1Y62: j\j/f퉢 4N7L9; ]-^j5 Xcm~\\29YEy6q|IIcZ$ ` Ǎppm=κ [c|mڨr`Y:ד|?@f3?ٛ[ 8ڠ.~T%B0zA=%E&_+&k5yq;m3̔>rpр %uySVW'1׽ʩ.pɁld$EcϘ;z*z hvc,@dc?l<.YPzɆ>_w0{)tm$8's @5ϣ0ڍ χC-c:SA^f%Zhv@N.n, ?h{3ME av#EE$>?3P+xI<mHiB(ʥ@{oΧ=hbBP6jڡavΊ_LS w'~  GT O(B;DPavcawɩ:5A5Qd񎏃B?\`<~t *|-{ alKtWŻ ys{#ОkO16TՄEx]+Ԧ::UX9D']kZ) v᪦6N[L~BgVfJ\\= p|m l,/_4xt65T *R'*HklQȻJy(OEdx: ʬ6`CJ<vb<(@ŭ3ᝮQEzȌ {g {^@ ƃ?/և^r B/=K0vd-X+pq:MCъH _0&. 4a @+Y1)T2q'].z,:XSJ"['(!|P 8z3j:Y..{mf LA[=g4A hE/w09 X-&z8t#]PW=LN]F*hodXj"?u1A @ ,E"Z;@X"yx7 LP[Mߕ$( zЂa =<ʓ%Cc#c•2b_;75Wbc F cs.,q elP,4 `'c9eZ{,7'3pq&WcA$`НʼnB2΢݈Xt{^7DתFoUBG=# }RaX/0QApp&rlM0S9pgpqA <xp-3c 8z؞p(/{$X ?Bޯn+_)ʚ,Y!}GjeJ4Ο؄s?^# =Eڸtts5B>٘ѸhUxH8ȆtvP0ץGGeAZ9!{5z̩\\k"@&[!u$}A}S/oLw9wmu=h+bk'c:-uFݱPі04/k ʄJyИpܘdDĒŦ$]eu )g4d|VW/g.}#9d{Ml g=fY+hZk\\M@wS~^T<$AD,& l7lxdڕ >bCjȑ"F#{]DOeRԲԁYzR!WR{T }W;v![逗 @ KT +/_y o)%5JZIO nfPrkٙ>3E)XrP(*Ē˕8Ev;!@!@!@+D !@!@@e&@Ze}wB B B(W@+WA!B B L<!@!@!PVC!@!@hyI B B B\ \ 1 B B *3"*蓾!@!@@"@Zb !@!@!@TfDU'}'B B rEr5B B B@̣ON!@!@hj81!@!@!P VG B B "pcB B B2 2>;!@!@!@+D @IʷhX)N!@ ҄@"@ 4yuݿ9?@_9S!@@"G =a0a u2s ` l>‘#amS ib3STѐ&o83CCi+l~֮ +C鷖|gߧ,E ,J %U"dCH̜dGw(shԢ6h hcH!@!@ @&r>k5-Zsg?{Cdџ-nv8[y;> *Wbc2>A2nL Ҿ`ĪOQƃy(Z?zd?lk[H⯣?J`:u/Jop;4 F5ׇ7Y҄~  F=}o?!JFzЈoS}y ufyM"{W6+ih%* !@!@hN`pOP ϫ=D5mmjJU^^(,ybZRXumPr&<Mҿ IDAT;awI/6Ng+Q{,|-؎h_hO4K886 lai<'(2p},(6ȑ, /.7m|2DIǟF!@ @K X"A{XqjfF0"SBcrַ+4!٧d;|DBy =,p0̾O{^~ihw 6=J787]AKhת&[R9.FKJƴ)tz{S&a}ꠍ }PFnb5[°9DA TWD}-Jɥ~>{m ߰#Qԑ*Єckk@(cI3 <K\FR}~,П1U5X+b1׭fhXzh'[q"Y) %>S,*G=BI0u #@M2\j*FF)4V2yk+ѥ]}heh3ͲbhWN`hQ !@KmrwX^6Vv@U"D Jcɣe{ȱi{ތw(R}\ K&m&H Ǩl$2 xka VX1BHba?yDhu ɤsq^ϟAz40RΆx&4X`WA-t[#ZcI.MeuL8 3C ,2%3]Ua_" 'ǃakĊa (-\x/J S=h|[5º(7F-[*(9%?^yy{uDwGJh|CR>ƌG.x zּu7,")yH((,uWceTlzQGi:Af:1X?Z2z(qѩj&-}q>h㍱oHuبq|CtTjb}cp(|| PϢcDZ'B O b@c&#"`NVƔ-d %$aRK =FHU١Bċ_&Q]RyX#-fi0KnQVB[k(K3s5@!sqacj6̢:w0I/(==u3 yD ObXr%EdY*#{ GRHC~^GeOHAdFqrc5 ! :JhoN(lP-ϖ,x V0dpK@ӭc}L*P+}jXX A!Pu-n*b? •5dh* ǾB׉s= 뢜߃6>}_/IoMI8.reB{"\9 q"1'9+N  E0H@c5+)46{`~eÛa#qHzd'ЀȐŅ8JH/ 6Bgm-(Д pa\uxl? XXn!ǫEPP"4ۛFU[X_;alk l<{@g@$۠vWxL |IΥX*kW)dS-0JٙJwaZLZyI"6!@ڳyL}IS0KVm}|uۇׇ. GA҃F;ϳ"͖Dm{]ԢOS[Dm,/HnG4N!-qp9P Ɉ0A,hV;]$EDX7D'fy˙I q2# J>xlI q,ZhuIbdD|Eh,lu-T-rH+Aƃ&̷WL";T hLcW}+Ӓ!H{R߼FLb> ]k0xuCAO' ؃VЃ'DA~dːV0х(M飱6,h@rʩma=h+/h]`}#%L_@btԲh%t偧bkI֠=R$ 6ƧA\Ȧ;k CE]Cusx.$ !@@y -Mr0 jjVȋRpA;<3/s F7 lGE^`Tx ƱW4mml󽧋:LEZ\>N^xP }'@ahG46UGWrD'dPHDŨTu yْ q,VP[Yg㪈 c,,el VxT1#pQA+YP5Cыiarc-T4GY9up'vb!)&(҃&U˧,$^mϾV}ň" 4 *v0AJ>\)8mYWŲ96y?R,BvOCp[lUǁA0JDU/bb[VGt' e7} 7-_x#eHyYFKg_ 08 u5 N lb4Knr1Dl B haâ8N.V eǒ||Vula f/ 7{?žPo-u&<1b>;븨6vY:A L ^;[u.,kck]Xt.9geYŲg<L݃8͚=$R"s]ɱ@9ie.)QZ?6hH D/|{Y.1w-]m9=9!dU4pTzEn%IF#5MKY; jzdgK~P?K?f+VsՁK.)MACa죥Dt$3%2iT#{ͲdY .G#2I#}r1AMV)%?ɪka7>nIϲ8ux&a˼EM"`aau@v$sy '@-[ t9иV(|A֩V<~`M̽x$ L"@ ?@@_~.}h#?B!/>tx+'Ɉc&\hB#sI^Vl zv.f/F;\+b^ Y|&$˧-|  ;9B%K/+~'!Rbξ[Y7]B>DL\B!D~78\3x` 5QPm#Eڮk0'9dV%@TbӃy^}|7ϲȊ\ɻR4 4`` >>gy-M=$Њ[=D"@J&p=IYSqd?2E 7}ޮ&Ob\FeRWU'+¦٨&3>y1AXhBM*L$D"@T V}|;vƼԹZT ^Bܪ&ISmS<)7" $T&A@["@(T0)+"rT.OʍE= 4UIDPÖJ,$1U"Lʊ@Sz1JH)C"P (Ij$K E:lIarSe|)\P{hPdc H- XHbD 6@cґRDPߓ@+HtؒBY$"I6@cԏJyM@~Ox!YW(aK*#eBO)1O܈*P+W  89fG҅ǗHR3އeu8n'?@~K?tEԨf o?A.q*C[6 UFvԺ 9T(ts1%>Ub Sel;6*j?-}~<7iš".В_E䚖?wn!(%z4ˑq|P4o{[ޗ0Z|񥼻cv崥ר"Ptr;(&Ǻ  jJ7a̦ܺ -5DDlX2o/1S"5{zt W^&y_BƑ%ҍDPv܂~cNm\Y:)~0 dOyOr,\P/ЂNqυ@yMKMK;6Wl"6#F4Zcq>ELƾ`UFOȒ3Qgnl[U!&"PT:lpjecђ (ucFNj;0g{XFp qȏgQR,E2&p6’]Ƭ<~E-8zɊ+6c(+}fnRhO9勔:qmMC,2 c Ql,<`)+g+M, _kw{ 1g ۸;f,l!)ULqY%0-^7_Tr̅s` kқ6@+k?^a0SOŝ0s6oMGW̜4mk[1jqh\WǺg$GL-*;o34GWxyKl<.Vȅ{1s#BBU^\*VςR6B )X,&,SU7ĜF8pj|0g`Vu|L uqzly *8qyӢt T_O 45^A-ǤeY:2{ tF$>/^jvԬ;׸`p` sʼn[hcarg7OG{/ @aQگoϿ:Ut*-;&=:vDs6#8xSй)ih6x&̱qA.hkۏf~,Sɫ|=h ̥$eZ1WH;{[a-,.ZվcxUgVlW {cDi.^0SEBCu'!@~@igF4?{}Lbc{Á+p;6JtKV$ pk 4:8vml5o>\߼m5d=CWGnO< 4f@=Qz4wh?h0 y8RC#?@^66&,]a&;f sD;÷T8օvxkp>6<}~8st1dT.tOTȇz2 ~gmn, a-*z2+Q(Ǯd6jt rechpKҮ"bT:.+puJF֣13R"``jۊBŢD\ܷ7OhwƩSXܦy5QÛs۱3l,z#(ׇ^,,7d76+A]۰sK+a`-cdC>[% hͥE[N{+|Yufjpl\ /ЪDDékX8n1ncjt"32Ƭ~<vp45 =e| ++`X Mbe08c=h@L,$y4 4`+2;>Nj/Xvply.&`L&Y{34Jg:lwh֮x~釽ܛ4dca61sVp IDATd^ViBŠwrAfػ|F5{~2|It%bs6T{6v"{%eA]{\l}._r: ɦWS0tkSav-%8DQC8n 7e== Lؠ˘=g5 {..JǦm݊ӣFީn󹸼&^( H)D̢P~$qlm Qp`ne;1:[653u}ȸTZp%>sc`S7@zV/~"7Y>95,ƃ7M[~2Y!>-C\UV̳9(S&w.%вlVyd űKEqv%mE~_& S]Fj>?ٳ-nxbjGs]ѻ:o<†50Og'ҽ:Bُ3^ZfsB0;AY$?G^ &ejxKu]K3Z!`vqٟ6tXg2 K7NKLZS̏pa WB#41SUdAoQM@KsF9Q)lд-ptԸŀ5g1a&{K+pώSVãr- Kt Ţ0ogҘ#ƣ%%!akOB5Z]016,Ɏ+N߇(>U5K 5 ?cnMtzcP]x|رgZ03]Ǝ 6.y?'ЀWW#s.xu`9ku8>NV ԃŁ@h S/v3a.ov2|<`g,?^i2 a&9׫g4<u<ԲPUxwO/mjocmq\s!F0bO۰]T1cP\!uzêBU'> O7i1߂5 ~1AX?u}OOy36sNVlfOKpsN\'e}A̮70b|Tx1]ѩc%W0=11﫰<ϥt^>GVc̊cRwUSZzVz$UOhb@YYJE84K4s\i i@_G&|"PPeEґeH9*ʶ y١a~3644=rƲʼnC%TZ2|n̒4p}yC >2Ҡ݄r,`GK!!!An~><'9</>LTf"@ M@~2_TɼY{(%z4ˑR&Xʫ}BL \ʺD(}JaV\[iu_Ry* eDT"^e) , T:d"?E:,1,bZ8^t7(@IS"~"B7sJ*ς}O~g{hsUQوH%N#@L Nj&>U}O< Nj&E= ߡ E:oQПXbaӣJSʭf\P{hPdc H- XHbDyhR6Qʏ{hſB@[BP(Ti()#">܆@<˓r#@@~OMjl,% IL"$ДhR:Rʐ{" bO $D"@ D#ZS$fiiYJ&gPRnS܈)\PL' 5E6Cf@Lϔr,O)ryRnD@@SZ"@&V~S gJ9lԧ[S<)7" HB-Dk4ah)Jr+x*'FT 4U% ТSC (Ci8<h,8 ٤O)ryRnD@@SZ"@>giPWӅX H$Hi4QZKrT.OʍhPKd#G{ 55m3 pF )1ցӏXq BrSr5Ô6Qވ "x*'FTOh"~?yw9" Rل}5/<3 6PS-( r&!4S(56qiP3*F2e"6,|+092FDI U`cS:Ӝ2\D5*.CښY&  0O:"B F=7!9t5dHIQ@vcwgJ'2"mzal1 A!L CxZ).e&k(2a,;~e~-.vo>FC/G}WLqGV E~ ._' {&7ne J;Ceu4= %VsW+8 B_-}y6<9t6-#g44!p^dݬ#"syx0 Vmz|Ej!A _0+sJ˔%P^3${ ^Lǚas'`4ߓJQDchy%U2SmGc޺%e'scif4]Ih̿탌V;s+'KM;@e89V.BOШ,~=r6Ox';f1ƶKQU;mI|g8U QLz*Hs<"@OhIo_`nj9'|]vЭ 3%{Rq$<&.{n J^{Cp}3apk3psx6#Wͱ.WW ѺVi^~%w3By84c/2Z<]ykԏ6ӆՅ! !8Z:Дqw 4Lw(Q$ol"Ppd薆=J)DZ]Wc@\k;5"ꏲT $ЊVOrEFl܎|Y2f;r Qֶ T>fz_F:9 #[&qD߰s셽m[^pw@)onѸ啚ڏJL 0(+a|MVF̕5L}30jgKӺZ5 S\a„iRpt|cg<#AL*+N|ōtk?*`[oA'l>CqboCӹ24 D5xތMqή.aȣTt0tSҬ*x ͙pLA,xER46x@e<c< pi|e\GՍg`U0BiN1.u0&1 pjCܫRڗg'hlg|Y?CCIwru046dt( s_ Q/#ţ <]-`u>z6bjkazHCKphrm<;t҈}m:XX10uupFIxXֽ:(_@ M4!{-;jKMqK>{B[s (AY2LpU, PLm [e;/u7g43uxi`^=#}~.-UpaPyd|Adi#Fߕ[ G8 {-89s(aݥ9eԙ5Dɘ,ǁØ9pW$e/hUDy >בs ؐC Da68W?DnGXۢ38c\EPG2FB-ʕFċ+6b.^ 7k1}.W1w<< 6`0/yL5;a7V[0&[Lۡl&<1{͵H-; GJMT/hW^е>*41݇B)hd 7CxH(n Ao R,Y, T8z= [,95 i(q o|+e Ž:?ñqw[pW aq<:W2(9[ *[Fy l6մ]uw6F,Vv6_sl$ ^; ;D2s3f?Q)c6fxT#Khjَ =MYi ~L[KqnJc eB7"R~@>) +F/ DnLX\]q8<2 +7B+qP—ۏq%Ioc| .+Yatq!k8Z{t+'955MμoXVAcځҘ߀/h9@3U3 "LǰqZv9C,0 Fť]XR1} DhbT2p4|6I-cޠְ7B(%@ S˛+>̋NAݨS𖇂x*(?"@~=_$"^-Yԡ/5ѭ;),ݿ&s8D/V 3?G^AS9@f;#={b&PQZy# A?z h!!rD@"P-pdVy3d>0'P&١ ;X7˽-SȊ>Q't_bSʄq>N>^Ulb1{&¸잴~k43\ULϥ9?O ~(yMR-\w 6r 40!ןbXV2zVZ9~v.@ gE{*5vg0lQڲ4ԹtCB$I9ie8ΩӸH\4CPF(LÙVx4#q}9$Fr>Q)Qqb{5>V?ƹ( 3F`@"#džpU1Qx3[i5\ j6p-'I(i@?5I )7+ d;N*fͿMRo|Mr=pqpvpMa+d0^ Z8T1$A" XbL] l- *CQVi jqF`o`kTp ؐ !T}4)0* =n:m~ mRKC2l>پL!E 90=::{^ kZ)@m5VLjzfZE18?!yOxbiA0x+,շZ`N0Ɏ|_؏^;+;sy8Fq_KJ>|ŹkXm|U3X{_4wZњ&5b) Cƃ)$%YO7'h*.@ct:.yeWs~ho_`S#01X g:kO$ ~n(ޣQ^ghJB LQ,Y*屰"KkXp烰蝐Z%^U DcqSb0^45G}+&Q#/g=|g822xlH)qb"h U0KǬˉXnOp5)<ډmB ݯ W_Ot`>9 dIDATYMu."ccpѷlI PhW3G0y g>'~g:`[jʾCMɳڱoΔ ǔQѣGNkT/* -%a0 CO6/eqlelvAY{U  aB*ZDX2 niߍpDŽcX4X(Z 4>u)Ȉf[؃s/9q,T'heh92w~r>QB8WwnsbLYN > }HA{Îp+Mz zf,CiT m&_#ɃVFoJX gBփ&̆1 rBH, pml-|1?`ΰ긶Դj :h+8bZkK|{AWb3a̪745 }:|!voػ=󁭣w̺)yЦ4kds[t ߷]aKzb`uW Jp q0ףt_hSCOb `[WV"6n 9R6F$P( Ŷxuj9_㯆hb.;-X=o31Dxvu3˘&89U2݃W w14qCPqDk'c|{ng0سb"7LO MmmN}l::/8Qh'/i81LYGIq#VG# I±'LË7ZI @% Fقŗ0Sy$J.L98rI[I>NN`V^8sPuL8r#:wdhhm\-/bcZ,9' 7Q4{{u[h8+~|^mp@.|G-C| WZR`Wo_T &;0rFyN* \QJuMqqWTrw*V| h9>6:t?nFfOg9Ldڣ81󺳦7&J=3prO\:zps/yY}d7:($q swDӲ\3:ygްe-`cN\E3~>b1{ |j4f^ YڢDGHEaAP#=^_JUcmjYVS_ 8.^]!կ * K|}F۾d Sͨ3`-_}&4I({2=h^!kdD䒔Q6NWF9Xci{ɽ Hyz>}D4 gO6 6&ߒKxSֶL:λn0f) \C@_i%e72"OóIzXUϘ݃.6dOe/ޡaU00DQs*ACf!X lS5n0<.%:C׾ɵl :) ڨc)CS\r{-,ob*f/spEV6x-8%2>OhaA1X'4ܒy#}NDF쏭Zq-ZcYnE~]Fۥ&Xg qa/rf>lhcS4f9X Rn6ƔO0$<[,ڹa]3x(5 őOhE 5+A 2xg&L!!00o9oHKN8^"R3xNEjQJ2"={M: 4vg[u$$"(@R<;ܪj_uEVgԭfϢF㥜g:)6+yN\ײ3 9_ m!HSl)qҳ+p$${XCID(HK%=GMkz{|C=1/}C^!r-jEk !RMfA|O) P-~=6ԔU A l kBX+CB %ݪHK`oq<bTךe^ع {HQj"P)& 'I^U::5iPWUHcY Ff0ֲm+o[]0hߍMXSfab޾7] Z|ސHS>b##ΦqDX.8J!IU @Mjl$$~XCKPԧ[S<)7" HB-Dk4ah)Jr+x*'FT 4U%@am&?? -e\B PRnO܈*M D"@ DB6_S8"HT\%; D"@ % \Tv"@ D"@hŪ:"@ D"@J2h%D"@ D+$ЊUu1D"@ Dd$JrSى D"@(VH c D"@(Hڧ"@ D"P@+VA"@ D"P @+ɵOe'D"@ DX V!D"@ D$?T/fаIENDB`google-certificate-transparency-go-2308f62/trillian/examples/000077500000000000000000000000001462611535200242755ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/000077500000000000000000000000001462611535200264555ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/000077500000000000000000000000001462611535200277245ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/ctfe/000077500000000000000000000000001462611535200306455ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/ctfe/Dockerfile000066400000000000000000000006631462611535200326440ustar00rootroot00000000000000FROM golang:1.22.3-bookworm@sha256:5c56bd47228dd572d8a82971cf1f946cd8bb1862a8ec6dc9f3d387cc94136976 as build ARG GOFLAGS="" ENV GOFLAGS=$GOFLAGS WORKDIR /build COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN go build ./trillian/ctfe/ct_server FROM gcr.io/distroless/base-debian12@sha256:786007f631d22e8a1a5084c5b177352d9dcac24b1e8c815187750f70b24a9fc6 COPY --from=build /build/ct_server / ENTRYPOINT ["/ct_server"] google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/ctfe/README.md000066400000000000000000000053241462611535200321300ustar00rootroot00000000000000# Dockerized Test Deployment This brings up a CTFE with its own trillian instance and DB server for users to get a feel for how deploying CTFE works. This is not recommended as a way of serving production logs! ## Requirements - Docker and Docker Compose Plugin - go tooling - git checkouts of: - github.com/google/trillian - github.com/google/certificate-transparency-go The instructions below assume you've checked out the repositories within `/workspaces/`, but if you have them in another location then just use a different path when you run the command. ```bash # Terminal 1 export GIT_HOME=/workspaces ``` ```bash # Terminal 2 export GIT_HOME=/workspaces ``` ## Deploying We will use 2 terminal sessions to the machine you will use for hosting the docker containers. Each of the code stanzas below will state which terminal to use. This makes it easier to see output logs and to avoid repeatedly changing directory. First bring up the trillian instance and the database: ```bash # Terminal 1 cd ${GIT_HOME}/certificate-transparency-go/trillian/examples/deployment/docker/ctfe/ docker compose up ``` This brings up everything except the CTFE. Now to provision the logs. ```bash # Terminal 2 docker exec -i ctfe-db mariadb -pzaphod -Dtest < ${GIT_HOME}/trillian/storage/mysql/schema/storage.sql docker exec -i ctfe-db mariadb -pzaphod -Dtest < ${GIT_HOME}/certificate-transparency-go/trillian/ctfe/storage/mysql/schema.sql ``` The CTFE requires some configuration files. First prepare a directory containing these, and expose it as a docker volume. These instructions prepare this config at `/tmp/ctfedocker` but if you plan on keeping this test instance alive for more than a few hours then pick a less temporary location on your filesystem. ```bash # Terminal 2 CTFE_CONF_DIR=/tmp/ctfedocker mkdir ${CTFE_CONF_DIR} TREE_ID=$(go run github.com/google/trillian/cmd/createtree@master --admin_server=localhost:8090) sed "s/@TREE_ID@/${TREE_ID}/" ${GIT_HOME}/certificate-transparency-go/trillian/examples/deployment/docker/ctfe/ct_server.cfg > ${CTFE_CONF_DIR}/ct_server.cfg cp ${GIT_HOME}/certificate-transparency-go/trillian/testdata/fake-ca.cert ${CTFE_CONF_DIR} docker volume create --driver local --opt type=none --opt device=${CTFE_CONF_DIR} --opt o=bind ctfe_config ``` Now that this configuration is available, you can bring up the CTFE: ```bash # Terminal 1 # kill the previous docker compose up command docker compose --profile frontend up ``` This will bring up the whole stack. Assuming there are no errors in the log, then the following command should return tree head for tree size 0. ```bash # Terminal 2 cd ${GIT_HOME}/certificate-transparency-go go run ./client/ctclient get-sth --log_uri http://localhost:8080/testlog ``` google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/ctfe/ct_server.cfg000066400000000000000000000024601462611535200333240ustar00rootroot00000000000000config { log_id: @TREE_ID@ prefix: "testlog" roots_pem_file: "/ctfe-config/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xd8\x8a\x49\xa2\x15\x3c\xbe\xb5\xb7\x6c\x63\xdc\xfd\xc0\x36\x64\x24\x88\xc3\x57\x9d\xfa\xd4\xa8\x70\x78\x32\x72\x29\x1a\xb1\x6f\xa1\x44\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 ctfe_storage_connection_string: "mysql://test:zaphod@tcp(db:3306)/test" extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE } docker-compose.yaml000066400000000000000000000032411462611535200343640ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/ctfeversion: "3.1" services: db: container_name: ctfe-db image: mariadb restart: always environment: - MYSQL_ROOT_PASSWORD=zaphod - MYSQL_DATABASE=test - MYSQL_USER=test - MYSQL_PASSWORD=zaphod ports: - "3306:3306" healthcheck: test: mysql --user=$$MYSQL_USER --password=$$MYSQL_PASSWORD --silent --execute "SHOW DATABASES;" interval: 3s timeout: 2s retries: 5 trillian-log-server: image: gcr.io/trillian-opensource-ci/log_server command: [ "--storage_system=mysql", "--mysql_uri=test:zaphod@tcp(db:3306)/test", "--rpc_endpoint=0.0.0.0:8090", "--http_endpoint=0.0.0.0:8091", "--alsologtostderr", ] restart: always ports: - "8090:8090" - "8091:8091" depends_on: - db trillian-log-signer: image: gcr.io/trillian-opensource-ci/log_signer command: [ "--storage_system=mysql", "--mysql_uri=test:zaphod@tcp(db:3306)/test", "--rpc_endpoint=0.0.0.0:8090", "--http_endpoint=0.0.0.0:8091", "--force_master", "--alsologtostderr", ] restart: always ports: - "8092:8091" depends_on: - db - trillian-log-server ctfe: image: gcr.io/trillian-opensource-ci/ctfe profiles: ["frontend"] command: [ "--log_rpc_server=trillian-log-server:8090", "--log_config=/ctfe-config/ct_server.cfg", "--http_endpoint=0.0.0.0:8091", "--alsologtostderr", ] restart: always ports: - "8080:8091" volumes: - ctfe_config:/ctfe-config:ro depends_on: - db - trillian-log-server volumes: ctfe_config: external: true google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/envsubst/000077500000000000000000000000001462611535200315755ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/envsubst/Dockerfile000066400000000000000000000003001462611535200335600ustar00rootroot00000000000000FROM alpine:3.19@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b RUN apk add --no-cache gettext ADD envsubst_inplace.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] envsubst_inplace.sh000077500000000000000000000001561462611535200354230ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/docker/envsubst#!/bin/sh -e for f in "$@"; do tmpfile=$(mktemp) envsubst < "$f" > "$tmpfile" mv "$tmpfile" "$f" done google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetes/000077500000000000000000000000001462611535200306245ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetes/README.md000066400000000000000000000057551462611535200321170ustar00rootroot00000000000000# Deploying onto Kubernetes in Google Cloud This document guides you through the process of spinning up an example CT personality on Google Cloud using Kubernetes and Cloud Spanner. ## Prerequisites 1. You have **already** created and deployed a Trillian instance, see [https://github.com/google/trillian/tree/master/examples/deployment/kubernetes] for instructions. 1. You should have this repo checked out :) 1. A recent [Debian](https://debian.org) based distribution (other platforms may work, but YMMV) 1. You must have the [`jq` binary](https://packages.debian.org/stretch/jq) installed (for command-line manipulation of JSON) 1. You have `gcloud`/`kubectl`/`go`/`Docker` etc. installed (See [Cloud quickstart](https://cloud.google.com/kubernetes-engine/docs/quickstart) docs) 1. You have a Google account with billing configured ## Process 1. Ensure that you've followed the instructions to [create a Trillian instance on GCP](https://github.com/google/trillian/tree/master/examples/deployment/kubernetes), and have provisioned a suitable log tree into it (and have the corresponding tree ID). 1. Create an "all-roots.pem" file which contains all of the trusted roots you want your CT instance to allow. (e.g. `cat /etc/ssl/certs/* > /tmp/all-roots.pem`) 1. Create a `ct_server.cfg` file, using the `ct_server.cfg.example` file as a template. Don't forget to **change**: 1. The `log_id:` field to contain the `tree_id` from the tree you provisioned into Trillian. 1. The `prefix:` to the URL path prefix where you want your log API to be served. 1. The `public_key:` and `private_key:` entries to your [own keys](../../../docs/ManualDeployment.md#key-generation). (The [`to_proto`](https://github.com/google/trillian-examples/gossip/testdata/to_proto) utility can help with the conversion to protobuf format.) 1. Run the [deploy.sh](deploy.sh) script, using the same `config.sh` file you used for your Trillian deployment: `./deploy.sh ../../../../../trillian/examples/deployment/kubernetes/config.sh` 1. The script may ask you to create a `configmap`. If so, follow the instructions it provides to do so, not forgetting to **run the `deploy.sh` script again**. The `deploy.sh` script prints out the externally available "ingress" IP when it completes. You can use this IP and the `prefix` from your `ct_server.cfg` to access the new CT server: `curl http://${IP}/${PREFIX}/ct/v1/get-sth` You may need to wait a couple of minutes for the pods to start and settle. If you're still not getting an STH from the above request after then, check the status of the deployment on the [console](https://console.cloud.google.com/kubernetes/discovery). ## Updating Update the jobs by re-running the `deploy.sh` script. If you want to change the `configmap` you'll need to: 1. Delete the old `configmap` like so: `kubectl delete configmap ctfe-configmap`. 1. Create the updated `configmap` as before. 1. Re-run `deploy.sh` to force kubernetes to update the pods. ct_server.cfg.example000066400000000000000000000021341462611535200346540ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetesconfig { log_id: 1539264761076488787 prefix: "test" roots_pem_file: "/ctfe-config/roots" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x07\xf8\x51\xaf\xaa\x8c\x56\x83\x90\x31\xb7\x80\xe3\xd6\x1a\xf7\x2f\x36\x06\x71\xec\xdd\x3b\xbe\x7e\x36\x6f\x0d\x1c\x1c\x60\x0b\x7f\xf5\x9f\xff\xe5\x24\x49\x34\x56\xf2\x4b\x10\x5f\xbf\x08\x1f\xf9\x0e\xcf\x35\xb5\x8a\x8a\x8b\x30\x0a\x54\xb7\xbf\x1d\x4d\xb9" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x84\x33\x84\xfa\x1c\x30\xf8\x12\xf3\xe7\x38\x8f\x52\xe0\xd9\xd3\x5a\x05\x20\x6f\xfa\xe7\xe9\xc7\xea\x23\xc5\x32\x01\x79\xd4\x85\xa1\x44\x03\x42\x00\x04\x07\xf8\x51\xaf\xaa\x8c\x56\x83\x90\x31\xb7\x80\xe3\xd6\x1a\xf7\x2f\x36\x06\x71\xec\xdd\x3b\xbe\x7e\x36\x6f\x0d\x1c\x1c\x60\x0b\x7f\xf5\x9f\xff\xe5\x24\x49\x34\x56\xf2\x4b\x10\x5f\xbf\x08\x1f\xf9\x0e\xcf\x35\xb5\x8a\x8a\x8b\x30\x0a\x54\xb7\xbf\x1d\x4d\xb9" } } } ctfe-deployment.yaml000066400000000000000000000046351462611535200345400ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetesapiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: io.kompose.service: trillian-ctfe name: trillian-ctfe-deployment spec: replicas: 4 selector: matchLabels: io.kompose.service: trillian-ctfe strategy: {} template: metadata: labels: io.kompose.service: trillian-ctfe spec: containers: - name: trillian-ctfe args: [ "--http_endpoint=0.0.0.0:6962", "--metrics_endpoint=0.0.0.0:6963", "--log_config=/ctfe-config/ct_server.cfg", "--alsologtostderr" ] image: gcr.io/${PROJECT_ID}/ctfe:${IMAGE_TAG} imagePullPolicy: Always resources: limits: cpu: 1.8 requests: cpu: 300m livenessProbe: httpGet: path: /metrics port: metrics failureThreshold: 3 periodSeconds: 30 timeoutSeconds: 30 env: - name: CLOUD_PROJECT valueFrom: configMapKeyRef: name: ctfe-configmap key: cloud-project ports: - containerPort: 6962 name: http - containerPort: 6963 name: http-metrics volumeMounts: - name: ctfe-config-volume mountPath: /ctfe-config - name: prometheus-to-sd image: gcr.io/google-containers/prometheus-to-sd:v0.5.2 ports: - name: profiler containerPort: 6060 command: - /monitor - --stackdriver-prefix=custom.googleapis.com - --source=ctfe:http://localhost:6963/metrics - --pod-id=$(POD_NAME) - --namespace-id=$(POD_NAMESPACE) - --scrape-interval=5s - --export-interval=60s env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumes: - name: ctfe-config-volume configMap: name: ctfe-configmap items: - key: ctfe-config-file path: ct_server.cfg - key: roots path: roots restartPolicy: Always status: {} google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetes/ctfe-ingress.yaml000066400000000000000000000004371462611535200341050ustar00rootroot00000000000000apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: trillian-ctfe-ingress annotations: kubernetes.io/ingress.global-static-ip-name: trillian-ctfe-global-static-ip spec: defaultBackend: service: name: trillian-ctfe-service port: number: 6962 google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetes/ctfe-service.yaml000066400000000000000000000004721462611535200340720ustar00rootroot00000000000000apiVersion: v1 kind: Service metadata: annotations: cloud.google.com/load-balancer-type: "Internal" labels: io.kompose.service: trillian-ctfe-service name: trillian-ctfe-service spec: type: NodePort ports: - port: 6962 targetPort: 6962 selector: io.kompose.service: trillian-ctfe google-certificate-transparency-go-2308f62/trillian/examples/deployment/kubernetes/deploy.sh000077500000000000000000000042221462611535200324570ustar00rootroot00000000000000#!/usr/bin/env bash #set -o pipefail #set -o errexit #set -o nounset #set -o xtrace function checkEnv() { if [ -z ${PROJECT_ID+x} ] || [ -z ${CLUSTER_NAME+x} ] || [ -z ${MASTER_ZONE+x} ]; then echo "You must either pass an argument which is a config file, or set all the required environment variables" exit 1 fi } set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ $# -eq 1 ]; then source $1 else checkEnv fi export IMAGE_TAG=${IMAGE_TAG:-$(git rev-parse HEAD)} gcloud --quiet config set project ${PROJECT_ID} gcloud --quiet config set container/cluster ${CLUSTER_NAME} gcloud --quiet config set compute/zone ${MASTER_ZONE} gcloud --quiet container clusters get-credentials ${CLUSTER_NAME} configmaps=$(kubectl get configmaps) if [[ ! "${configmaps}" =~ "ctfe-configmap" ]]; then echo "Missing ctfe config map." echo echo "Ensure you have a PEM file containing all the roots your log should accept." echo "and a working CTFE configuration file, then create a CTFE configmap by" echo "running the following command:" echo " kubectl create configmap ctfe-configmap \\" echo " --from-file=roots=path/to/all-roots.pem \\" echo " --from-file=ctfe-config-file=path/to/ct_server.cfg \\" echo " --from-literal=cloud-project=${PROJECT_ID}" echo echo "Once you've created the configmap, re-run this script" exit 1 fi echo "Building docker images.." cd $GOPATH/src/github.com/google/certificate-transparency-go docker build --quiet -f trillian/examples/deployment/docker/ctfe/Dockerfile -t gcr.io/${PROJECT_ID}/ctfe:${IMAGE_TAG} . echo "Pushing docker image..." gcloud docker -- push gcr.io/${PROJECT_ID}/ctfe:${IMAGE_TAG} echo "Tagging docker image..." gcloud --quiet container images add-tag gcr.io/${PROJECT_ID}/ctfe:${IMAGE_TAG} gcr.io/${PROJECT_ID}/ctfe:latest echo "Updating jobs..." envsubst < trillian/examples/deployment/kubernetes/ctfe-deployment.yaml | kubectl apply -f - envsubst < trillian/examples/deployment/kubernetes/ctfe-service.yaml | kubectl apply -f - envsubst < trillian/examples/deployment/kubernetes/ctfe-ingress.yaml | kubectl apply -f - echo "CTFE is available at:" kubectl get ingress google-certificate-transparency-go-2308f62/trillian/integration/000077500000000000000000000000001462611535200250025ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/integration/chains.go000066400000000000000000000227501462611535200266040ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "errors" "fmt" "time" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" ct "github.com/google/certificate-transparency-go" ) // ChainGenerator encapsulates objects that can generate certificate chains for testing. type ChainGenerator interface { // CertChain generates a certificate chain. CertChain() ([]ct.ASN1Cert, error) // PreCertChain generates a precertificate chain, and also returns the leaf TBS data PreCertChain() ([]ct.ASN1Cert, []byte, error) } // GeneratorFactory is a method that builds a Log-specific ChainGenerator. type GeneratorFactory func(c *configpb.LogConfig) (ChainGenerator, error) // SyntheticChainGenerator builds synthetic certificate chains based on // a template chain and intermediate CA private key. type SyntheticChainGenerator struct { chain []ct.ASN1Cert leafCert *x509.Certificate caCert *x509.Certificate // Signer which matches the caCert signer crypto.Signer notAfter time.Time } // NewSyntheticChainGenerator returns a ChainGenerator that mints synthetic certificates based on the // given template chain. The provided signer should match the public key of the first issuer cert. func NewSyntheticChainGenerator(chain []ct.ASN1Cert, signer crypto.Signer, notAfter time.Time) (ChainGenerator, error) { if len(chain) < 2 { return nil, fmt.Errorf("chain too short (%d)", len(chain)) } leaf, err := x509.ParseCertificate(chain[0].Data) if err != nil { return nil, fmt.Errorf("failed to parse leaf cert: %v", err) } issuer, err := x509.ParseCertificate(chain[1].Data) if err != nil { return nil, fmt.Errorf("failed to parse issuer cert: %v", err) } if notAfter.IsZero() { notAfter = time.Now().Add(24 * time.Hour) } return &SyntheticChainGenerator{ chain: chain, leafCert: leaf, caCert: issuer, signer: signer, notAfter: notAfter, }, nil } // CertChain builds a new synthetic chain with a fresh leaf cert, changing SubjectKeyId and re-signing. func (g *SyntheticChainGenerator) CertChain() ([]ct.ASN1Cert, error) { cert := *g.leafCert cert.NotAfter = g.notAfter chain := make([]ct.ASN1Cert, len(g.chain)) copy(chain[1:], g.chain[1:]) // Randomize the subject key ID. randData := make([]byte, 128) if _, err := rand.Read(randData); err != nil { return nil, fmt.Errorf("failed to read random data: %v", err) } cert.SubjectKeyId = randData // Create a fresh certificate, signed by the intermediate CA, for the leaf. var err error chain[0].Data, err = x509.CreateCertificate(rand.Reader, &cert, g.caCert, cert.PublicKey, g.signer) if err != nil { return nil, fmt.Errorf("failed to create certificate: %v", err) } return chain, nil } // PreCertChain builds a new synthetic precert chain; also returns the leaf TBS data. func (g *SyntheticChainGenerator) PreCertChain() ([]ct.ASN1Cert, []byte, error) { prechain := make([]ct.ASN1Cert, len(g.chain)) copy(prechain[1:], g.chain[1:]) cert, err := x509.ParseCertificate(g.chain[0].Data) if err != nil { return nil, nil, fmt.Errorf("failed to parse certificate to build precert from: %v", err) } cert.NotAfter = g.notAfter prechain[0].Data, err = buildNewPrecertData(cert, g.caCert, g.signer) if err != nil { return nil, nil, fmt.Errorf("failed to create certificate: %v", err) } // For later verification, build the leaf TBS data that is included in the log. tbs, err := buildLeafTBS(prechain[0].Data, nil) if err != nil { return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err) } return prechain, tbs, nil } // buildLeafTBS builds the raw pre-cert data (a DER-encoded TBSCertificate) that is included // in the log. func buildLeafTBS(precertData []byte, preIssuer *x509.Certificate) ([]byte, error) { reparsed, err := x509.ParseCertificate(precertData) if err != nil { return nil, fmt.Errorf("failed to re-parse created precertificate: %v", err) } return x509.BuildPrecertTBS(reparsed.RawTBSCertificate, preIssuer) } // makePreIssuerPrecertChain builds a precert chain where the pre-cert is signed by a new // pre-issuer intermediate. func makePreIssuerPrecertChain(chain []ct.ASN1Cert, issuer *x509.Certificate, signer crypto.Signer) ([]ct.ASN1Cert, []byte, error) { prechain := make([]ct.ASN1Cert, len(chain)+1) copy(prechain[2:], chain[1:]) // Create a new private key and intermediate CA cert to go with it. preSigner, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, fmt.Errorf("failed to create pre-issuer private key: %v", err) } preIssuerTemplate := *issuer preIssuerTemplate.RawSubject = nil preIssuerTemplate.Subject.CommonName += "PrecertIssuer" preIssuerTemplate.PublicKeyAlgorithm = x509.ECDSA preIssuerTemplate.PublicKey = preSigner.Public() preIssuerTemplate.ExtKeyUsage = append(preIssuerTemplate.ExtKeyUsage, x509.ExtKeyUsageCertificateTransparency) // Set a new subject-key-id for the intermediate (to ensure it's different from the true // issuer's subject-key-id). randData := make([]byte, 128) if _, err := rand.Read(randData); err != nil { return nil, nil, fmt.Errorf("failed to read random data: %v", err) } preIssuerTemplate.SubjectKeyId = randData prechain[1].Data, err = x509.CreateCertificate(rand.Reader, &preIssuerTemplate, issuer, preIssuerTemplate.PublicKey, signer) if err != nil { return nil, nil, fmt.Errorf("failed to create pre-issuer certificate: %v", err) } // Parse the pre-issuer back to a fully-populated x509.Certificate. preIssuer, err := x509.ParseCertificate(prechain[1].Data) if err != nil { return nil, nil, fmt.Errorf("failed to re-parse generated pre-issuer: %v", err) } cert, err := x509.ParseCertificate(chain[0].Data) if err != nil { return nil, nil, fmt.Errorf("failed to parse certificate to build precert from: %v", err) } prechain[0].Data, err = buildNewPrecertData(cert, preIssuer, preSigner) if err != nil { return nil, nil, fmt.Errorf("failed to create certificate: %v", err) } if err := verifyChain(prechain); err != nil { return nil, nil, fmt.Errorf("failed to verify just-created prechain: %v", err) } // The leaf data has the poison removed and the issuance information changed. tbs, err := buildLeafTBS(prechain[0].Data, preIssuer) if err != nil { return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err) } return prechain, tbs, nil } // verifyChain checks that a chain of certificates validates locally. func verifyChain(rawChain []ct.ASN1Cert) error { chain := make([]*x509.Certificate, 0, len(rawChain)) for i, c := range rawChain { cert, err := x509.ParseCertificate(c.Data) if err != nil { return fmt.Errorf("failed to parse rawChain[%d]: %v", i, err) } chain = append(chain, cert) } // First verify signatures cert-by-cert. for i := 1; i < len(chain); i++ { issuer := chain[i] cert := chain[i-1] if err := cert.CheckSignatureFrom(issuer); err != nil { return fmt.Errorf("failed to check signature on rawChain[%d] using rawChain[%d]: %v", i-1, i, err) } } // Now verify the chain as a whole intermediatePool := x509.NewCertPool() for i := 1; i < len(chain); i++ { // Don't check path-len constraints chain[i].MaxPathLen = -1 intermediatePool.AddCert(chain[i]) } rootPool := x509.NewCertPool() rootPool.AddCert(chain[len(chain)-1]) opts := x509.VerifyOptions{ Roots: rootPool, Intermediates: intermediatePool, DisableTimeChecks: true, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } chain[0].UnhandledCriticalExtensions = nil chains, err := chain[0].Verify(opts) if err != nil { return fmt.Errorf("chain[0].Verify(%+v) failed: %v", opts, err) } if len(chains) == 0 { return errors.New("no path to root found when trying to validate chains") } return nil } // SyntheticGeneratorFactory returns a function that creates per-Log ChainGenerator instances // that create synthetic certificates (details of which are specified by the arguments). func SyntheticGeneratorFactory(testDir, leafNotAfter string) (GeneratorFactory, error) { leafChain, err := GetChain(testDir, "leaf01.chain") if err != nil { return nil, fmt.Errorf("failed to load certificate: %v", err) } signer, err := MakeSigner(testDir) if err != nil { return nil, fmt.Errorf("failed to retrieve signer for re-signing: %v", err) } var notAfterOverride time.Time if leafNotAfter != "" { notAfterOverride, err = time.Parse(time.RFC3339, leafNotAfter) if err != nil { return nil, fmt.Errorf("failed to parse leaf notAfter: %v", err) } } // Build a synthetic generator for each target log. return func(c *configpb.LogConfig) (ChainGenerator, error) { notAfter := notAfterOverride if notAfter.IsZero() { var err error notAfter, err = NotAfterForLog(c) if err != nil { return nil, fmt.Errorf("failed to determine notAfter for %s: %v", c.Prefix, err) } } return NewSyntheticChainGenerator(leafChain, signer, notAfter) }, nil } google-certificate-transparency-go-2308f62/trillian/integration/consoles/000077500000000000000000000000001462611535200266275ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/integration/consoles/reads.html000066400000000000000000000042771462611535200306250ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Read Path

    HTTP Requests:

    HTTP Errors:

    gRPC Requests:

    gRPC Errors:

    {{ template "prom_content_tail" . }} {{ template "tail" }} google-certificate-transparency-go-2308f62/trillian/integration/consoles/signer.html000066400000000000000000000031271462611535200310070ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Signer

    Sequence Rate:

    Sequencer Batch Latency (centiles):

    {{ template "prom_content_tail" . }} {{ template "tail" }} google-certificate-transparency-go-2308f62/trillian/integration/consoles/trillian.html000066400000000000000000000032221462611535200313320ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Trillian Overview

    Tree Size:

    Signer master count per log; should always be 1 (modulo scraping interval overlaps):

    Graph of log IDs known about by ctfe less those known about by logsigner; should always be 0:

    {{ template "prom_content_tail" . }} {{ template "tail" }} google-certificate-transparency-go-2308f62/trillian/integration/consoles/writes.html000066400000000000000000000043341462611535200310360ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Write Path

    HTTP Requests:

    HTTP Errors:

    gRPC Requests:

    gRPC Errors:

    {{ template "prom_content_tail" . }} {{ template "tail" }} google-certificate-transparency-go-2308f62/trillian/integration/copier.go000066400000000000000000000217721462611535200266230ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "context" "crypto/sha256" "errors" "fmt" "math/rand" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) // CopyChainGenerator creates certificate chains by copying suitable examples // from a source log. type CopyChainGenerator struct { start, limit time.Time sourceRoots, targetRoots *x509util.PEMCertPool certs, precerts chan []ct.ASN1Cert } // CopyChainOptions describes the parameters for a CopyChainGenerator instance. type CopyChainOptions struct { // StartIndex indicates where to start scanning; negative value implies starting from a random position. StartIndex int64 // BufSize is the number of buffered chains to hold. BufSize int // BatchSize indicates how many entries should be requested from the source log at a time. BatchSize int // ParallelFetch indicates how many parallel entry fetchers to run. ParallelFetch int } // NewCopyChainGenerator builds a certificate chain generator that sources // chains from another source log, starting at startIndex (or a random index in // the current tree size if startIndex is negative). This function starts // background goroutines that scan the log; cancelling the context will // terminate these goroutines (after that the [Pre]CertChain() entrypoints will // permanently fail). func NewCopyChainGenerator(ctx context.Context, client *client.LogClient, cfg *configpb.LogConfig, startIndex int64, bufSize int) (ChainGenerator, error) { opts := CopyChainOptions{ StartIndex: startIndex, BufSize: bufSize, BatchSize: 500, ParallelFetch: 2, } return NewCopyChainGeneratorFromOpts(ctx, client, cfg, opts) } // NewCopyChainGeneratorFromOpts builds a certificate chain generator that // sources chains from another source log, starting at opts.StartIndex (or a // random index in the current tree size if this is negative). This function // starts background goroutines that scan the log; cancelling the context will // terminate these goroutines (after that the [Pre]CertChain() entrypoints // will permanently fail). func NewCopyChainGeneratorFromOpts(ctx context.Context, client *client.LogClient, cfg *configpb.LogConfig, opts CopyChainOptions) (ChainGenerator, error) { var start, limit time.Time var err error if cfg.NotAfterStart != nil { if err := cfg.NotAfterStart.CheckValid(); err != nil { return nil, fmt.Errorf("failed to parse NotAfterStart: %v", err) } start = cfg.NotAfterStart.AsTime() } if cfg.NotAfterLimit != nil { if err := cfg.NotAfterLimit.CheckValid(); err != nil { return nil, fmt.Errorf("failed to parse NotAfterLimit: %v", err) } limit = cfg.NotAfterLimit.AsTime() } targetPool := x509util.NewPEMCertPool() for _, pemFile := range cfg.RootsPemFile { if err := targetPool.AppendCertsFromPEMFile(pemFile); err != nil { return nil, fmt.Errorf("failed to read trusted roots for target log: %v", err) } } if klog.V(4).Enabled() { for _, cert := range targetPool.RawCertificates() { klog.Infof("target root cert: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject) } } seenOverlap := false srcRoots, err := client.GetAcceptedRoots(ctx) if err != nil { return nil, fmt.Errorf("failed to read trusted roots for source log: %v", err) } sourcePool := x509util.NewPEMCertPool() for _, root := range srcRoots { cert, err := x509.ParseCertificate(root.Data) if x509.IsFatal(err) { klog.Warningf("Failed to parse root certificate from source log: %v", err) continue } klog.V(4).Infof("source log root cert: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject) sourcePool.AddCert(cert) if targetPool.Included(cert) { klog.V(3).Infof("source log root cert is accepted by target: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject) seenOverlap = true } } if !seenOverlap { return nil, fmt.Errorf("failed to find any overlap in accepted roots for target %s", cfg.Prefix) } startIndex := opts.StartIndex if startIndex < 0 { // Pick a random start point in the source log's tree. sth, err := client.GetSTH(ctx) if err != nil { return nil, fmt.Errorf("failed to get STH for source log: %v", err) } startIndex = rand.Int63n(int64(sth.TreeSize)) klog.Infof("starting CopyChainGenerator from index %d (of %d) in source log", startIndex, sth.TreeSize) } generator := &CopyChainGenerator{ start: start, limit: limit, targetRoots: targetPool, sourceRoots: sourcePool, certs: make(chan []ct.ASN1Cert, opts.BufSize), precerts: make(chan []ct.ASN1Cert, opts.BufSize), } // Start two goroutines to scan the source log for certs and precerts respectively. fetchOpts := scanner.FetcherOptions{ BatchSize: opts.BatchSize, ParallelFetch: opts.ParallelFetch, Continuous: true, StartIndex: startIndex, } go func() { certFetcher := scanner.NewFetcher(client, &fetchOpts) if err := certFetcher.Run(ctx, func(batch scanner.EntryBatch) { generator.processBatch(batch, generator.certs, ct.X509LogEntryType) }); err != nil { klog.Errorf("processBatch(): %v", err) } }() go func() { precertFetcher := scanner.NewFetcher(client, &fetchOpts) if err := precertFetcher.Run(ctx, func(batch scanner.EntryBatch) { generator.processBatch(batch, generator.precerts, ct.PrecertLogEntryType) }); err != nil { klog.Errorf("processBatch(): %v", err) } }() return generator, nil } // processBatch extracts chains of the desired type from a batch of entries and sends // them down the channel. May block on the channel consumer. func (g *CopyChainGenerator) processBatch(batch scanner.EntryBatch, chains chan []ct.ASN1Cert, eType ct.LogEntryType) { klog.V(2).Infof("processBatch(%d): examine batch [%d, %d)", eType, batch.Start, int(batch.Start)+len(batch.Entries)) for i, entry := range batch.Entries { index := batch.Start + int64(i) entry, err := ct.RawLogEntryFromLeaf(index, &entry) if err != nil { klog.Errorf("processBatch(%d): failed to build raw log entry %d: %v", eType, index, err) continue } if entry.Leaf.TimestampedEntry.EntryType != eType { klog.V(4).Infof("skip entry %d as EntryType=%d not %d", index, entry.Leaf.TimestampedEntry.EntryType, eType) continue } root, err := x509.ParseCertificate(entry.Chain[len(entry.Chain)-1].Data) if err != nil { klog.V(3).Infof("skip entry %d as its root cannot be parsed to check accepted: %v", index, err) continue } if !g.targetRoots.Included(root) { klog.V(3).Infof("skip entry %d as its root is not accepted by target log", index) continue } if !g.start.IsZero() || !g.limit.IsZero() { // Target log has NotAfter boundaries, so we need to parse the leaf cert to check // whether it complies with them. cert, err := x509.ParseCertificate(entry.Cert.Data) if x509.IsFatal(err) { klog.V(3).Infof("skip entry %d as its leaf cannot be parsed to check NotAfter: %v", index, err) continue } if !g.start.IsZero() && cert.NotAfter.Before(g.start) { klog.V(3).Infof("skip entry %d as its NotAfter (%v) is before %v", index, cert.NotAfter, g.start) continue } if !g.limit.IsZero() && !cert.NotAfter.Before(g.limit) { klog.V(3).Infof("skip entry %d as its NotAfter (%v) is after %v", index, cert.NotAfter, g.limit) continue } } chain := make([]ct.ASN1Cert, len(entry.Chain)+1) chain[0] = entry.Cert copy(chain[1:], entry.Chain) chains <- chain } } // CertChain returns a new cert chain taken from the source log. // This may block until a suitable cert has been discovered in the source log. func (g *CopyChainGenerator) CertChain() ([]ct.ASN1Cert, error) { chain := <-g.certs if len(chain) == 0 { return nil, errors.New("no certs available") } return chain, nil } // PreCertChain returns a new precert chain taken from the source log. // This may block until a suitable precert has been discovered in the source log. func (g *CopyChainGenerator) PreCertChain() ([]ct.ASN1Cert, []byte, error) { prechain := <-g.precerts if len(prechain) == 0 { return nil, nil, errors.New("no precerts available") } tbs, err := buildLeafTBS(prechain[0].Data, nil) if err != nil { return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err) } return prechain, tbs, nil } google-certificate-transparency-go-2308f62/trillian/integration/ct_functions.sh000066400000000000000000000121551462611535200300400ustar00rootroot00000000000000# Functions for setting up CT personalities in Trillian integration tests # Requires github.com/google/trillian/integration/functions.sh declare -a CT_SERVER_PIDS CT_SERVERS= CT_CFG= CT_LIFECYCLE_CFG= CT_COMBINED_CFG= PROMETHEUS_CFGDIR= readonly CT_GO_PATH=$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go) # ct_prep_test prepares a set of running processes for a CT test. # Parameters: # - number of CT personality instances to run # Populates: # - CT_SERVERS : list of HTTP addresses (comma separated) # - CT_SERVER_1 : first HTTP address # - CT_METRICS_SERVERS : list of HTTP addresses (comma separated) serving metrics # - CT_SERVER_PIDS : bash array of CT HTTP server pids # in addition to the variables populated by log_prep_test. # If etcd and Prometheus are configured, it also populates: # - PROMETHEUS_PID : pid of local Prometheus server # - PROMETHEUS_CFGDIR : Prometheus configuration directory ct_prep_test() { # Default to one of everything. local http_server_count=${1:-1} echo "PREP: Trillian: ${RPC_SERVER_1} [${RPC_SERVERS}]" echo "Building CT personality code" go build github.com/google/certificate-transparency-go/trillian/ctfe/ct_server echo "Provisioning logs for CT" ct_provision "${RPC_SERVER_1}" echo "Launching CT personalities" for ((i=0; i < http_server_count; i++)); do local port=$(pick_unused_port) CT_SERVERS="${CT_SERVERS},localhost:${port}" local metrics_port=$(pick_unused_port ${port}) CT_METRICS_SERVERS="${CT_METRICS_SERVERS},localhost:${metrics_port}" if [[ $i -eq 0 ]]; then CT_SERVER_1="localhost:${port}" fi echo "Starting CT HTTP server on localhost:${port}, metrics on localhost:${metrics_port}" ./ct_server ${ETCD_OPTS} --log_config="${CT_COMBINED_CFG}" --log_rpc_server="${RPC_SERVERS}" --http_endpoint="localhost:${port}" --metrics_endpoint="localhost:${metrics_port}" ${CTFE_OPTS} & pid=$! CT_SERVER_PIDS+=(${pid}) wait_for_server_startup ${port} done CT_SERVERS="${CT_SERVERS:1}" CT_METRICS_SERVERS="${CT_METRICS_SERVERS:1}" if [[ ! -z "${ETCD_OPTS}" ]]; then echo "Registered HTTP endpoints" ETCDCTL_API=3 etcdctl get trillian-ctfe-http/ --prefix ETCDCTL_API=3 etcdctl get trillian-ctfe-metrics-http/ --prefix fi if [[ -x "${PROMETHEUS_DIR}/prometheus" ]]; then if [[ ! -z "${ETCD_OPTS}" ]]; then PROMETHEUS_CFGDIR="$(mktemp -d ${TMPDIR}/ct-prometheus-XXXXXX)" echo "Launching Prometheus (default location localhost:9090)" ${PROMETHEUS_DIR}/prometheus --config.file=${CT_GO_PATH}/trillian/integration/prometheus.yml \ --web.console.templates=${CT_GO_PATH}/trillian/integration/consoles \ --web.console.libraries=${CT_GO_PATH}/third_party/prometheus/console_libs & PROMETHEUS_PID=$! fi fi } # ct_provision generates a CT configuration file and provisions the trees for it. # Parameters: # - location of admin server instance # Populates: # - CT_CFG : configuration file for CT integration test # - CT_LIFECYCLE_CFG : configuration file for CT lifecycle test # - CT_COMBINED_CFG : the above configs concatenated together ct_provision() { local admin_server="$1" # Build config files with absolute paths CT_CFG=$(mktemp ${TMPDIR}/ct-XXXXXX) sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration/ct_integration_test.cfg > "${CT_CFG}" CT_LIFECYCLE_CFG=$(mktemp ${TMPDIR}/ct-XXXXXX) sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration/ct_lifecycle_test.cfg > "${CT_LIFECYCLE_CFG}" echo 'Building createtree' go build github.com/google/trillian/cmd/createtree/ echo 'Provisioning Integration Logs' ct_provision_cfg ${admin_server} ${CT_CFG} echo 'Provisioning Lifecycle Logs' ct_provision_cfg ${admin_server} ${CT_LIFECYCLE_CFG} CT_COMBINED_CFG=$(mktemp ${TMPDIR}/ct-XXXXXX) cat ${CT_CFG} ${CT_LIFECYCLE_CFG} > ${CT_COMBINED_CFG} echo "CT Integration Configuration in ${CT_CFG}:" cat "${CT_CFG}" echo "CT Lifecycle Configuration in ${CT_LIFECYCLE_CFG}:" cat "${CT_LIFECYCLE_CFG}" echo } # ct_provision_cfg provisions trees for the logs in a specified config file. # Parameters: # - location of admin server instance # - the config file to be provisioned for ct_provision_cfg() { local admin_server="$1" local cfg="$2" num_logs=$(grep -c '@TREE_ID@' ${cfg}) for i in $(seq ${num_logs}); do tree_id=$(./createtree --admin_server="${admin_server}") echo "Created tree ${tree_id}" # Need suffix for sed -i to cope with both GNU and non-GNU (e.g. OS X) sed. sed -i'.bak' "1,/@TREE_ID@/s/@TREE_ID@/${tree_id}/" "${cfg}" rm -f "${cfg}.bak" done } # ct_stop_test closes the running processes for a CT test. # Assumes the following variables are set, in addition to those needed by logStopTest: # - CT_SERVER_PIDS : bash array of CT HTTP server pids ct_stop_test() { local pids if [[ "${PROMETHEUS_PID}" != "" ]]; then pids+=" ${PROMETHEUS_PID}" fi echo "Stopping CT HTTP servers (pids ${CT_SERVER_PIDS[@]})" pids+=" ${CT_SERVER_PIDS[@]}" kill_pid ${pids} } google-certificate-transparency-go-2308f62/trillian/integration/ct_hammer/000077500000000000000000000000001462611535200267415ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/integration/ct_hammer/main.go000066400000000000000000000327151462611535200302240ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ct_hammer is a stress/load test for a CT log. package main import ( "bytes" "compress/gzip" "context" "crypto/tls" "encoding/base64" "flag" "fmt" "io" "net/http" "os" "strings" "sync" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/trillian/integration" "github.com/google/certificate-transparency-go/x509util" "github.com/google/trillian/monitoring" "github.com/google/trillian/monitoring/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/time/rate" "k8s.io/klog/v2" ) var ( banner = flag.Bool("banner", true, "Display intro") httpServers = flag.String("ct_http_servers", "localhost:8092", "Comma-separated list of (assumed interchangeable) servers, each as address:port") // Options for synthetic cert generation. testDir = flag.String("testdata_dir", "testdata", "Name of directory with test data") leafNotAfter = flag.String("leaf_not_after", "", "Not-After date to use for leaf certs, RFC3339/ISO-8601 format (e.g. 2017-11-26T12:29:19Z)") // Options for copied-cert generation. srcLogURI = flag.String("src_log_uri", "", "URI for source log to copy certificates from") srcPubKey = flag.String("src_pub_key", "", "Name of file containing source log's public key") srcLogName = flag.String("src_log_name", "", "Name of source log to copy certificate from (from --log_list)") logList = flag.String("log_list", loglist3.AllLogListURL, "Location of master log list (URL or filename)") skipHTTPSVerify = flag.Bool("skip_https_verify", false, "Skip verification of HTTPS transport connection to source log") chainBufSize = flag.Int("buffered_chains", 100, "Number of buffered certificate chains to hold") startIndex = flag.Int64("start_index", 0, "Index of start point in source log to scan from (-1 for random start index)") batchSize = flag.Int("batch_size", 500, "Max number of entries to request at per call to get-entries") parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches") metricsEndpoint = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will not be exposed") logConfig = flag.String("log_config", "", "File holding log config in JSON") mmd = flag.Duration("mmd", 2*time.Minute, "Default MMD for logs") operations = flag.Uint64("operations", ^uint64(0), "Number of operations to perform") minGetEntries = flag.Int("min_get_entries", 1, "Minimum get-entries request size") maxGetEntries = flag.Int("max_get_entries", 500, "Maximum get-entries request size") oversizedGetEntries = flag.Bool("oversized_get_entries", false, "Whether get-entries requests can go beyond log size") maxParallelChains = flag.Int("max_parallel_chains", 2, "Maximum number of chains to add in parallel (will always add at least 1 chain)") limit = flag.Int("rate_limit", 0, "Maximum rate of requests to an individual log; 0 for no rate limit") ignoreErrors = flag.Bool("ignore_errors", false, "Whether to ignore errors and retry the operation") maxRetry = flag.Duration("max_retry", 60*time.Second, "How long to keep retrying when ignore_errors is set") reqDeadline = flag.Duration("req_deadline", 10*time.Second, "Deadline to set on individual requests") ) var ( addChainBias = flag.Int("add_chain", 20, "Bias for add-chain operations") addPreChainBias = flag.Int("add_pre_chain", 20, "Bias for add-pre-chain operations") getSTHBias = flag.Int("get_sth", 2, "Bias for get-sth operations") getSTHConsistencyBias = flag.Int("get_sth_consistency", 2, "Bias for get-sth-consistency operations") getProofByHashBias = flag.Int("get_proof_by_hash", 2, "Bias for get-proof-by-hash operations") getEntriesBias = flag.Int("get_entries", 2, "Bias for get-entries operations") getRootsBias = flag.Int("get_roots", 1, "Bias for get-roots operations") getEntryAndProofBias = flag.Int("get_entry_and_proof", 0, "Bias for get-entry-and-proof operations") invalidChance = flag.Int("invalid_chance", 10, "Chance of generating an invalid operation, as the N in 1-in-N (0 for never)") dupeChance = flag.Int("duplicate_chance", 10, "Chance of generating a duplicate submission, as the N in 1-in-N (0 for never)") strictSTHConsistencySize = flag.Bool("strict_sth_consistency_size", true, "If set to true, hammer will use only tree sizes from STHs it's seen for consistency proofs, otherwise it'll choose a random size for the smaller tree") ) func newLimiter(limit int) integration.Limiter { if limit <= 0 { return nil } return rate.NewLimiter(rate.Limit(limit), 1) } // copierGeneratorFactory returns a function that creates per-Log ChainGenerator instances // that are based off a source CT log specified by the command line arguments. func copierGeneratorFactory(ctx context.Context) integration.GeneratorFactory { var tlsCfg *tls.Config if *skipHTTPSVerify { klog.Warning("Skipping HTTPS connection verification") tlsCfg = &tls.Config{InsecureSkipVerify: *skipHTTPSVerify} } httpClient := &http.Client{ Timeout: 60 * time.Second, Transport: &http.Transport{ TLSHandshakeTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, ExpectContinueTimeout: 1 * time.Second, TLSClientConfig: tlsCfg, }, } uri := *srcLogURI var opts jsonclient.Options if *srcPubKey != "" { pubkey, err := os.ReadFile(*srcPubKey) if err != nil { klog.Exit(err) } opts.PublicKey = string(pubkey) } if len(*srcLogName) > 0 { llData, err := x509util.ReadFileOrURL(*logList, httpClient) if err != nil { klog.Exitf("Failed to read log list: %v", err) } ll, err := loglist3.NewFromJSON(llData) if err != nil { klog.Exitf("Failed to build log list: %v", err) } logs := ll.FindLogByName(*srcLogName) if len(logs) == 0 { klog.Exitf("No log with name like %q found in loglist %q", *srcLogName, *logList) } if len(logs) > 1 { logNames := make([]string, len(logs)) for i, log := range logs { logNames[i] = fmt.Sprintf("%q", log.Description) } klog.Exitf("Multiple logs with name like %q found in loglist: %s", *srcLogName, strings.Join(logNames, ",")) } uri = "https://" + logs[0].URL if opts.PublicKey == "" { opts.PublicKeyDER = logs[0].Key } } logClient, err := client.New(uri, httpClient, opts) if err != nil { klog.Exitf("Failed to create client for %q: %v", uri, err) } klog.Infof("Testing with certs copied from log at %s starting at index %d", uri, *startIndex) genOpts := integration.CopyChainOptions{ StartIndex: *startIndex, BufSize: *chainBufSize, BatchSize: *batchSize, ParallelFetch: *parallelFetch, } return func(c *configpb.LogConfig) (integration.ChainGenerator, error) { return integration.NewCopyChainGeneratorFromOpts(ctx, logClient, c, genOpts) } } func main() { klog.InitFlags(nil) flag.Parse() if *logConfig == "" { klog.Exit("Test aborted as no log config provided (via --log_config)") } cfg, err := ctfe.LogConfigFromFile(*logConfig) if err != nil { klog.Exitf("Failed to read log config: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() var generatorFactory integration.GeneratorFactory if len(*srcLogURI) > 0 || len(*srcLogName) > 0 { // Test cert chains will be generated by copying from a source log. generatorFactory = copierGeneratorFactory(ctx) } else if *testDir != "" { // Test cert chains will be generated as synthetic certs from a template. // Retrieve the test data holding the template and key. klog.Infof("Testing with synthetic certs based on data from %s", *testDir) generatorFactory, err = integration.SyntheticGeneratorFactory(*testDir, *leafNotAfter) if err != nil { klog.Exitf("Failed to make cert generator: %v", err) } } if generatorFactory == nil { klog.Warningf("Warning: add-[pre-]chain operations disabled as no cert generation method available") *addChainBias = 0 *addPreChainBias = 0 generatorFactory = func(c *configpb.LogConfig) (integration.ChainGenerator, error) { return nil, nil } } bias := integration.HammerBias{ Bias: map[ctfe.EntrypointName]int{ ctfe.AddChainName: *addChainBias, ctfe.AddPreChainName: *addPreChainBias, ctfe.GetSTHName: *getSTHBias, ctfe.GetSTHConsistencyName: *getSTHConsistencyBias, ctfe.GetProofByHashName: *getProofByHashBias, ctfe.GetEntriesName: *getEntriesBias, ctfe.GetRootsName: *getRootsBias, ctfe.GetEntryAndProofName: *getEntryAndProofBias, }, InvalidChance: map[ctfe.EntrypointName]int{ ctfe.AddChainName: *invalidChance, ctfe.AddPreChainName: *invalidChance, ctfe.GetSTHName: 0, ctfe.GetSTHConsistencyName: *invalidChance, ctfe.GetProofByHashName: *invalidChance, ctfe.GetEntriesName: *invalidChance, ctfe.GetRootsName: 0, ctfe.GetEntryAndProofName: 0, }, } var mf monitoring.MetricFactory if *metricsEndpoint != "" { mf = prometheus.MetricFactory{} http.Handle("/metrics", promhttp.Handler()) server := http.Server{Addr: *metricsEndpoint, Handler: nil} klog.Infof("Serving metrics at %v", *metricsEndpoint) go func() { err := server.ListenAndServe() klog.Warningf("Metrics server exited: %v", err) }() } else { mf = monitoring.InertMetricFactory{} } if *banner { fmt.Print("\n\nStop") for i := 0; i < 8; i++ { time.Sleep(100 * time.Millisecond) fmt.Print(".") } mc := "H4sIAAAAAAAA/4xVPbLzMAjsv1OkU8FI9LqDOAUFDUNBxe2/QXYSS/HLe5SeXZYfsf73+D1KB8D2B2RxZpGw8gcsSoQYeH1ya0fof1BpnhpuUR+P8ijorESq8Yto6WYWqsrMGh4qSkdI/YFZWu8d3AAAkklEHBGTNAYxbpKltWRgRzQ3A3CImDIjVSVCicThbLK0VjsiAGAGIIKbmUcIq/KkqYo4BNZDqtgZMAPNPSJCRISZZ36d5OiTUbqJZAOYIoCHUreImJsCPMobQ20SqjBbLWWbBGRREhHQU2MMUu9TwB12cC7X3SNrs1yPKvv5gD4yn+kzshOfMg69fVknJNbdcsjuDvgNXWPmTXCuEnuvP4NdlSWymIQjfsFWzbERZ5sz730NpbvoOGMOzu7eeBUaW3w8r4z2iRuD4uY6W9wgZ96+YZvpHW7SabvlH7CviKWQyp81EL2zj7Fcbee7MpSuNHzj2z18LdAvAkAr8pr/3cGFUO+apa2n64TK3XouTBpEch2Rf8GnzajAFY438+SzgURfV7sXT+q1FNTJYdLF9WxJzFheAyNmXfKuiel5/mW2QqSx2umlQ+L2GpTPWZBu5tvpXW5/fy4xTYd2ly+vR052dZbjTIh0u4vzyRDF6kPzoRLRfhp2pqnr5wce5eAGP6onaRv8EYdl7gfd5zIId/gxYvr4pWW7KnbjoU6kRL62e25b44ZQz7Oaf4GrTovnqemNsyOdL40Dls11ocMPn29nYeUvmt3S1v8DAAD//wEAAP//TRo+KHEIAAA=" mcData, _ := base64.StdEncoding.DecodeString(mc) b := bytes.NewReader(mcData) r, _ := gzip.NewReader(b) if _, err := io.Copy(os.Stdout, r); err != nil { klog.Exitf("Failed to print banner!") } if err := r.Close(); err != nil { klog.Exitf("Failed to close reader: %v", err) } fmt.Print("\n\nHammer Time\n\n") } type result struct { prefix string err error } results := make(chan result, len(cfg)) var wg sync.WaitGroup for _, c := range cfg { wg.Add(1) pool, err := integration.NewRandomPool(*httpServers, c.PublicKey, c.Prefix) if err != nil { klog.Exitf("Failed to create client pool: %v", err) } mmd := *mmd // Note: Although the (usually lower than MMD) expected merge delay is not // a guarantee, it should be OK for testing. if emd := c.ExpectedMergeDelaySec; emd != 0 { mmd = time.Second * time.Duration(emd) } generator, err := generatorFactory(c) if err != nil { klog.Exitf("Failed to build chain generator: %v", err) } cfg := integration.HammerConfig{ LogCfg: c, MetricFactory: mf, MMD: mmd, ChainGenerator: generator, ClientPool: pool, EPBias: bias, MinGetEntries: *minGetEntries, MaxGetEntries: *maxGetEntries, OversizedGetEntries: *oversizedGetEntries, Operations: *operations, Limiter: newLimiter(*limit), MaxParallelChains: *maxParallelChains, IgnoreErrors: *ignoreErrors, MaxRetryDuration: *maxRetry, RequestDeadline: *reqDeadline, DuplicateChance: *dupeChance, StrictSTHConsistencySize: *strictSTHConsistencySize, } go func(cfg integration.HammerConfig) { defer wg.Done() err := integration.HammerCTLog(ctx, cfg) results <- result{prefix: cfg.LogCfg.Prefix, err: err} }(cfg) } wg.Wait() klog.Infof("completed tests on all %d logs:", len(cfg)) close(results) errCount := 0 for e := range results { if e.err != nil { errCount++ klog.Errorf(" %s: failed with %v", e.prefix, e.err) } } if errCount > 0 { klog.Exitf("non-zero error count (%d), exiting", errCount) } klog.Info(" no errors; done") } google-certificate-transparency-go-2308f62/trillian/integration/ct_hammer_test.sh000077500000000000000000000020531462611535200303370ustar00rootroot00000000000000#!/bin/bash set -e . "$(go list -f '{{ .Dir }}' github.com/google/trillian)"/integration/functions.sh INTEGRATION_DIR="$( cd "$( dirname "$0" )" && pwd )" . "${INTEGRATION_DIR}"/ct_functions.sh # We're not using Trillian's log setup/turn down, so allow the caller to # specify where the trillian log server is. export RPC_SERVERS=${TRILLIAN_LOG_SERVERS:-localhost:8090} export RPC_SERVER_1=${TRILLIAN_LOG_SERVER_1:-localhost:8090} go build ${GOFLAGS} github.com/google/certificate-transparency-go/trillian/integration/ct_hammer ct_prep_test 1 # Cleanup for the personality TO_DELETE="${TO_DELETE} ${CT_CFG}" TO_KILL+=(${CT_SERVER_PIDS[@]}) metrics_port=$(pick_unused_port) echo "Running test(s) with metrics at localhost:${metrics_port}" set +e ./ct_hammer --log_config "${CT_CFG}" --ct_http_servers=${CT_SERVERS} --mmd=30s --testdata_dir=$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go)/trillian/testdata --metrics_endpoint="localhost:${metrics_port}" --logtostderr ${HAMMER_OPTS} RESULT=$? set -e ct_stop_test TO_KILL=() exit $RESULT google-certificate-transparency-go-2308f62/trillian/integration/ct_integration.go000066400000000000000000001103411462611535200303420ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package integration holds test-only code for running tests on // an integrated system of the CT personality and a Trillian log. package integration import ( "bufio" "bytes" "context" "crypto" cryptorand "crypto/rand" "crypto/sha256" "encoding/pem" "fmt" "math/rand" "net" "net/http" "os" "path/filepath" "reflect" "regexp" "strconv" "strings" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" "github.com/google/trillian" "github.com/google/trillian/crypto/keyspb" "github.com/kylelemons/godebug/pretty" "github.com/transparency-dev/merkle" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" "golang.org/x/net/context/ctxhttp" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/protobuf/types/known/fieldmaskpb" ct "github.com/google/certificate-transparency-go" ) const ( reqStatsRE = `^http_reqs{ep="(\w+)",logid="(\d+)"} ([.\d]+)$` rspStatsRE = `^http_rsps{ep="(\w+)",logid="(\d+)",rc="(\d+)"} (?P[.\d]+)$` ) // DefaultTransport is a http Transport more suited for use in the hammer // context. // In particular it increases the number of reusable connections to the same // host. This helps to prevent starvation of ports through TIME_WAIT when // using the hammer with a high number of parallel chain submissions. var DefaultTransport = &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 1000, MaxIdleConnsPerHost: 1000, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } // ClientPool describes an entity which produces LogClient instances. type ClientPool interface { // Next returns the next LogClient instance to be used. Next() *client.LogClient } // RandomPool holds a collection of CT LogClient instances. type RandomPool []*client.LogClient var _ ClientPool = &RandomPool{} // Next picks a random client from the pool. func (p RandomPool) Next() *client.LogClient { if len(p) == 0 { return nil } return p[rand.Intn(len(p))] } // NewRandomPool creates a pool which returns a random client from list of servers. func NewRandomPool(servers string, pubKey *keyspb.PublicKey, prefix string) (ClientPool, error) { opts := jsonclient.Options{ PublicKeyDER: pubKey.GetDer(), UserAgent: "ct-go-integrationtest/1.0", } hc := &http.Client{Transport: DefaultTransport} var pool RandomPool for _, s := range strings.Split(servers, ",") { c, err := client.New(fmt.Sprintf("http://%s/%s", s, prefix), hc, opts) if err != nil { return nil, fmt.Errorf("failed to create LogClient instance: %v", err) } pool = append(pool, c) } return &pool, nil } // testInfo holds per-test information. type testInfo struct { prefix string cfg *configpb.LogConfig metricsServers string adminServer string stats *logStats pool ClientPool hasher merkle.LogHasher } func (t *testInfo) checkStats() error { return t.stats.check(t.cfg, t.metricsServers) } func (t *testInfo) client() *client.LogClient { return t.pool.Next() } // awaitTreeSize loops until the an STH is retrieved that is the specified size (or larger, if exact is false). func (t *testInfo) awaitTreeSize(ctx context.Context, size uint64, exact bool, mmd time.Duration) (*ct.SignedTreeHead, error) { var sth *ct.SignedTreeHead deadline := time.Now().Add(mmd) for sth == nil || sth.TreeSize < size { if time.Now().After(deadline) { return nil, fmt.Errorf("deadline for STH inclusion expired (MMD=%v)", mmd) } time.Sleep(200 * time.Millisecond) var err error sth, err = t.client().GetSTH(ctx) if t.stats != nil { t.stats.expect(ctfe.GetSTHName, 200) } if err != nil { return nil, fmt.Errorf("failed to get STH: %v", err) } } if exact && sth.TreeSize != size { return nil, fmt.Errorf("sth.TreeSize=%d; want: %d", sth.TreeSize, size) } return sth, nil } // checkInclusionOf checks that a given certificate chain and associated SCT are included // under a signed tree head. func (t *testInfo) checkInclusionOf(ctx context.Context, chain []ct.ASN1Cert, sct *ct.SignedCertificateTimestamp, sth *ct.SignedTreeHead) error { leaf := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: sct.Timestamp, EntryType: ct.X509LogEntryType, X509Entry: &(chain[0]), Extensions: sct.Extensions, }, } leafHash, err := ct.LeafHashForLeaf(&leaf) if err != nil { return fmt.Errorf("ct.LeafHashForLeaf(leaf[%d])=(nil,%v); want (_,nil)", 0, err) } rsp, err := t.client().GetProofByHash(ctx, leafHash[:], sth.TreeSize) t.stats.expect(ctfe.GetProofByHashName, 200) if err != nil { return fmt.Errorf("got GetProofByHash(sct[%d],size=%d)=(nil,%v); want (_,nil)", 0, sth.TreeSize, err) } if err := proof.VerifyInclusion(t.hasher, uint64(rsp.LeafIndex), sth.TreeSize, leafHash[:], rsp.AuditPath, sth.SHA256RootHash[:]); err != nil { return fmt.Errorf("got VerifyInclusion(%d, %d,...)=%v", 0, sth.TreeSize, err) } return nil } // checkInclusionOfPreCert checks a pre-cert is included at given index. func (t *testInfo) checkInclusionOfPreCert(ctx context.Context, tbs []byte, issuer *x509.Certificate, sct *ct.SignedCertificateTimestamp, sth *ct.SignedTreeHead) error { leaf := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: sct.Timestamp, EntryType: ct.PrecertLogEntryType, PrecertEntry: &ct.PreCert{ IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo), TBSCertificate: tbs, }, Extensions: sct.Extensions, }, } leafHash, err := ct.LeafHashForLeaf(&leaf) if err != nil { return fmt.Errorf("ct.LeafHashForLeaf(precertLeaf)=(nil,%v); want (_,nil)", err) } rsp, err := t.client().GetProofByHash(ctx, leafHash[:], sth.TreeSize) t.stats.expect(ctfe.GetProofByHashName, 200) if err != nil { return fmt.Errorf("got GetProofByHash(sct, size=%d)=nil,%v", sth.TreeSize, err) } fmt.Printf("%s: Inclusion proof leaf %d @ %d -> root %d = %x\n", t.prefix, rsp.LeafIndex, sct.Timestamp, sth.TreeSize, rsp.AuditPath) if err := proof.VerifyInclusion(t.hasher, uint64(rsp.LeafIndex), sth.TreeSize, leafHash[:], rsp.AuditPath, sth.SHA256RootHash[:]); err != nil { return fmt.Errorf("got VerifyInclusion(%d,%d,...)=%v; want nil", rsp.LeafIndex, sth.TreeSize, err) } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } return nil } // checkPreCertEntry retrieves a pre-cert from a known index and checks it. func (t *testInfo) checkPreCertEntry(ctx context.Context, precertIndex int64, tbs []byte) error { precertEntries, err := t.client().GetEntries(ctx, precertIndex, precertIndex) t.stats.expect(ctfe.GetEntriesName, 200) if err != nil { return fmt.Errorf("got GetEntries(%d,%d)=(nil,%v); want (_,nil)", precertIndex, precertIndex, err) } if len(precertEntries) != 1 { return fmt.Errorf("len(entries)=%d; want %d", len(precertEntries), 1) } leaf := precertEntries[0].Leaf ts := leaf.TimestampedEntry fmt.Printf("%s: Entry[%d] = {Index:%d Leaf:{Version:%v TS:{EntryType:%v Timestamp:%v}}}\n", t.prefix, precertIndex, precertEntries[0].Index, leaf.Version, ts.EntryType, timeFromMS(ts.Timestamp)) if ts.EntryType != ct.PrecertLogEntryType { return fmt.Errorf("leaf[%d].ts.EntryType=%v; want PrecertLogEntryType", precertIndex, ts.EntryType) } if !bytes.Equal(ts.PrecertEntry.TBSCertificate, tbs) { return fmt.Errorf("leaf[%d].ts.PrecertEntry differs from originally uploaded cert", precertIndex) } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } return nil } // RunCTIntegrationForLog tests against the log with configuration cfg, with a set // of comma-separated server addresses given by servers, assuming that testdir holds // a variety of test data files. // nolint: gocyclo func RunCTIntegrationForLog(cfg *configpb.LogConfig, servers, metricsServers, testdir string, mmd time.Duration, stats *logStats) error { ctx := context.Background() pool, err := NewRandomPool(servers, cfg.PublicKey, cfg.Prefix) if err != nil { return fmt.Errorf("failed to create pool: %v", err) } t := testInfo{ prefix: cfg.Prefix, cfg: cfg, metricsServers: metricsServers, stats: stats, pool: pool, hasher: rfc6962.DefaultHasher, } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 0: get accepted roots, which should just be the fake CA. roots, err := t.client().GetAcceptedRoots(ctx) t.stats.expect(ctfe.GetRootsName, 200) if err != nil { return fmt.Errorf("got GetAcceptedRoots()=(nil,%v); want (_,nil)", err) } if len(roots) > 2 { return fmt.Errorf("len(GetAcceptedRoots())=%d; want <=2", len(roots)) } // Stage 1: get the STH, which should be empty. sth0, err := t.client().GetSTH(ctx) t.stats.expect(ctfe.GetSTHName, 200) if err != nil { return fmt.Errorf("got GetSTH()=(nil,%v); want (_,nil)", err) } if sth0.Version != 0 { return fmt.Errorf("sth.Version=%v; want V1(0)", sth0.Version) } if sth0.TreeSize != 0 { return fmt.Errorf("sth.TreeSize=%d; want 0", sth0.TreeSize) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth0.Timestamp), sth0.TreeSize, sth0.SHA256RootHash) // Stage 2: add a single cert (the intermediate CA), get an SCT. var scts [21]*ct.SignedCertificateTimestamp // 0=int-ca, 1-20=leaves var chain [21][]ct.ASN1Cert chain[0], err = GetChain(testdir, "int-ca.cert") if err != nil { return fmt.Errorf("failed to load certificate: %v", err) } issuer, err := x509.ParseCertificate(chain[0][0].Data) if err != nil { return fmt.Errorf("failed to parse int-ca.cert: %v", err) } scts[0], err = t.client().AddChain(ctx, chain[0]) t.stats.expect(ctfe.AddChainName, 200) if err != nil { return fmt.Errorf("got AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err) } // Display the SCT fmt.Printf("%s: Uploaded int-ca.cert to %v log, got SCT(time=%q)\n", t.prefix, scts[0].SCTVersion, timeFromMS(scts[0].Timestamp)) // Keep getting the STH until tree size becomes 1 and check the cert is included. sth1, err := t.awaitTreeSize(ctx, 1, true, mmd) if err != nil { return fmt.Errorf("AwaitTreeSize(1)=(nil,%v); want (_,nil)", err) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth1.Timestamp), sth1.TreeSize, sth1.SHA256RootHash) if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } if err := t.checkInclusionOf(ctx, chain[0], scts[0], sth1); err != nil { return err } // Stage 2.5: add the same cert, expect an SCT with the same timestamp as before. var sctCopy *ct.SignedCertificateTimestamp sctCopy, err = t.client().AddChain(ctx, chain[0]) if err != nil { return fmt.Errorf("got re-AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err) } t.stats.expect(ctfe.AddChainName, 200) if scts[0].Timestamp != sctCopy.Timestamp { return fmt.Errorf("got sct @ %v; want @ %v", sctCopy, scts[0]) } // Stage 3: add a second cert, wait for tree size = 2 chain[1], err = GetChain(testdir, "leaf01.chain") if err != nil { return fmt.Errorf("failed to load certificate: %v", err) } scts[1], err = t.client().AddChain(ctx, chain[1]) t.stats.expect(ctfe.AddChainName, 200) if err != nil { return fmt.Errorf("got AddChain(leaf01)=(nil,%v); want (_,nil)", err) } fmt.Printf("%s: Uploaded cert01.chain to %v log, got SCT(time=%q)\n", t.prefix, scts[1].SCTVersion, timeFromMS(scts[1].Timestamp)) sth2, err := t.awaitTreeSize(ctx, 2, true, mmd) if err != nil { return fmt.Errorf("failed to get STH for size=1: %v", err) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth2.TreeSize, sth2.SHA256RootHash) // Stage 4: get a consistency proof from size 1-> size 2. proof12, err := t.client().GetSTHConsistency(ctx, 1, 2) t.stats.expect(ctfe.GetSTHConsistencyName, 200) if err != nil { return fmt.Errorf("got GetSTHConsistency(1, 2)=(nil,%v); want (_,nil)", err) } // sth2 // / \ // sth1 => a b // | | | // d0 d0 d1 // So consistency proof is [b] and we should have: // sth2 == SHA256(0x01 | sth1 | b) if len(proof12) != 1 { return fmt.Errorf("len(proof12)=%d; want 1", len(proof12)) } if err := t.checkCTConsistencyProof(sth1, sth2, proof12); err != nil { return fmt.Errorf("got CheckCTConsistencyProof(sth1,sth2,proof12)=%v; want nil", err) } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 4.5: get a consistency proof from size 0-> size 2, which should be empty. proof02, err := t.client().GetSTHConsistency(ctx, 0, 2) t.stats.expect(ctfe.GetSTHConsistencyName, 200) if err != nil { return fmt.Errorf("got GetSTHConsistency(0, 2)=(nil,%v); want (_,nil)", err) } if len(proof02) != 0 { return fmt.Errorf("len(proof02)=%d; want 0", len(proof02)) } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 5: add certificates 2, 3, 4, 5,...N, for some random N in [4,20] atLeast := 4 count := atLeast + rand.Intn(20-atLeast) for i := 2; i <= count; i++ { filename := fmt.Sprintf("leaf%02d.chain", i) chain[i], err = GetChain(testdir, filename) if err != nil { return fmt.Errorf("failed to load certificate: %v", err) } scts[i], err = t.client().AddChain(ctx, chain[i]) t.stats.expect(ctfe.AddChainName, 200) if err != nil { return fmt.Errorf("got AddChain(leaf%02d)=(nil,%v); want (_,nil)", i, err) } } fmt.Printf("%s: Uploaded leaf02-leaf%02d to log, got SCTs\n", t.prefix, count) if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 6: keep getting the STH until tree size becomes 1 + N (allows for int-ca.cert). treeSize := 1 + count sthN, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd) if err != nil { return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN.Timestamp), sthN.TreeSize, sthN.SHA256RootHash) // Stage 7: get a consistency proof from 2->(1+N). proof2N, err := t.client().GetSTHConsistency(ctx, 2, uint64(treeSize)) t.stats.expect(ctfe.GetSTHConsistencyName, 200) if err != nil { return fmt.Errorf("got GetSTHConsistency(2, %d)=(nil,%v); want (_,nil)", treeSize, err) } fmt.Printf("%s: Proof size 2->%d: %x\n", t.prefix, treeSize, proof2N) if err := t.checkCTConsistencyProof(sth2, sthN, proof2N); err != nil { return fmt.Errorf("got CheckCTConsistencyProof(sth2,sthN,proof2N)=%v; want nil", err) } // Stage 8: get entries [1, N] (start at 1 to skip int-ca.cert) entries, err := t.client().GetEntries(ctx, 1, int64(count)) t.stats.expect(ctfe.GetEntriesName, 200) if err != nil { return fmt.Errorf("got GetEntries(1,%d)=(nil,%v); want (_,nil)", count, err) } if len(entries) < count { return fmt.Errorf("len(entries)=%d; want %d", len(entries), count) } gotHashes := make(map[[sha256.Size]byte]bool) wantHashes := make(map[[sha256.Size]byte]bool) for i, entry := range entries { leaf := entry.Leaf ts := leaf.TimestampedEntry if leaf.Version != 0 { return fmt.Errorf("leaf[%d].Version=%v; want V1(0)", i, leaf.Version) } if leaf.LeafType != ct.TimestampedEntryLeafType { return fmt.Errorf("leaf[%d].Version=%v; want TimestampedEntryLeafType", i, leaf.LeafType) } if ts.EntryType != ct.X509LogEntryType { return fmt.Errorf("leaf[%d].ts.EntryType=%v; want X509LogEntryType", i, ts.EntryType) } // The certificates might not be sequenced in the order they were uploaded, so // compare the set of hashes. gotHashes[sha256.Sum256(ts.X509Entry.Data)] = true wantHashes[sha256.Sum256(chain[i+1][0].Data)] = true } if diff := pretty.Compare(gotHashes, wantHashes); diff != "" { return fmt.Errorf("retrieved cert hashes don't match uploaded cert hashes, diff:\n%v", diff) } fmt.Printf("%s: Got entries [1:%d+1]\n", t.prefix, count) if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 9: get an audit proof for each certificate we have an SCT for. for i := 1; i <= count; i++ { if err := t.checkInclusionOf(ctx, chain[i], scts[i], sthN); err != nil { return err } } fmt.Printf("%s: Got inclusion proofs [1:%d+1]\n", t.prefix, count) if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 10: attempt to upload a corrupt certificate. corruptChain := make([]ct.ASN1Cert, len(chain[1])) copy(corruptChain, chain[1]) corruptAt := len(corruptChain[0].Data) - 3 corruptChain[0].Data[corruptAt] = corruptChain[0].Data[corruptAt] + 1 if sct, err := t.client().AddChain(ctx, corruptChain); err == nil { return fmt.Errorf("got AddChain(corrupt-cert)=(%+v,nil); want (nil,error)", sct) } t.stats.expect(ctfe.AddChainName, 400) fmt.Printf("%s: AddChain(corrupt-cert)=nil,%v\n", t.prefix, err) // Stage 11: attempt to upload a certificate without chain. if sct, err := t.client().AddChain(ctx, chain[1][0:0]); err == nil { return fmt.Errorf("got AddChain(leaf-only)=(%+v,nil); want (nil,error)", sct) } t.stats.expect(ctfe.AddChainName, 400) fmt.Printf("%s: AddChain(leaf-only)=nil,%v\n", t.prefix, err) if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 12: build and add a pre-certificate. signer, err := MakeSigner(testdir) if err != nil { return fmt.Errorf("failed to retrieve signer for re-signing: %v", err) } generator, err := NewSyntheticChainGenerator(chain[1], signer, time.Time{}) if err != nil { return fmt.Errorf("failed to create chain generator: %v", err) } prechain, tbs, err := generator.PreCertChain() if err != nil { return fmt.Errorf("failed to build pre-certificate: %v", err) } precertSCT, err := t.client().AddPreChain(ctx, prechain) t.stats.expect(ctfe.AddPreChainName, 200) if err != nil { return fmt.Errorf("got AddPreChain()=(nil,%v); want (_,nil)", err) } fmt.Printf("%s: Uploaded precert to %v log, got SCT(time=%q)\n", t.prefix, precertSCT.SCTVersion, timeFromMS(precertSCT.Timestamp)) treeSize++ sthN1, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd) if err != nil { return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN1.Timestamp), sthN1.TreeSize, sthN1.SHA256RootHash) // Stage 13: retrieve and check pre-cert. precertIndex := int64(count + 1) if err := t.checkPreCertEntry(ctx, precertIndex, tbs); err != nil { return fmt.Errorf("failed to check pre-cert entry: %v", err) } // Stage 14: get an inclusion proof for the precert. if err := t.checkInclusionOfPreCert(ctx, tbs, issuer, precertSCT, sthN1); err != nil { return fmt.Errorf("failed to check inclusion of pre-cert entry: %v", err) } // Stage 15: invalid consistency proof if rsp, err := t.client().GetSTHConsistency(ctx, 2, 299); err == nil { return fmt.Errorf("got GetSTHConsistency(2,299)=(%+v,nil); want (nil,_)", rsp) } t.stats.expect(ctfe.GetSTHConsistencyName, 400) fmt.Printf("%s: GetSTHConsistency(2,299)=(nil,_)\n", t.prefix) // Stage 16: invalid inclusion proof; expect a client.RspError{404}. wrong := sha256.Sum256([]byte("simply wrong")) if rsp, err := t.client().GetProofByHash(ctx, wrong[:], sthN1.TreeSize); err == nil { return fmt.Errorf("got GetProofByHash(wrong, size=%d)=(%v,nil); want (nil,_)", sthN1.TreeSize, rsp) } else if rspErr, ok := err.(client.RspError); ok { if rspErr.StatusCode != http.StatusNotFound { return fmt.Errorf("got GetProofByHash(wrong)=_, %d; want (nil, 404)", rspErr.StatusCode) } } else { return fmt.Errorf("got GetProofByHash(wrong)=%+v (%T); want (client.RspError)", err, err) } t.stats.expect(ctfe.GetProofByHashName, 404) fmt.Printf("%s: GetProofByHash(wrong,%d)=(nil,_)\n", t.prefix, sthN1.TreeSize) // Stage 17: build and add a pre-certificate signed by a pre-issuer. preIssuerChain, preTBS, err := makePreIssuerPrecertChain(chain[1], issuer, signer) if err != nil { return fmt.Errorf("failed to build pre-issued pre-certificate: %v", err) } preIssuerCertSCT, err := pool.Next().AddPreChain(ctx, preIssuerChain) stats.expect(ctfe.AddPreChainName, 200) if err != nil { return fmt.Errorf("got AddPreChain()=(nil,%v); want (_,nil)", err) } fmt.Printf("%s: Uploaded pre-issued precert to %v log, got SCT(time=%q)\n", t.prefix, precertSCT.SCTVersion, timeFromMS(precertSCT.Timestamp)) treeSize++ sthN2, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd) if err != nil { return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN2.Timestamp), sthN2.TreeSize, sthN2.SHA256RootHash) // Stage 18: retrieve and check pre-issued pre-cert. preIssuerCertIndex := int64(count + 2) if err := t.checkPreCertEntry(ctx, preIssuerCertIndex, preTBS); err != nil { return fmt.Errorf("failed to check pre-issued pre-cert entry: %v", err) } // Stage 19: get an inclusion proof for the pre-issued precert. if err := t.checkInclusionOfPreCert(ctx, preTBS, issuer, preIssuerCertSCT, sthN2); err != nil { return fmt.Errorf("failed to check inclusion of pre-cert entry: %v", err) } // Final stats check. if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } return nil } // RunCTLifecycleForLog does a simple log lifecycle test. The log // is assumed to be newly created when this test runs. A random number of // entries are then submitted to build up a queue. The log is set to // DRAINING state and the test checks that all the entries are integrated // into the tree and we can verify a consistency proof to the latest entry. func RunCTLifecycleForLog(cfg *configpb.LogConfig, servers, metricsServers, adminServer string, testDir string, mmd time.Duration, stats *logStats) error { // Retrieve the test data. caChain, err := GetChain(testDir, "int-ca.cert") if err != nil { return err } leafChain, err := GetChain(testDir, "leaf01.chain") if err != nil { return err } signer, err := MakeSigner(testDir) if err != nil { return err } generator, err := NewSyntheticChainGenerator(leafChain, signer, time.Time{}) if err != nil { return err } ctx := context.Background() pool, err := NewRandomPool(servers, cfg.PublicKey, cfg.Prefix) if err != nil { return fmt.Errorf("failed to create pool: %v", err) } t := testInfo{ prefix: cfg.Prefix, cfg: cfg, metricsServers: metricsServers, adminServer: adminServer, stats: stats, pool: pool, hasher: rfc6962.DefaultHasher, } if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } // Stage 0: get accepted roots, which should just be the fake CA. roots, err := t.client().GetAcceptedRoots(ctx) t.stats.expect(ctfe.GetRootsName, 200) if err != nil { return fmt.Errorf("got GetAcceptedRoots()=(nil,%v); want (_,nil)", err) } if got := len(roots); got != 1 { return fmt.Errorf("len(GetAcceptedRoots())=%d; want 1", got) } // Stage 1: get the STH, which should be empty. sth0, err := t.client().GetSTH(ctx) t.stats.expect(ctfe.GetSTHName, 200) if err != nil { return fmt.Errorf("got GetSTH()=(nil,%v); want (_,nil)", err) } if sth0.Version != 0 { return fmt.Errorf("sth.Version=%v; want V1(0)", sth0.Version) } if sth0.TreeSize != 0 { return fmt.Errorf("sth.TreeSize=%d; want 0", sth0.TreeSize) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth0.Timestamp), sth0.TreeSize, sth0.SHA256RootHash) // Stage 2: add certificates 2, 3, 4, 5,...N, for some random N with // at least 2000 so the queue builds up a bit. atLeast := 2000 count := atLeast + rand.Intn(3000-atLeast) fmt.Printf("%s: Starting upload of %d certificates ....\n", t.prefix, count) for i := 1; i <= count; i++ { chain, err := generator.CertChain() if err != nil { return err } _, err = t.client().AddChain(ctx, chain) t.stats.expect(ctfe.AddChainName, 200) if err != nil { return fmt.Errorf("got AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err) } } fmt.Printf("%s: Upload of %d certificates complete\n", t.prefix, count) // Stage 3: Set the log to DRAINING using the admin server. ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_DRAINING); err != nil { return fmt.Errorf("setTreeState(DRAINING)=%v, want: nil", err) } // Stage 4a: Get an updated STH. We'll use this point for a consistency // proof later. sth1, err := t.client().GetSTH(ctx) t.stats.expect(ctfe.GetSTHName, 200) if err != nil { return fmt.Errorf("got GetSTH(DRAINING)=(nil,%v); want (_,nil)", err) } if sth1.Version != 0 { return fmt.Errorf("sth.Version=%v; want V1(0)", sth1.Version) } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth1.Timestamp), sth1.TreeSize, sth1.SHA256RootHash) // Stage 4b: Wait for the queue to drain and everything to be integrated. sth2, err := t.awaitTreeSize(context.Background(), uint64(count), true, mmd) if err != nil { return err } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth2.TreeSize, sth2.SHA256RootHash) // Stage 5. Get a consistency proof from sth1 to sth2 and verify it. proof, err := t.client().GetSTHConsistency(ctx, sth1.TreeSize, sth2.TreeSize) t.stats.expect(ctfe.GetSTHConsistencyName, 200) if err != nil { return err } if err := t.checkCTConsistencyProof(sth1, sth2, proof); err != nil { return err } fmt.Printf("%s: VerifiedConsistency(time=%q, size1=%d, size2=%d): final roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth1.TreeSize, sth2.TreeSize, sth2.SHA256RootHash) // Stage 6. Try to submit a chain and it should be rejected with 403. _, err = t.client().AddChain(ctx, caChain) t.stats.expect(ctfe.AddChainName, 403) if err == nil || !strings.Contains(err.Error(), "403") { return fmt.Errorf("got AddChain(DRAINING: int-ca.cert)=(nil,%v); want (_,err inc. 403)", err) } // Stage 7a - Set the log state back to ACTIVE and submit again. This should // be accepted. ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) defer cancel() if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_ACTIVE); err != nil { return fmt.Errorf("setTreeState(ACTIVE)=%v, want: nil", err) } _, err = t.client().AddChain(ctx, caChain) t.stats.expect(ctfe.AddChainName, 200) if err != nil { return fmt.Errorf("got AddChain(ACTIVE: int-ca.cert)=(nil,%v); want (_,nil)", err) } // Stage 7b: Wait for that new certificate to be integrated. sthCaCert, err := t.awaitTreeSize(context.Background(), uint64(count+1), true, mmd) if err != nil { return err } fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthCaCert.Timestamp), sthCaCert.TreeSize, sthCaCert.SHA256RootHash) // Stage 8 - Set the log to FROZEN using the admin server. ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) defer cancel() if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_FROZEN); err != nil { return fmt.Errorf("setTreeState(FROZEN)=%v, want: nil", err) } // Stage 9 - Try to upload the pre-cert again and it should be rejected // with FORBIDDEN status. _, err = t.client().AddChain(ctx, caChain) t.stats.expect(ctfe.AddChainName, 403) if err == nil || !strings.Contains(err.Error(), "403") { return fmt.Errorf("got AddChain(FROZEN: int-ca.cert)=(nil,%v); want (_,err inc. 403)", err) } // Stage 10 - Obtain latest STH and check it hasn't increased in size since // the last submission. sth3, err := t.client().GetSTH(ctx) t.stats.expect(ctfe.GetSTHName, 200) if err != nil { return fmt.Errorf("got GetSTH(FROZEN)=(nil,%v); want (_,nil)", err) } // We know that anything queued was integrated so is should be impossible // that the tree has grown. There is one extra certificate from the test // that we could submit in Stage 7a. if sth2.TreeSize+1 != sth3.TreeSize { return fmt.Errorf("sth3 got TreeSize=%d, want: %d", sth3.TreeSize, sth2.TreeSize) } // Final stats check. if err := t.checkStats(); err != nil { return fmt.Errorf("stats check failed: %v", err) } return nil } // timeFromMS converts a timestamp in milliseconds (as used in CT) to a time.Time. func timeFromMS(ts uint64) time.Time { secs := int64(ts / 1000) msecs := int64(ts % 1000) return time.Unix(secs, msecs*1000000) } // GetChain retrieves a certificate from a file of the given name and directory. func GetChain(dir, path string) ([]ct.ASN1Cert, error) { certdata, err := os.ReadFile(filepath.Join(dir, path)) if err != nil { return nil, fmt.Errorf("failed to load certificate: %v", err) } return CertsFromPEM(certdata), nil } // CertsFromPEM loads X.509 certificates from the provided PEM-encoded data. func CertsFromPEM(data []byte) []ct.ASN1Cert { var chain []ct.ASN1Cert for { var block *pem.Block block, data = pem.Decode(data) if block == nil { break } if block.Type == "CERTIFICATE" { chain = append(chain, ct.ASN1Cert{Data: block.Bytes}) } } return chain } // checkCTConsistencyProof checks the given consistency proof. func (t *testInfo) checkCTConsistencyProof(sth1, sth2 *ct.SignedTreeHead, pf [][]byte) error { return proof.VerifyConsistency(t.hasher, sth1.TreeSize, sth2.TreeSize, pf, sth1.SHA256RootHash[:], sth2.SHA256RootHash[:]) } // buildNewPrecertData creates a new pre-certificate based on the given template cert (which is // modified) func buildNewPrecertData(cert, issuer *x509.Certificate, signer crypto.Signer) ([]byte, error) { // Randomize the subject key ID. randData := make([]byte, 128) if _, err := cryptorand.Read(randData); err != nil { return nil, fmt.Errorf("failed to read random data: %v", err) } cert.SubjectKeyId = randData // Add the CT poison extension. cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{ Id: x509.OIDExtensionCTPoison, Critical: true, Value: []byte{0x05, 0x00}, // ASN.1 NULL }) // Create a fresh certificate, signed by the issuer. cert.AuthorityKeyId = issuer.SubjectKeyId data, err := x509.CreateCertificate(cryptorand.Reader, cert, issuer, cert.PublicKey, signer) if err != nil { return nil, fmt.Errorf("failed to CreateCertificate: %v", err) } return data, nil } // MakeSigner creates a signer using the private key in the test directory. func MakeSigner(testDir string) (crypto.Signer, error) { fileName := filepath.Join(testDir, "int-ca.privkey.pem") keyPEM, err := os.ReadFile(fileName) if err != nil { return nil, fmt.Errorf("error reading file %q: %w", fileName, err) } block, _ := pem.Decode(keyPEM) decPEM, err := x509.DecryptPEMBlock(block, []byte("babelfish")) if err != nil { return nil, fmt.Errorf("failed to decrypt file %q: %w", fileName, err) } key, err := x509.ParseECPrivateKey(decPEM) if err != nil { return nil, fmt.Errorf("failed to load private key for re-signing: %v", err) } return key, nil } // Track HTTP requests/responses so we can check the stats exported by the log. type logStats struct { logID int64 reqs map[string]int // entrypoint =>count rsps map[string]map[string]int // entrypoint => status => count } func newLogStats(logID int64) *logStats { stats := logStats{ logID: logID, reqs: make(map[string]int), rsps: make(map[string]map[string]int), } for _, ep := range ctfe.Entrypoints { stats.rsps[string(ep)] = make(map[string]int) } return &stats } func (ls *logStats) expect(ep ctfe.EntrypointName, rc int) { if ls == nil { return } ls.reqs[string(ep)]++ ls.rsps[string(ep)][strconv.Itoa(rc)]++ } func (ls *logStats) fromServer(ctx context.Context, servers string) (*logStats, error) { reqsRE := regexp.MustCompile(reqStatsRE) rspsRE := regexp.MustCompile(rspStatsRE) got := newLogStats(int64(ls.logID)) for _, s := range strings.Split(servers, ",") { httpReq, err := http.NewRequest(http.MethodGet, "http://"+s+"/metrics", nil) if err != nil { return nil, fmt.Errorf("failed to build GET request: %v", err) } c := new(http.Client) httpRsp, err := ctxhttp.Do(ctx, c, httpReq) if err != nil { return nil, fmt.Errorf("getting stats failed: %v", err) } defer func() { if err := httpRsp.Body.Close(); err != nil { fmt.Printf("Operation to close http response body failed: %v\n", err) } }() if httpRsp.StatusCode != http.StatusOK { return nil, fmt.Errorf("got HTTP Status %q", httpRsp.Status) } scanner := bufio.NewScanner(httpRsp.Body) for scanner.Scan() { line := scanner.Text() m := reqsRE.FindStringSubmatch(line) if m != nil { if m[2] == strconv.FormatInt(ls.logID, 10) { if val, err := strconv.ParseFloat(m[3], 64); err == nil { ep := m[1] got.reqs[ep] += int(val) } } continue } m = rspsRE.FindStringSubmatch(line) if m != nil { if m[2] == strconv.FormatInt(ls.logID, 10) { if val, err := strconv.ParseFloat(m[4], 64); err == nil { ep := m[1] rc := m[3] got.rsps[ep][rc] += int(val) } } continue } } } return got, nil } func (ls *logStats) check(cfg *configpb.LogConfig, servers string) error { if ls == nil { return nil } ctx := context.Background() got, err := ls.fromServer(ctx, servers) if err != nil { return err } // Now compare accumulated actual stats with what we expect to see. if !reflect.DeepEqual(got.reqs, ls.reqs) { return fmt.Errorf("got reqs %+v; want %+v", got.reqs, ls.reqs) } if !reflect.DeepEqual(got.rsps, ls.rsps) { return fmt.Errorf("got rsps %+v; want %+v", got.rsps, ls.rsps) } return nil } func setTreeState(ctx context.Context, adminServer string, logID int64, state trillian.TreeState) error { treeStateMask := &fieldmaskpb.FieldMask{ Paths: []string{"tree_state"}, } req := &trillian.UpdateTreeRequest{ Tree: &trillian.Tree{ TreeId: logID, TreeState: state, }, UpdateMask: treeStateMask, } conn, err := grpc.Dial(adminServer, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return err } defer func() { if err := conn.Close(); err != nil { fmt.Printf("Operation to close RPC connection failed: %v\n", err) } }() adminClient := trillian.NewTrillianAdminClient(conn) _, err = adminClient.UpdateTree(ctx, req) if err != nil { return err } return nil } // NotAfterForLog returns a NotAfter time to be used for certs submitted // to the given log instance, allowing for any temporal shard configuration. func NotAfterForLog(c *configpb.LogConfig) (time.Time, error) { if c.NotAfterStart == nil && c.NotAfterLimit == nil { return time.Now().Add(24 * time.Hour), nil } if c.NotAfterStart != nil { if err := c.NotAfterStart.CheckValid(); err != nil { return time.Time{}, fmt.Errorf("failed to parse NotAfterStart: %v", err) } start := c.NotAfterStart.AsTime() if c.NotAfterLimit == nil { return start.Add(24 * time.Hour), nil } if err := c.NotAfterLimit.CheckValid(); err != nil { return time.Time{}, fmt.Errorf("failed to parse NotAfterLimit: %v", err) } limit := c.NotAfterLimit.AsTime() midDelta := limit.Sub(start) / 2 return start.Add(midDelta), nil } if err := c.NotAfterLimit.CheckValid(); err != nil { return time.Time{}, fmt.Errorf("failed to parse NotAfterLimit: %v", err) } limit := c.NotAfterLimit.AsTime() return limit.Add(-1 * time.Hour), nil } google-certificate-transparency-go-2308f62/trillian/integration/ct_integration_test.cfg000066400000000000000000000075001462611535200315350ustar00rootroot00000000000000config { log_id: @TREE_ID@ prefix: "athos" roots_pem_file: "@TESTDATA@/fake-ca.cert" roots_pem_file: "@TESTDATA@/../../testdata/gossip-root.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xc4\x2d\x99\xc7\x9e\x31\x77\x99\xd7\xda\x4c\xab\xdb\xb9\x37\xeb\x95\xde\x6a\x72\x1b\x84\xbd\x0b\xfe\xb3\x4b\x1e\xce\xa8\xbb\x2f\xa1\x44\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 # Note: 120s is unrealistically fast for a real log, but OK for testing. } config { log_id: @TREE_ID@ prefix: "porthos" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xd8\x8a\x49\xa2\x15\x3c\xbe\xb5\xb7\x6c\x63\xdc\xfd\xc0\x36\x64\x24\x88\xc3\x57\x9d\xfa\xd4\xa8\x70\x78\x32\x72\x29\x1a\xb1\x6f\xa1\x44\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC } config { log_id: @TREE_ID@ prefix: "aramis" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x97\x94\x1f\x33\xa7\x36\xac\x0b\xcb\x11\x09\x23\x8a\xfb\x73\xc1\x17\xc5\xc5\x23\x5d\xdb\xa8\x8f\x32\x94\xc5\xdd\x67\x4b\xff\x5e\xa1\x44\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 ctfe_storage_connection_string: "mysql://cttest:beeblebrox@tcp(mysql:3306)/cttest" extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE } google-certificate-transparency-go-2308f62/trillian/integration/ct_integration_test.go000066400000000000000000000161311462611535200314030ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "context" "encoding/pem" "flag" "fmt" "os" "strings" "testing" "time" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/storage/testdb" "google.golang.org/protobuf/types/known/anypb" timestamp "google.golang.org/protobuf/types/known/timestamppb" ) var ( adminServer = flag.String("admin_server", "", "Address of log admin RPC server. Required for lifecycle test.") httpServers = flag.String("ct_http_servers", "localhost:8092", "Comma-separated list of (assumed interchangeable) servers, each as address:port") metricsServers = flag.String("ct_metrics_servers", "localhost:8093", "Comma-separated list of (assumed interchangeable) metrics servers, each as address:port") testDir = flag.String("testdata_dir", "testdata", "Name of directory with test data") logConfig = flag.String("log_config", "", "File holding log config in JSON") mmd = flag.Duration("mmd", 30*time.Second, "MMD for tested logs") skipStats = flag.Bool("skip_stats", false, "Skip checks of expected log statistics") ) func commonSetup(t *testing.T) []*configpb.LogConfig { t.Helper() if *logConfig == "" { t.Skip("Integration test skipped as no log config provided") } cfgs, err := ctfe.LogConfigFromFile(*logConfig) if err != nil { t.Fatalf("Failed to read log config: %v", err) } return cfgs } func TestLiveCTIntegration(t *testing.T) { flag.Parse() cfgs := commonSetup(t) for _, cfg := range cfgs { cfg := cfg // capture config t.Run(cfg.Prefix, func(t *testing.T) { t.Parallel() var stats *logStats if !*skipStats { stats = newLogStats(cfg.LogId) } if err := RunCTIntegrationForLog(cfg, *httpServers, *metricsServers, *testDir, *mmd, stats); err != nil { t.Errorf("%s: failed: %v", cfg.Prefix, err) } }) } } func TestLiveLifecycleCTIntegration(t *testing.T) { flag.Parse() cfgs := commonSetup(t) for _, cfg := range cfgs { cfg := cfg // capture config t.Run(cfg.Prefix, func(t *testing.T) { t.Parallel() var stats *logStats if !*skipStats { stats = newLogStats(cfg.LogId) } if err := RunCTLifecycleForLog(cfg, *httpServers, *metricsServers, *adminServer, *testDir, *mmd, stats); err != nil { t.Errorf("%s: failed: %v", cfg.Prefix, err) } }) } } const ( rootsPEMFile = "../testdata/fake-ca.cert" pubKeyPEMFile = "../testdata/ct-http-server.pubkey.pem" privKeyPEMFile = "../testdata/ct-http-server.privkey.pem" privKeyPassword = "dirk" ) func TestInProcessCTIntegration(t *testing.T) { testdb.SkipIfNoMySQL(t) pubKeyDER, err := loadPublicKey(pubKeyPEMFile) if err != nil { t.Fatalf("Could not load public key: %v", err) } pubKey := &keyspb.PublicKey{Der: pubKeyDER} privKey, err := anypb.New(&keyspb.PEMKeyFile{Path: privKeyPEMFile, Password: privKeyPassword}) if err != nil { t.Fatalf("Could not marshal private key as protobuf Any: %v", err) } ctx := context.Background() cfgs := []*configpb.LogConfig{ { Prefix: "athos", RootsPemFile: []string{rootsPEMFile}, PublicKey: pubKey, PrivateKey: privKey, }, { Prefix: "porthos", RootsPemFile: []string{rootsPEMFile}, PublicKey: pubKey, PrivateKey: privKey, }, { Prefix: "aramis", RootsPemFile: []string{rootsPEMFile}, PublicKey: pubKey, PrivateKey: privKey, }, } env, err := NewCTLogEnv(ctx, cfgs, 2, "TestInProcessCTIntegration") if err != nil { t.Fatalf("Failed to launch test environment: %v", err) } defer env.Close() mmd := 120 * time.Second // Run a container for the parallel sub-tests, so that we wait until they // all complete before terminating the test environment. t.Run("container", func(t *testing.T) { for _, cfg := range cfgs { cfg := cfg // capture config t.Run(cfg.Prefix, func(t *testing.T) { t.Parallel() stats := newLogStats(cfg.LogId) if err := RunCTIntegrationForLog(cfg, env.CTAddr, env.CTAddr, "../testdata", mmd, stats); err != nil { t.Errorf("%s: failed: %v", cfg.Prefix, err) } }) } }) } func loadPublicKey(path string) ([]byte, error) { pemKey, err := os.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(pemKey) if block == nil { return nil, fmt.Errorf("could not decode PEM public key: %v", path) } if block.Type != "PUBLIC KEY" { return nil, fmt.Errorf("got %q PEM, want \"PUBLIC KEY\": %v", block.Type, path) } return block.Bytes, nil } func TestNotAfterForLog(t *testing.T) { tests := []struct { desc string cfg *configpb.LogConfig want time.Time wantErr string }{ { desc: "no-limits", cfg: &configpb.LogConfig{}, want: time.Now().Add(24 * time.Hour), }, { desc: "malformed-start", cfg: &configpb.LogConfig{ NotAfterStart: ×tamp.Timestamp{Seconds: 1000, Nanos: -1}, }, wantErr: "failed to parse NotAfterStart", }, { desc: "malformed-limit", cfg: &configpb.LogConfig{ NotAfterLimit: ×tamp.Timestamp{Seconds: 1000, Nanos: -1}, }, wantErr: "failed to parse NotAfterLimit", }, { desc: "start-no-limit", cfg: &configpb.LogConfig{ NotAfterStart: ×tamp.Timestamp{Seconds: 1230000000}, }, want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(24 * time.Hour), }, { desc: "limit-no-start", cfg: &configpb.LogConfig{ NotAfterLimit: ×tamp.Timestamp{Seconds: 1230000000}, }, want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(-1 * time.Hour), }, { desc: "mid-range", cfg: &configpb.LogConfig{ NotAfterStart: ×tamp.Timestamp{Seconds: 1230000000}, NotAfterLimit: ×tamp.Timestamp{Seconds: 1230000000 + 86400}, }, want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(43200 * time.Second), }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { got, err := NotAfterForLog(test.cfg) if err != nil { if len(test.wantErr) == 0 { t.Errorf("NotAfterForLog()=nil,%v, want _,nil", err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("NotAfterForLog()=nil,%v, want _,err containing %q", err, test.wantErr) } return } if len(test.wantErr) > 0 { t.Errorf("NotAfterForLog()=%v, nil, want nil,err containing %q", got, test.wantErr) } delta := got.Sub(test.want) if delta < 0 { delta = -delta } if delta > time.Second { t.Errorf("NotAfterForLog()=%v, want %v (delta %v)", got, test.want, delta) } }) } } google-certificate-transparency-go-2308f62/trillian/integration/ct_integration_test.sh000077500000000000000000000026161462611535200314160ustar00rootroot00000000000000#!/bin/bash set -e . "$(go list -f '{{ .Dir }}' github.com/google/trillian)"/integration/functions.sh INTEGRATION_DIR="$( cd "$( dirname "$0" )" && pwd )" . "${INTEGRATION_DIR}"/ct_functions.sh # We're not using Trillian's log setup/turn down, so allow the caller to # specify where the trillian log server is. export RPC_SERVERS=${TRILLIAN_LOG_SERVERS:-localhost:8090} export RPC_SERVER_1=${TRILLIAN_LOG_SERVER_1:-localhost:8090} ct_prep_test 1 # Cleanup for the personality TO_DELETE="${TO_DELETE} ${CT_CFG} ${CT_LIFECYCLE_CFG} ${CT_COMBINED_CONFIG}" TO_KILL+=(${CT_SERVER_PIDS[@]}) COMMON_ARGS="--ct_http_servers=${CT_SERVERS} --ct_metrics_servers=${CT_METRICS_SERVERS} --testdata_dir="$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go)"/trillian/testdata" echo "Running test(s)" pushd "${INTEGRATION_DIR}" set +e go test -v -run ".*LiveCT.*" --timeout=5m ./ --log_config "${CT_CFG}" ${COMMON_ARGS} RESULT=$? set -e popd # Integration test run failed? Clean up and exit if so if [[ "${RESULT}" != "0" ]]; then ct_stop_test TO_KILL=() exit $RESULT fi # Now run the lifecycle test. This will use the same servers but with a # different set of empty logs. pushd "${INTEGRATION_DIR}" set +e go test -v -run ".*LiveLifecycle.*" --timeout=5m ./ --log_config "${CT_LIFECYCLE_CFG}" --admin_server="${RPC_SERVER_1}" ${COMMON_ARGS} RESULT=$? set -e popd ct_stop_test TO_KILL=() exit $RESULT google-certificate-transparency-go-2308f62/trillian/integration/ct_killall.sh000077500000000000000000000004271462611535200274560ustar00rootroot00000000000000#!/bin/bash # Kill all ctfe/trillian related processes. killall $@ ct_server killall $@ trillian_log_server killall $@ trillian_log_signer if [[ -x "${ETCD_DIR}/etcd" ]]; then killall $@ etcd if [[ -x "${PROMETHEUS_DIR}/prometheus" ]]; then killall $@ prometheus fi fi google-certificate-transparency-go-2308f62/trillian/integration/ct_lifecycle_test.cfg000066400000000000000000000073761462611535200311640ustar00rootroot00000000000000config { log_id: @TREE_ID@ prefix: "alpha" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x78\xf4\xe5\xd4\x49\x4e\xf9\xe1\x7e\x28\x5e\x88\xf5\x58\x2d\x6c\xf0\x92\xaf\xd7\xb4\x22\x75\x7b\xc6\xb4\x15\x17\xeb\x59\xad\xd4\x7e\x91\x8c\x92\xbb\x07\xa1\xba\x25\x69\xc7\x38\x04\x9f\x00\x4f\x26\xad\xc8\x54\x3a\x35\x1a\xfe\x67\xf9\x8a\xba\x2a\xdb\x77\x15" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x6f\x67\x62\x64\x1e\x9e\x4d\xe7\x91\xbe\x2d\xd6\x0c\x9e\xb2\x6d\xc3\x46\xc0\x23\x5b\x4b\x77\x6e\x6e\xa3\xac\x70\x01\xf2\x71\xd2\xa1\x44\x03\x42\x00\x04\x78\xf4\xe5\xd4\x49\x4e\xf9\xe1\x7e\x28\x5e\x88\xf5\x58\x2d\x6c\xf0\x92\xaf\xd7\xb4\x22\x75\x7b\xc6\xb4\x15\x17\xeb\x59\xad\xd4\x7e\x91\x8c\x92\xbb\x07\xa1\xba\x25\x69\xc7\x38\x04\x9f\x00\x4f\x26\xad\xc8\x54\x3a\x35\x1a\xfe\x67\xf9\x8a\xba\x2a\xdb\x77\x15" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 # Note: 120s is unrealistically fast for a real log, but OK for testing. } config { log_id: @TREE_ID@ prefix: "beta" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x75\x79\x7c\x29\x9e\xbb\x39\x5b\x35\x24\x53\xd9\xfb\x58\x5d\x7f\x55\x02\x29\x7b\x3c\x9e\x7c\x72\x51\xfc\xc4\xe4\x01\x22\x00\xd3\xbc\xa9\x5a\xff\x06\x99\x5e\x55\xc8\xa9\xf9\xf2\x13\x9c\x80\xc3\xf1\x26\x1f\xe9\x55\x53\x2d\x46\xbb\x2f\x10\x85\xf9\x17\xe2\xe8" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x6b\x0d\xda\x1d\x9f\x23\x43\x94\xea\xa8\xce\x8e\x3b\x05\x71\x6c\xf1\xff\xd5\x0a\x14\xb4\xad\x9a\x9c\x9c\x0a\x64\x29\xb6\xa1\x1d\xa1\x44\x03\x42\x00\x04\x75\x79\x7c\x29\x9e\xbb\x39\x5b\x35\x24\x53\xd9\xfb\x58\x5d\x7f\x55\x02\x29\x7b\x3c\x9e\x7c\x72\x51\xfc\xc4\xe4\x01\x22\x00\xd3\xbc\xa9\x5a\xff\x06\x99\x5e\x55\xc8\xa9\xf9\xf2\x13\x9c\x80\xc3\xf1\x26\x1f\xe9\x55\x53\x2d\x46\xbb\x2f\x10\x85\xf9\x17\xe2\xe8" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC } config { log_id: @TREE_ID@ prefix: "gamma" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x55\x32\x88\x34\xe9\x87\x81\x16\x6f\x41\xb3\xd5\x9d\x64\xae\x6c\x24\xbc\x9c\x6a\x21\x41\x0b\xb8\xd6\x0a\xf7\x8f\xc0\x7a\x0a\xc4\x10\xcf\x88\x0e\xa6\x78\xfd\xba\xde\x4f\x1f\x2b\xc7\x06\xec\x71\xed\x77\x34\xb1\xc7\x7d\xe5\x43\xd3\xdc\x15\x6f\x69\x7b\xf0\x56" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xff\x81\x10\xd0\xb3\x06\x48\xf6\x75\x68\x77\x16\x95\xdd\x34\x80\x4c\x3e\x0f\x60\xc9\x2c\x5a\xf4\xe4\xcf\x07\xc7\x06\x68\xb3\x73\xa1\x44\x03\x42\x00\x04\x55\x32\x88\x34\xe9\x87\x81\x16\x6f\x41\xb3\xd5\x9d\x64\xae\x6c\x24\xbc\x9c\x6a\x21\x41\x0b\xb8\xd6\x0a\xf7\x8f\xc0\x7a\x0a\xc4\x10\xcf\x88\x0e\xa6\x78\xfd\xba\xde\x4f\x1f\x2b\xc7\x06\xec\x71\xed\x77\x34\xb1\xc7\x7d\xe5\x43\xd3\xdc\x15\x6f\x69\x7b\xf0\x56" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 120 ctfe_storage_connection_string: "mysql://cttest:beeblebrox@tcp(mysql:3306)/cttest" extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE } google-certificate-transparency-go-2308f62/trillian/integration/demo-script.cfg000066400000000000000000000022241462611535200277110ustar00rootroot00000000000000config { log_id: @TREE_ID@ prefix: "athos" roots_pem_file: "@TESTDATA@/fake-ca.cert" public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } private_key: { [type.googleapis.com/keyspb.PrivateKey] { der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xc4\x2d\x99\xc7\x9e\x31\x77\x99\xd7\xda\x4c\xab\xdb\xb9\x37\xeb\x95\xde\x6a\x72\x1b\x84\xbd\x0b\xfe\xb3\x4b\x1e\xce\xa8\xbb\x2f\xa1\x44\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } } max_merge_delay_sec: 86400 expected_merge_delay_sec: 7200 } google-certificate-transparency-go-2308f62/trillian/integration/demo-script.sh000077500000000000000000000130411462611535200275660ustar00rootroot00000000000000#!/bin/bash # This is a linear script for demonstrating a Trillian-backed CT log; its contents # are extracted from the main trillian/integration/ct_integration_test.sh script. if [ $(uname) == "Darwin" ]; then URLOPEN=open else URLOPEN=xdg-open fi hash ${URLOPEN} 2>/dev/null || { echo >&2 "WARNING: ${URLOPEN} not found - browser windows will fail to open"; } if [[ ! -d "${GOPATH}" ]]; then echo "Error: GOPATH not set" exit 1 fi if [[ ${PWD} -ef ${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/integration ]]; then echo "Error: cannot run from directory ${PWD}; try: cd ../..; ./trillian/integration/demo-script.sh" exit 1 fi echo 'Prepared before demo: edit trillian/integration/demo-script.cfg to fill in local GOPATH' sed "s~@TESTDATA@~${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/testdata~" ${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/integration/demo-script.cfg > demo-script.cfg echo '-----------------------------------------------' set -x echo 'Reset MySQL database' yes | ${GOPATH}/src/github.com/google/trillian/scripts/resetdb.sh echo 'Building Trillian log code' go build github.com/google/trillian/server/trillian_log_server/ go build github.com/google/trillian/server/trillian_log_signer/ echo 'Start a Trillian Log server (do in separate terminal)' ./trillian_log_server --rpc_endpoint=localhost:6962 --http_endpoint=localhost:6963 --logtostderr & echo 'Start a Trillian Log signer (do in separate terminal)' ./trillian_log_signer --force_master --sequencer_interval=1s --batch_size=500 --rpc_endpoint=localhost:6961 --http_endpoint=localhost:6964 --num_sequencers 2 --logtostderr & echo 'Wait for things to come up' sleep 8 echo 'Building provisioning tool' go build github.com/google/trillian/cmd/createtree/ echo 'Provision a log and remember the its tree ID' tree_id=$(./createtree --admin_server=localhost:6962) echo ${tree_id} echo 'Manually edit CT config file to put the tree ID value in place of @TREE_ID@' sed -i'.bak' "1,/@TREE_ID@/s/@TREE_ID@/${tree_id}/" demo-script.cfg echo 'Building CT personality code' go build github.com/google/certificate-transparency-go/trillian/ctfe/ct_server echo 'Running the CT personality (do in separate terminal)' ./ct_server --log_config=demo-script.cfg --log_rpc_server=localhost:6962 --http_endpoint=localhost:6965 & ct_pid=$! sleep 5 echo 'Log is now accessible -- see in browser window' ${URLOPEN} http://localhost:6965/athos/ct/v1/get-sth echo 'But is has no data, so building the Hammer test tool' go build github.com/google/certificate-transparency-go/trillian/integration/ct_hammer echo 'Hammer time' ./ct_hammer --log_config demo-script.cfg --ct_http_servers=localhost:6965 --mmd=30s --testdata_dir=${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/testdata --logtostderr & hammer_pid=$! echo 'After waiting for a while, refresh the browser window to see a bigger tree' sleep 5 ${URLOPEN} http://localhost:6965/athos/ct/v1/get-sth sleep 10 echo 'Now lets add another log. First kill the hammer' kill -9 ${hammer_pid} echo 'Provision a log and remember the its tree ID' tree_id_2=$(./createtree --admin_server=localhost:6962 --private_key_format=PrivateKey --pem_key_path=${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/testdata/log-rpc-server.privkey.pem --pem_key_password=towel --signature_algorithm=ECDSA) echo ${tree_id_2} echo 'Manually edit CT config file to copy the athos config to be a second config with prefix: "porthos" and with the new tree ID' cp demo-script.cfg demo-script-2.cfg cat demo-script.cfg | sed 's/athos/porthos/' | sed "s/${tree_id}/${tree_id_2}/" >> demo-script-2.cfg echo 'Stop and restart the CT personality to use the new config (note changed --log_config)' kill -9 ${ct_pid} ./ct_server --log_config=demo-script-2.cfg --log_rpc_server=localhost:6962 --http_endpoint=localhost:6965 & sleep 5 echo 'See the new (empty) log' ${URLOPEN} http://localhost:6965/porthos/ct/v1/get-sth echo 'Double Hammer time (note changed --log_config)' ./ct_hammer --log_config demo-script-2.cfg --ct_http_servers=localhost:6965 --mmd=30s --testdata_dir=${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/testdata --logtostderr & hammer_pid=$! sleep 30 echo 'Remember to kill off all of the jobs, so their (hard-coded) ports get freed up. Shortcut:' ${GOPATH}/src/github.com/google/certificate-transparency-go/trillian/integration/ct_killall.sh echo '...but ct_killall does not kill the hammer' killall -9 ct_hammer # Other ideas to extend a linear demo: # 1) Add a temporal log config, which just involves adding a fragment like the following (for 2017): # not_after_start { # seconds: 1483228800 # } # not_after_limit { # seconds: 1514764800 # } # 2) Run multiple signers and use etcd to provide mastership election: # - install etcd with: go install ./vendor/github.com/coreos/etcd/cmd/etcd # - run etcd, which listens on default port :2379 # - drop the --force_master argument to the signer # - add argument to the signers: --etcd_servers=localhost:2379 # 3) Run Prometheus for metrics collection and examination (best to use top-level scripts for this): # - go get github.com/prometheus/prometheus/cmd/... # - export ETCD_DIR=${GOPATH}/bin # - export PROMETHEUS_DIR=${GOPATH}/bin # - ./trillian/integration/ct_hammer_test.sh 3 3 1 # - open http://localhost:9090/targets to see what's being monitored # - open http://localhost:9090/consoles/trillian.html to see Trillian-specific metrics google-certificate-transparency-go-2308f62/trillian/integration/goshawk.cfg000066400000000000000000000027411462611535200271320ustar00rootroot00000000000000source_log: < name: "porthos" url: "http://@SERVER@/porthos" min_req_interval: < seconds: 1 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } > source_log: < name: "aramis" url: "http://@SERVER@/aramis" min_req_interval: < seconds: 1 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e" } > dest_hub: < name: "athos" url: "http://@SERVER@/athos" min_req_interval: < seconds: 10 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } > google-certificate-transparency-go-2308f62/trillian/integration/gosmin.cfg000066400000000000000000000047201462611535200267620ustar00rootroot00000000000000source_log: < name: "porthos" url: "http://@SERVER@/porthos" min_req_interval: < seconds: 10 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b" } > source_log: < name: "aramis" url: "http://@SERVER@/aramis" min_req_interval: < seconds: 10 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e" } > dest_hub: < name: "athos" url: "http://@SERVER@/athos" min_req_interval: < seconds: 25 > public_key: { der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f" } > root_cert: "-----BEGIN CERTIFICATE-----\nMIICQTCCAeegAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBpMQswCQYDVQQGEwJHQjEP\nMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds\nZTEMMAoGA1UECxMDRW5nMRkwFwYDVQQDExBUZXN0R29zc2lwZXJSb290MB4XDTE4\nMDIyNTA4MTA1M1oXDTI4MDIyMzA4MTA1M1owaTELMAkGA1UEBhMCR0IxDzANBgNV\nBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxDDAK\nBgNVBAsTA0VuZzEZMBcGA1UEAxMQVGVzdEdvc3NpcGVyUm9vdDBZMBMGByqGSM49\nAgEGCCqGSM49AwEHA0IABOqzZufPSU6hMJOIbljkjklDvQKBGYW9VenI6i7HSiyH\nccPUuh3F3fbbe2MrLtuRCjH7nqvcELPqBJsL3IVgQJijfTB7MB0GA1UdDgQWBBRq\n6hoXslGgHhrCVJMu4jrYlksyZjAfBgNVHSMEGDAWgBRq6hoXslGgHhrCVJMu4jrY\nlksyZjASBgNVHRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwICBDAVBgNVHSUE\nDjAMBgorBgEEAdZ5AgQGMAoGCCqGSM49BAMCA0gAMEUCIQCQCnWTIOlC6LqkcdH0\nfWZeNo5E3AaZBb9Tkv76ET2fJAIgOeGJvfiiOIlDV41/bIOg5eTHb/fxg80TCQBe\n6ia6ZS8=\n-----END CERTIFICATE-----" private_key: < type_url: "type.googleapis.com/keyspb.PEMKeyFile" value: "\n\035testdata/gossiper.privkey.pem\022\013hissing-sid" > google-certificate-transparency-go-2308f62/trillian/integration/hammer.go000066400000000000000000001074771462611535200266220ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "context" "crypto/sha256" "encoding/base64" "fmt" "math/rand" "net/http" "strconv" "sync" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/schedule" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" "github.com/google/trillian/monitoring" "github.com/transparency-dev/merkle" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) const ( // How many STHs and SCTs to hold on to. sthCount = 10 sctCount = 10 // How far beyond current tree size to request for invalid requests. invalidStretch = int64(1000000000) ) var ( // Metrics are all per-log (label "logid"), but may also be // per-entrypoint (label "ep") or per-return-code (label "rc"). once sync.Once reqs monitoring.Counter // logid, ep => value errs monitoring.Counter // logid, ep => value rsps monitoring.Counter // logid, ep, rc => value rspLatency monitoring.Histogram // logid, ep, rc => values invalidReqs monitoring.Counter // logid, ep => value ) // setupMetrics initializes all the exported metrics. func setupMetrics(mf monitoring.MetricFactory) { reqs = mf.NewCounter("reqs", "Number of valid requests sent", "logid", "ep") errs = mf.NewCounter("errs", "Number of error responses received for valid requests", "logid", "ep") rsps = mf.NewCounter("rsps", "Number of responses received for valid requests", "logid", "ep", "rc") rspLatency = mf.NewHistogram("rsp_latency", "Latency of valid responses in seconds", "logid", "ep", "rc") invalidReqs = mf.NewCounter("invalid_reqs", "Number of deliberately-invalid requests sent", "logid", "ep") } // errSkip indicates that a test operation should be skipped. type errSkip struct{} func (e errSkip) Error() string { return "test operation skipped" } // Choice represents a random decision about a hammer operation. type Choice string // Constants for per-operation choices. const ( ParamTooBig = Choice("ParamTooBig") Param2TooBig = Choice("Param2TooBig") ParamNegative = Choice("ParamNegative") ParamInvalid = Choice("ParamInvalid") ParamsInverted = Choice("ParamsInverted") InvalidBase64 = Choice("InvalidBase64") EmptyChain = Choice("EmptyChain") CertNotPrecert = Choice("CertNotPrecert") PrecertNotCert = Choice("PrecertNotCert") NoChainToRoot = Choice("NoChainToRoot") UnparsableCert = Choice("UnparsableCert") NewCert = Choice("NewCert") LastCert = Choice("LastCert") FirstCert = Choice("FirstCert") ) // Limiter is an interface to allow different rate limiters to be used with the // hammer. type Limiter interface { Wait(context.Context) error } type unLimited struct{} func (u unLimited) Wait(ctx context.Context) error { return nil } // HammerConfig provides configuration for a stress/load test. type HammerConfig struct { // Configuration for the log. LogCfg *configpb.LogConfig // How to create process-wide metrics. MetricFactory monitoring.MetricFactory // Maximum merge delay. MMD time.Duration // Certificate chain generator. ChainGenerator ChainGenerator // ClientPool provides the clients used to make requests. ClientPool ClientPool // Bias values to favor particular log operations. EPBias HammerBias // Range of how many entries to get. MinGetEntries, MaxGetEntries int // OversizedGetEntries governs whether get-entries requests that go beyond the // current tree size are allowed (with a truncated response expected). OversizedGetEntries bool // Number of operations to perform. Operations uint64 // Rate limiter Limiter Limiter // MaxParallelChains sets the upper limit for the number of parallel // add-*-chain requests to make when the biasing model says to perform an add. MaxParallelChains int // EmitInterval defines how frequently stats are logged. EmitInterval time.Duration // IgnoreErrors controls whether a hammer run fails immediately on any error. IgnoreErrors bool // MaxRetryDuration governs how long to keep retrying when IgnoreErrors is true. MaxRetryDuration time.Duration // RequestDeadline indicates the deadline to set on each request to the log. RequestDeadline time.Duration // DuplicateChance sets the probability of attempting to add a duplicate when // calling add[-pre]-chain (as the N in 1-in-N). Set to 0 to disable sending // duplicates. DuplicateChance int // StrictSTHConsistencySize if set to true will cause Hammer to only request // STH consistency proofs between tree sizes for which it's seen valid STHs. // If set to false, Hammer will request a consistency proof between the // current tree size, and a random smaller size greater than zero. StrictSTHConsistencySize bool } // HammerBias indicates the bias for selecting different log operations. type HammerBias struct { Bias map[ctfe.EntrypointName]int total int // InvalidChance gives the odds of performing an invalid operation, as the N in 1-in-N. InvalidChance map[ctfe.EntrypointName]int } // Choose randomly picks an operation to perform according to the biases. func (hb HammerBias) Choose() ctfe.EntrypointName { if hb.total == 0 { for _, ep := range ctfe.Entrypoints { hb.total += hb.Bias[ep] } } which := rand.Intn(hb.total) for _, ep := range ctfe.Entrypoints { which -= hb.Bias[ep] if which < 0 { return ep } } panic("random choice out of range") } // Invalid randomly chooses whether an operation should be invalid. func (hb HammerBias) Invalid(ep ctfe.EntrypointName) bool { chance := hb.InvalidChance[ep] if chance <= 0 { return false } return rand.Intn(chance) == 0 } type submittedCert struct { leafData []byte leafHash [sha256.Size]byte sct *ct.SignedCertificateTimestamp integrateBy time.Time precert bool } // pendingCerts holds certificates that have been submitted that we want // to check inclusion proofs for. The array is ordered from oldest to // most recent, but new entries are only appended when enough time has // passed since the last append, so the SCTs that get checked are spread // out across the MMD period. type pendingCerts struct { mu sync.Mutex certs [sctCount]*submittedCert } func (pc *pendingCerts) empty() bool { pc.mu.Lock() defer pc.mu.Unlock() return pc.certs[0] == nil } // tryAppendCert locks mu, checks whether it's possible to append the cert, and // appends it if so. func (pc *pendingCerts) tryAppendCert(now time.Time, mmd time.Duration, submitted *submittedCert) { pc.mu.Lock() defer pc.mu.Unlock() if pc.canAppend(now, mmd) { which := 0 for ; which < sctCount; which++ { if pc.certs[which] == nil { break } } pc.certs[which] = submitted } } // canAppend checks whether a pending cert can be appended. // It must be called with mu locked. func (pc *pendingCerts) canAppend(now time.Time, mmd time.Duration) bool { if pc.certs[sctCount-1] != nil { return false // full already } if pc.certs[0] == nil { return true // nothing yet } // Only allow append if enough time has passed, namely MMD/#savedSCTs. last := sctCount - 1 for ; last >= 0; last-- { if pc.certs[last] != nil { break } } lastTime := timeFromMS(pc.certs[last].sct.Timestamp) nextTime := lastTime.Add(mmd / sctCount) return now.After(nextTime) } // oldestIfMMDPassed returns the oldest submitted certificate if the maximum // merge delay has passed, i.e. it is expected to be integrated as of now. This // function locks mu. func (pc *pendingCerts) oldestIfMMDPassed(now time.Time) *submittedCert { pc.mu.Lock() defer pc.mu.Unlock() if pc.certs[0] == nil { return nil } submitted := pc.certs[0] if !now.After(submitted.integrateBy) { // Oldest cert not due to be integrated yet, so neither will any others. return nil } return submitted } // dropOldest removes the oldest submitted certificate. func (pc *pendingCerts) dropOldest() { pc.mu.Lock() defer pc.mu.Unlock() // Can pop the oldest cert and shuffle the others along, which make room for // another cert to be stored. for i := 0; i < (sctCount - 1); i++ { pc.certs[i] = pc.certs[i+1] } pc.certs[sctCount-1] = nil } // hammerState tracks the operations that have been performed during a test run, including // earlier SCTs/STHs for later checking. type hammerState struct { cfg *HammerConfig // Store the first submitted and the most recently submitted [pre-]chain, // to allow submission of both old and new duplicates. chainMu sync.Mutex firstChain, lastChain []ct.ASN1Cert firstChainIntegrated time.Time firstPreChain, lastPreChain []ct.ASN1Cert firstPreChainIntegrated time.Time firstTBS, lastTBS []byte mu sync.RWMutex // STHs are arranged from later to earlier (so [0] is the most recent), and the // discovery of new STHs will push older ones off the end. sth [sthCount]*ct.SignedTreeHead // Submitted certs also run from later to earlier, but the discovery of new SCTs // does not affect the existing contents of the array, so if the array is full it // keeps the same elements. Instead, the oldest entry is removed (and a space // created) when we are able to get an inclusion proof for it. pending pendingCerts // Operations that are required to fix dependencies. nextOp []ctfe.EntrypointName hasher merkle.LogHasher } func newHammerState(cfg *HammerConfig) (*hammerState, error) { mf := cfg.MetricFactory if mf == nil { mf = monitoring.InertMetricFactory{} } once.Do(func() { setupMetrics(mf) }) if cfg.MinGetEntries <= 0 { cfg.MinGetEntries = 1 } if cfg.MaxGetEntries <= cfg.MinGetEntries { cfg.MaxGetEntries = cfg.MinGetEntries + 300 } if cfg.EmitInterval <= 0 { cfg.EmitInterval = 10 * time.Second } if cfg.Limiter == nil { cfg.Limiter = unLimited{} } if cfg.MaxRetryDuration <= 0 { cfg.MaxRetryDuration = 60 * time.Second } if cfg.LogCfg.IsMirror { klog.Warningf("%v: disabling add-[pre-]chain for mirror log", cfg.LogCfg.Prefix) cfg.EPBias.Bias[ctfe.AddChainName] = 0 cfg.EPBias.Bias[ctfe.AddPreChainName] = 0 } state := hammerState{ cfg: cfg, nextOp: make([]ctfe.EntrypointName, 0), hasher: rfc6962.DefaultHasher, } return &state, nil } func (s *hammerState) client() *client.LogClient { return s.cfg.ClientPool.Next() } func (s *hammerState) lastTreeSize() uint64 { if s.sth[0] == nil { return 0 } return s.sth[0].TreeSize } func (s *hammerState) needOps(ops ...ctfe.EntrypointName) { klog.V(2).Infof("need operations %+v to satisfy dependencies", ops) s.nextOp = append(s.nextOp, ops...) } // addMultiple calls the passed in function a random number // (1 <= n < MaxParallelChains) of times. // The first of any errors returned by calls to addOne will be returned by this function. func (s *hammerState) addMultiple(ctx context.Context, addOne func(context.Context) error) error { var wg sync.WaitGroup numAdds := rand.Intn(s.cfg.MaxParallelChains) + 1 klog.V(2).Infof("%s: do %d parallel add operations...", s.cfg.LogCfg.Prefix, numAdds) errs := make(chan error, numAdds) for i := 0; i < numAdds; i++ { wg.Add(1) go func() { if err := addOne(ctx); err != nil { errs <- err } wg.Done() }() } wg.Wait() klog.V(2).Infof("%s: do %d parallel add operations...done", s.cfg.LogCfg.Prefix, numAdds) select { case err := <-errs: return err default: } return nil } func (s *hammerState) getChain() (Choice, []ct.ASN1Cert, error) { s.chainMu.Lock() defer s.chainMu.Unlock() choice := s.chooseCertToAdd() // Override choice if necessary if s.lastChain == nil { choice = NewCert } if choice == FirstCert && time.Now().Before(s.firstChainIntegrated) { choice = NewCert } switch choice { case NewCert: chain, err := s.cfg.ChainGenerator.CertChain() if err != nil { return choice, nil, fmt.Errorf("failed to make fresh cert: %v", err) } if s.firstChain == nil { s.firstChain = chain s.firstChainIntegrated = time.Now().Add(s.cfg.MMD) } s.lastChain = chain return choice, chain, nil case FirstCert: return choice, s.firstChain, nil case LastCert: return choice, s.lastChain, nil } return choice, nil, fmt.Errorf("unhandled choice %s", choice) } func (s *hammerState) addChain(ctx context.Context) error { choice, chain, err := s.getChain() if err != nil { return fmt.Errorf("failed to make chain (%s): %v", choice, err) } sct, err := s.client().AddChain(ctx, chain) if err != nil { if err, ok := err.(client.RspError); ok { klog.Errorf("%s: add-chain(%s): error %v HTTP status %d body %s", s.cfg.LogCfg.Prefix, choice, err.Error(), err.StatusCode, err.Body) } return fmt.Errorf("failed to add-chain(%s): %v", choice, err) } klog.V(2).Infof("%s: Uploaded %s cert, got SCT(time=%q)", s.cfg.LogCfg.Prefix, choice, timeFromMS(sct.Timestamp)) // Calculate leaf hash = SHA256(0x00 | tls-encode(MerkleTreeLeaf)) submitted := submittedCert{precert: false, sct: sct} leaf := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: sct.Timestamp, EntryType: ct.X509LogEntryType, X509Entry: &(chain[0]), Extensions: sct.Extensions, }, } submitted.integrateBy = timeFromMS(sct.Timestamp).Add(s.cfg.MMD) submitted.leafData, err = tls.Marshal(leaf) if err != nil { return fmt.Errorf("failed to tls.Marshal leaf cert: %v", err) } submitted.leafHash = sha256.Sum256(append([]byte{ct.TreeLeafPrefix}, submitted.leafData...)) s.pending.tryAppendCert(time.Now(), s.cfg.MMD, &submitted) klog.V(3).Infof("%s: Uploaded %s cert has leaf-hash %x", s.cfg.LogCfg.Prefix, choice, submitted.leafHash) return nil } func (s *hammerState) addChainInvalid(ctx context.Context) error { choices := []Choice{EmptyChain, PrecertNotCert, NoChainToRoot, UnparsableCert} choice := choices[rand.Intn(len(choices))] var err error var chain []ct.ASN1Cert switch choice { case EmptyChain: case PrecertNotCert: chain, _, err = s.cfg.ChainGenerator.PreCertChain() if err != nil { return fmt.Errorf("failed to make chain(%s): %v", choice, err) } case NoChainToRoot: chain, err = s.cfg.ChainGenerator.CertChain() if err != nil { return fmt.Errorf("failed to make chain(%s): %v", choice, err) } // Drop the intermediate (chain[1]). chain = append(chain[:1], chain[2:]...) case UnparsableCert: chain, err = s.cfg.ChainGenerator.CertChain() if err != nil { return fmt.Errorf("failed to make chain(%s): %v", choice, err) } // Remove the initial ASN.1 SEQUENCE type byte (0x30) to make an unparsable cert. chain[0].Data[0] = 0x00 default: klog.Exitf("Unhandled choice %s", choice) } sct, err := s.client().AddChain(ctx, chain) klog.V(3).Infof("invalid add-chain(%s) => error %v", choice, err) if err, ok := err.(client.RspError); ok { klog.V(3).Infof(" HTTP status %d body %s", err.StatusCode, err.Body) } if err == nil { return fmt.Errorf("unexpected success: add-chain(%s): %+v", choice, sct) } return nil } // chooseCertToAdd determines whether to add a new or pre-existing cert. func (s *hammerState) chooseCertToAdd() Choice { if s.cfg.DuplicateChance > 0 && rand.Intn(s.cfg.DuplicateChance) == 0 { // TODO(drysdale): restore LastCert as an option return FirstCert } return NewCert } func (s *hammerState) getPreChain() (Choice, []ct.ASN1Cert, []byte, error) { s.chainMu.Lock() defer s.chainMu.Unlock() choice := s.chooseCertToAdd() // Override choice if necessary if s.lastPreChain == nil { choice = NewCert } if choice == FirstCert && time.Now().Before(s.firstPreChainIntegrated) { choice = NewCert } switch choice { case NewCert: prechain, tbs, err := s.cfg.ChainGenerator.PreCertChain() if err != nil { return choice, nil, nil, fmt.Errorf("failed to make fresh pre-cert: %v", err) } if s.firstPreChain == nil { s.firstPreChain = prechain s.firstPreChainIntegrated = time.Now().Add(s.cfg.MMD) s.firstTBS = tbs } s.lastPreChain = prechain s.lastTBS = tbs return choice, prechain, tbs, nil case FirstCert: return choice, s.firstPreChain, s.firstTBS, nil case LastCert: return choice, s.lastPreChain, s.lastTBS, nil } return choice, nil, nil, fmt.Errorf("unhandled choice %s", choice) } func (s *hammerState) addPreChain(ctx context.Context) error { choice, prechain, tbs, err := s.getPreChain() if err != nil { return fmt.Errorf("failed to make pre-cert chain (%s): %v", choice, err) } issuer, err := x509.ParseCertificate(prechain[1].Data) if err != nil { return fmt.Errorf("failed to parse pre-cert issuer: %v", err) } sct, err := s.client().AddPreChain(ctx, prechain) if err != nil { if err, ok := err.(client.RspError); ok { klog.Errorf("%s: add-pre-chain(%s): error %v HTTP status %d body %s", s.cfg.LogCfg.Prefix, choice, err.Error(), err.StatusCode, err.Body) } return fmt.Errorf("failed to add-pre-chain: %v", err) } klog.V(2).Infof("%s: Uploaded %s pre-cert, got SCT(time=%q)", s.cfg.LogCfg.Prefix, choice, timeFromMS(sct.Timestamp)) // Calculate leaf hash = SHA256(0x00 | tls-encode(MerkleTreeLeaf)) submitted := submittedCert{precert: true, sct: sct} leaf := ct.MerkleTreeLeaf{ Version: ct.V1, LeafType: ct.TimestampedEntryLeafType, TimestampedEntry: &ct.TimestampedEntry{ Timestamp: sct.Timestamp, EntryType: ct.PrecertLogEntryType, PrecertEntry: &ct.PreCert{ IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo), TBSCertificate: tbs, }, Extensions: sct.Extensions, }, } submitted.integrateBy = timeFromMS(sct.Timestamp).Add(s.cfg.MMD) submitted.leafData, err = tls.Marshal(leaf) if err != nil { return fmt.Errorf("tls.Marshal(precertLeaf)=(nil,%v); want (_,nil)", err) } submitted.leafHash = sha256.Sum256(append([]byte{ct.TreeLeafPrefix}, submitted.leafData...)) s.pending.tryAppendCert(time.Now(), s.cfg.MMD, &submitted) klog.V(3).Infof("%s: Uploaded %s pre-cert has leaf-hash %x", s.cfg.LogCfg.Prefix, choice, submitted.leafHash) return nil } func (s *hammerState) addPreChainInvalid(ctx context.Context) error { choices := []Choice{EmptyChain, CertNotPrecert, NoChainToRoot, UnparsableCert} choice := choices[rand.Intn(len(choices))] var err error var prechain []ct.ASN1Cert switch choice { case EmptyChain: case CertNotPrecert: prechain, err = s.cfg.ChainGenerator.CertChain() if err != nil { return fmt.Errorf("failed to make pre-chain(%s): %v", choice, err) } case NoChainToRoot: prechain, _, err = s.cfg.ChainGenerator.PreCertChain() if err != nil { return fmt.Errorf("failed to make pre-chain(%s): %v", choice, err) } // Drop the intermediate (prechain[1]). prechain = append(prechain[:1], prechain[2:]...) case UnparsableCert: prechain, _, err = s.cfg.ChainGenerator.PreCertChain() if err != nil { return fmt.Errorf("failed to make pre-chain(%s): %v", choice, err) } // Remove the initial ASN.1 SEQUENCE type byte (0x30) to make an unparsable cert. prechain[0].Data[0] = 0x00 default: klog.Exitf("Unhandled choice %s", choice) } sct, err := s.client().AddPreChain(ctx, prechain) klog.V(3).Infof("invalid add-pre-chain(%s) => error %v", choice, err) if err, ok := err.(client.RspError); ok { klog.V(3).Infof(" HTTP status %d body %s", err.StatusCode, err.Body) } if err == nil { return fmt.Errorf("unexpected success: add-pre-chain: %+v", sct) } return nil } func (s *hammerState) getSTH(ctx context.Context) error { // Shuffle earlier STHs along. for i := sthCount - 1; i > 0; i-- { s.sth[i] = s.sth[i-1] } var err error s.sth[0], err = s.client().GetSTH(ctx) if err != nil { return fmt.Errorf("failed to get-sth: %v", err) } klog.V(2).Infof("%s: Got STH(time=%q, size=%d)", s.cfg.LogCfg.Prefix, timeFromMS(s.sth[0].Timestamp), s.sth[0].TreeSize) return nil } // chooseSTHs gets the current STH, and also picks an earlier STH. func (s *hammerState) chooseSTHs(ctx context.Context) (*ct.SignedTreeHead, *ct.SignedTreeHead, error) { // Get current size, and pick an earlier size sthNow, err := s.client().GetSTH(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to get-sth for current tree: %v", err) } which := rand.Intn(sthCount) if s.sth[which] == nil { klog.V(3).Infof("%s: skipping get-sth-consistency as no earlier STH", s.cfg.LogCfg.Prefix) s.needOps(ctfe.GetSTHName) return nil, sthNow, errSkip{} } if s.sth[which].TreeSize == 0 { klog.V(3).Infof("%s: skipping get-sth-consistency as no earlier STH", s.cfg.LogCfg.Prefix) s.needOps(ctfe.AddChainName, ctfe.GetSTHName) return nil, sthNow, errSkip{} } if s.sth[which].TreeSize == sthNow.TreeSize { klog.V(3).Infof("%s: skipping get-sth-consistency as same size (%d)", s.cfg.LogCfg.Prefix, sthNow.TreeSize) s.needOps(ctfe.AddChainName, ctfe.GetSTHName) return nil, sthNow, errSkip{} } return s.sth[which], sthNow, nil } func (s *hammerState) getSTHConsistency(ctx context.Context) error { sthOld, sthNow, err := s.chooseSTHs(ctx) if err != nil { // bail on actual errors if _, ok := err.(errSkip); !ok { return err } // If we're being asked to skip, it's because we don't have an earlier STH, // if the config says we must only use "known" STHs then we'll have to wait // until we get a larger STH. if s.cfg.StrictSTHConsistencySize { return err } // Otherwise, let's use our imagination and make one up, if possible... if sthNow.TreeSize < 2 { klog.V(3).Infof("%s: current STH size too small to invent a smaller STH for consistency proof (%d)", s.cfg.LogCfg.Prefix, sthNow.TreeSize) return errSkip{} } sthOld = &ct.SignedTreeHead{TreeSize: uint64(1 + rand.Int63n(int64(sthNow.TreeSize)))} klog.V(3).Infof("%s: Inventing a smaller STH size for consistency proof (%d)", s.cfg.LogCfg.Prefix, sthOld.TreeSize) } proof, err := s.client().GetSTHConsistency(ctx, sthOld.TreeSize, sthNow.TreeSize) if err != nil { return fmt.Errorf("failed to get-sth-consistency(%d, %d): %v", sthOld.TreeSize, sthNow.TreeSize, err) } if sthOld.Timestamp == 0 { klog.V(3).Infof("%s: Skipping consistency proof verification for invented STH", s.cfg.LogCfg.Prefix) return nil } if err := s.checkCTConsistencyProof(sthOld, sthNow, proof); err != nil { return fmt.Errorf("get-sth-consistency(%d, %d) proof check failed: %v", sthOld.TreeSize, sthNow.TreeSize, err) } klog.V(2).Infof("%s: Got STH consistency proof (size=%d => %d) len %d", s.cfg.LogCfg.Prefix, sthOld.TreeSize, sthNow.TreeSize, len(proof)) return nil } func (s *hammerState) getSTHConsistencyInvalid(ctx context.Context) error { lastSize := s.lastTreeSize() if lastSize == 0 { return errSkip{} } choices := []Choice{ParamTooBig, ParamsInverted, ParamNegative, ParamInvalid} choice := choices[rand.Intn(len(choices))] var err error var proof [][]byte switch choice { case ParamTooBig: first := lastSize + uint64(invalidStretch) second := first + 100 proof, err = s.client().GetSTHConsistency(ctx, first, second) case Param2TooBig: first := lastSize second := lastSize + uint64(invalidStretch) proof, err = s.client().GetSTHConsistency(ctx, first, second) case ParamsInverted: var sthOld, sthNow *ct.SignedTreeHead sthOld, sthNow, err = s.chooseSTHs(ctx) if err != nil { return err } proof, err = s.client().GetSTHConsistency(ctx, sthNow.TreeSize, sthOld.TreeSize) case ParamNegative, ParamInvalid: params := make(map[string]string) switch choice { case ParamNegative: params["first"] = "-3" params["second"] = "-1" case ParamInvalid: params["first"] = "foo" params["second"] = "bar" } // Need to use lower-level API to be able to use invalid parameters var resp ct.GetSTHConsistencyResponse var httpRsp *http.Response var body []byte httpRsp, body, err = s.client().GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp) if err != nil && httpRsp != nil { err = client.RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } proof = resp.Consistency default: klog.Exitf("Unhandled choice %s", choice) } klog.V(3).Infof("invalid get-sth-consistency(%s) => error %v", choice, err) if err, ok := err.(client.RspError); ok { klog.V(3).Infof(" HTTP status %d body %s", err.StatusCode, err.Body) } if err == nil { return fmt.Errorf("unexpected success: get-sth-consistency(%s): %+v", choice, proof) } return nil } func (s *hammerState) getProofByHash(ctx context.Context) error { submitted := s.pending.oldestIfMMDPassed(time.Now()) if submitted == nil { // No SCT that is guaranteed to be integrated, so move on. return errSkip{} } // Get an STH that should include this submitted [pre-]cert. sth, err := s.client().GetSTH(ctx) if err != nil { return fmt.Errorf("failed to get-sth for proof: %v", err) } // Get and check an inclusion proof. rsp, err := s.client().GetProofByHash(ctx, submitted.leafHash[:], sth.TreeSize) if err != nil { return fmt.Errorf("failed to get-proof-by-hash(size=%d) on cert with SCT @ %v: %v, %+v", sth.TreeSize, timeFromMS(submitted.sct.Timestamp), err, rsp) } if err := proof.VerifyInclusion(s.hasher, uint64(rsp.LeafIndex), sth.TreeSize, submitted.leafHash[:], rsp.AuditPath, sth.SHA256RootHash[:]); err != nil { return fmt.Errorf("failed to VerifyInclusion(%d, %d)=%v", rsp.LeafIndex, sth.TreeSize, err) } s.pending.dropOldest() return nil } func (s *hammerState) getProofByHashInvalid(ctx context.Context) error { lastSize := s.lastTreeSize() if lastSize == 0 { return errSkip{} } submitted := s.pending.oldestIfMMDPassed(time.Now()) choices := []Choice{ParamInvalid, ParamTooBig, ParamNegative, InvalidBase64} choice := choices[rand.Intn(len(choices))] var err error var rsp *ct.GetProofByHashResponse switch choice { case ParamInvalid: rsp, err = s.client().GetProofByHash(ctx, []byte{0x01, 0x02}, 1) // Hash too short case ParamTooBig: if submitted == nil { return errSkip{} } rsp, err = s.client().GetProofByHash(ctx, submitted.leafHash[:], lastSize+uint64(invalidStretch)) case ParamNegative, InvalidBase64: params := make(map[string]string) switch choice { case ParamNegative: if submitted == nil { return errSkip{} } params["tree_size"] = "-1" params["hash"] = base64.StdEncoding.EncodeToString(submitted.leafHash[:]) case InvalidBase64: params["tree_size"] = "1" params["hash"] = "@^()" } var r ct.GetProofByHashResponse rsp = &r var httpRsp *http.Response var body []byte httpRsp, body, err = s.client().GetAndParse(ctx, ct.GetProofByHashPath, params, &r) if err != nil && httpRsp != nil { err = client.RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body} } default: klog.Exitf("Unhandled choice %s", choice) } klog.V(3).Infof("invalid get-proof-by-hash(%s) => error %v", choice, err) if err, ok := err.(client.RspError); ok { klog.V(3).Infof(" HTTP status %d body %s", err.StatusCode, err.Body) } if err == nil { return fmt.Errorf("unexpected success: get-proof-by-hash(%s): %+v", choice, rsp) } return nil } func (s *hammerState) getEntries(ctx context.Context) error { if s.sth[0] == nil { klog.V(3).Infof("%s: skipping get-entries as no earlier STH", s.cfg.LogCfg.Prefix) s.needOps(ctfe.GetSTHName) return errSkip{} } lastSize := s.lastTreeSize() if lastSize == 0 { if s.pending.empty() { klog.V(3).Infof("%s: skipping get-entries as tree size 0", s.cfg.LogCfg.Prefix) s.needOps(ctfe.AddChainName, ctfe.GetSTHName) return errSkip{} } klog.V(3).Infof("%s: skipping get-entries as STH stale", s.cfg.LogCfg.Prefix) s.needOps(ctfe.GetSTHName) return errSkip{} } // Entry indices are zero-based, and may or may not be allowed to extend // beyond current tree size (RFC 6962 s4.6). first := rand.Intn(int(lastSize)) span := s.cfg.MaxGetEntries - s.cfg.MinGetEntries count := s.cfg.MinGetEntries + rand.Intn(int(span)) last := first + count if !s.cfg.OversizedGetEntries && last >= int(lastSize) { last = int(lastSize) - 1 } entries, err := s.client().GetEntries(ctx, int64(first), int64(last)) if err != nil { return fmt.Errorf("failed to get-entries(%d,%d): %v", first, last, err) } for i, entry := range entries { if want := int64(first + i); entry.Index != want { return fmt.Errorf("leaf[%d].LeafIndex=%d; want %d", i, entry.Index, want) } leaf := entry.Leaf if leaf.Version != 0 { return fmt.Errorf("leaf[%d].Version=%v; want V1(0)", i, leaf.Version) } if leaf.LeafType != ct.TimestampedEntryLeafType { return fmt.Errorf("leaf[%d].Version=%v; want TimestampedEntryLeafType", i, leaf.LeafType) } ts := leaf.TimestampedEntry if ts.EntryType != ct.X509LogEntryType && ts.EntryType != ct.PrecertLogEntryType { return fmt.Errorf("leaf[%d].ts.EntryType=%v; want {X509,Precert}LogEntryType", i, ts.EntryType) } } klog.V(2).Infof("%s: Got entries [%d:%d)\n", s.cfg.LogCfg.Prefix, first, first+len(entries)) return nil } func (s *hammerState) getEntriesInvalid(ctx context.Context) error { lastSize := s.lastTreeSize() if lastSize == 0 { return errSkip{} } choices := []Choice{ParamTooBig, ParamNegative, ParamsInverted} choice := choices[rand.Intn(len(choices))] var first, last int64 switch choice { case ParamTooBig: last = int64(lastSize) + invalidStretch first = last - 4 case ParamNegative: first = -2 last = 10 case ParamsInverted: first = 10 last = 5 default: klog.Exitf("Unhandled choice %s", choice) } entries, err := s.client().GetEntries(ctx, first, last) klog.V(3).Infof("invalid get-entries(%s) => error %v", choice, err) if err, ok := err.(client.RspError); ok { klog.V(3).Infof(" HTTP status %d body %s", err.StatusCode, err.Body) } if err == nil { return fmt.Errorf("unexpected success: get-entries(%d,%d): %d entries", first, last, len(entries)) } return nil } func (s *hammerState) getRoots(ctx context.Context) error { roots, err := s.client().GetAcceptedRoots(ctx) if err != nil { return fmt.Errorf("failed to get-roots: %v", err) } klog.V(2).Infof("%s: Got roots (len=%d)", s.cfg.LogCfg.Prefix, len(roots)) return nil } func sthSize(sth *ct.SignedTreeHead) string { if sth == nil { return "n/a" } return fmt.Sprintf("%d", sth.TreeSize) } func (s *hammerState) label() string { return strconv.FormatInt(s.cfg.LogCfg.LogId, 10) } func (s *hammerState) String() string { s.mu.RLock() defer s.mu.RUnlock() details := "" statusOK := strconv.Itoa(http.StatusOK) totalReqs := 0 totalInvalidReqs := 0 totalErrs := 0 for _, ep := range ctfe.Entrypoints { reqCount := int(reqs.Value(s.label(), string(ep))) totalReqs += reqCount if s.cfg.EPBias.Bias[ep] > 0 { details += fmt.Sprintf(" %s=%d/%d", ep, int(rsps.Value(s.label(), string(ep), statusOK)), reqCount) } totalInvalidReqs += int(invalidReqs.Value(s.label(), string(ep))) totalErrs += int(errs.Value(s.label(), string(ep))) } return fmt.Sprintf("%10s: lastSTH.size=%s ops: total=%d invalid=%d errs=%v%s", s.cfg.LogCfg.Prefix, sthSize(s.sth[0]), totalReqs, totalInvalidReqs, totalErrs, details) } func (s *hammerState) performOp(ctx context.Context, ep ctfe.EntrypointName) (int, error) { if err := s.cfg.Limiter.Wait(ctx); err != nil { return http.StatusRequestTimeout, fmt.Errorf("Limiter.Wait(): %v", err) } s.mu.Lock() defer s.mu.Unlock() if s.cfg.RequestDeadline > 0 { cctx, cancel := context.WithTimeout(ctx, s.cfg.RequestDeadline) defer cancel() ctx = cctx } status := http.StatusOK var err error switch ep { case ctfe.AddChainName: err = s.addMultiple(ctx, s.addChain) case ctfe.AddPreChainName: err = s.addMultiple(ctx, s.addPreChain) case ctfe.GetSTHName: err = s.getSTH(ctx) case ctfe.GetSTHConsistencyName: err = s.getSTHConsistency(ctx) case ctfe.GetProofByHashName: err = s.getProofByHash(ctx) case ctfe.GetEntriesName: err = s.getEntries(ctx) case ctfe.GetRootsName: err = s.getRoots(ctx) case ctfe.GetEntryAndProofName: status = http.StatusNotImplemented klog.V(2).Infof("%s: hammering entrypoint %s not yet implemented", s.cfg.LogCfg.Prefix, ep) default: err = fmt.Errorf("internal error: unknown entrypoint %s selected", ep) } return status, err } func (s *hammerState) performInvalidOp(ctx context.Context, ep ctfe.EntrypointName) error { if err := s.cfg.Limiter.Wait(ctx); err != nil { return fmt.Errorf("Limiter.Wait(): %v", err) } switch ep { case ctfe.AddChainName: return s.addChainInvalid(ctx) case ctfe.AddPreChainName: return s.addPreChainInvalid(ctx) case ctfe.GetSTHConsistencyName: return s.getSTHConsistencyInvalid(ctx) case ctfe.GetProofByHashName: return s.getProofByHashInvalid(ctx) case ctfe.GetEntriesName: return s.getEntriesInvalid(ctx) case ctfe.GetSTHName, ctfe.GetRootsName: return fmt.Errorf("no invalid request possible for entrypoint %s", ep) case ctfe.GetEntryAndProofName: return fmt.Errorf("hammering entrypoint %s not yet implemented", ep) } return fmt.Errorf("internal error: unknown entrypoint %s", ep) } func (s *hammerState) chooseOp() (ctfe.EntrypointName, bool) { s.mu.Lock() defer s.mu.Unlock() if len(s.nextOp) > 0 { ep := s.nextOp[0] s.nextOp = s.nextOp[1:] if s.cfg.EPBias.Bias[ep] > 0 { return ep, false } } ep := s.cfg.EPBias.Choose() return ep, s.cfg.EPBias.Invalid(ep) } // Perform a random operation on the log, retrying if necessary. If non-empty, the // returned entrypoint should be performed next to unblock dependencies. func (s *hammerState) retryOneOp(ctx context.Context) error { ep, invalid := s.chooseOp() if invalid { klog.V(3).Infof("perform invalid %s operation", ep) invalidReqs.Inc(s.label(), string(ep)) err := s.performInvalidOp(ctx, ep) if _, ok := err.(errSkip); ok { klog.V(2).Infof("invalid operation %s was skipped", ep) return nil } return err } klog.V(3).Infof("perform %s operation", ep) deadline := time.Now().Add(s.cfg.MaxRetryDuration) for { if err := ctx.Err(); err != nil { return err } start := time.Now() reqs.Inc(s.label(), string(ep)) status, err := s.performOp(ctx, ep) period := time.Since(start) rspLatency.Observe(period.Seconds(), s.label(), string(ep), strconv.Itoa(status)) switch err.(type) { case nil: rsps.Inc(s.label(), string(ep), strconv.Itoa(status)) return nil case errSkip: klog.V(2).Infof("operation %s was skipped", ep) return nil default: errs.Inc(s.label(), string(ep)) if s.cfg.IgnoreErrors { left := time.Until(deadline) if left < 0 { klog.Warningf("%s: gave up retrying failed op %v after %v, returning last err: %v", s.cfg.LogCfg.Prefix, ep, s.cfg.MaxRetryDuration, err) return err } klog.Warningf("%s: op %v failed after %v (will retry for %v more): %v", s.cfg.LogCfg.Prefix, ep, period, left, err) } else { return err } } } } // checkCTConsistencyProof checks the given consistency proof. func (s *hammerState) checkCTConsistencyProof(sth1, sth2 *ct.SignedTreeHead, pf [][]byte) error { return proof.VerifyConsistency(s.hasher, sth1.TreeSize, sth2.TreeSize, pf, sth1.SHA256RootHash[:], sth2.SHA256RootHash[:]) } // HammerCTLog performs load/stress operations according to given config. func HammerCTLog(ctx context.Context, cfg HammerConfig) error { s, err := newHammerState(&cfg) if err != nil { return err } ctx, cancel := context.WithCancel(ctx) defer cancel() go schedule.Every(ctx, cfg.EmitInterval, func(ctx context.Context) { klog.Info(s.String()) }) for count := uint64(1); count < cfg.Operations; count++ { if err := s.retryOneOp(ctx); err != nil { return err } // Terminate from the loop if the context is cancelled. if err := ctx.Err(); err != nil { return err } } klog.Infof("%s: completed %d operations on log", cfg.LogCfg.Prefix, cfg.Operations) return nil } google-certificate-transparency-go-2308f62/trillian/integration/hammer_test.go000066400000000000000000000276361462611535200276570ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "context" "crypto" "encoding/json" "fmt" "io" "net" "net/http" "testing" "time" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" "google.golang.org/protobuf/types/known/timestamppb" "k8s.io/klog/v2" ct "github.com/google/certificate-transparency-go" ) func TestHammer_NotAfter(t *testing.T) { keys := loadTestKeys(t) s, lc := newFakeCTServer(t) defer s.close() now := time.Now() notAfterStart := now.Add(-48 * time.Hour) notAfterOverride := now.Add(23 * time.Hour) notAfterLimit := now.Add(48 * time.Hour) ctx := context.Background() addChain := func(hs *hammerState) error { return hs.addChain(ctx) } addPreChain := func(hs *hammerState) error { return hs.addPreChain(ctx) } tests := []struct { desc string fn func(hs *hammerState) error notAfterOverride, notAfterStart, notAfterLimit time.Time // wantNotAfter is only checked if not zeroed wantNotAfter time.Time }{ { desc: "nonTemporalAddChain", fn: addChain, }, { desc: "nonTemporalAddPreChain", fn: addPreChain, }, { desc: "nonTemporalFixedAddChain", fn: addChain, notAfterOverride: notAfterOverride, wantNotAfter: notAfterOverride, }, { desc: "nonTemporalFixedAddPreChain", fn: addPreChain, notAfterOverride: notAfterOverride, wantNotAfter: notAfterOverride, }, { desc: "temporalAddChain", fn: addChain, notAfterStart: notAfterStart, notAfterLimit: notAfterLimit, }, { desc: "temporalAddPreChain", fn: addPreChain, notAfterStart: notAfterStart, notAfterLimit: notAfterLimit, }, { desc: "temporalFixedAddChain", fn: addChain, notAfterOverride: notAfterOverride, notAfterStart: notAfterStart, notAfterLimit: notAfterLimit, wantNotAfter: notAfterOverride, }, { desc: "temporalFixedAddPreChain", fn: addPreChain, notAfterOverride: notAfterOverride, notAfterStart: notAfterStart, notAfterLimit: notAfterLimit, wantNotAfter: notAfterOverride, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { s.reset() var startPB, limitPB *timestamppb.Timestamp if ts := test.notAfterStart; ts.UnixNano() > 0 { startPB = timestamppb.New(ts) } if ts := test.notAfterLimit; ts.UnixNano() > 0 { limitPB = timestamppb.New(ts) } generator, err := NewSyntheticChainGenerator(keys.leafChain, keys.signer, test.notAfterOverride) if err != nil { t.Fatalf("Failed to build chain generator: %v", err) } hs, err := newHammerState(&HammerConfig{ ChainGenerator: generator, ClientPool: RandomPool{lc}, LogCfg: &configpb.LogConfig{ NotAfterStart: startPB, NotAfterLimit: limitPB, }, }) if err != nil { t.Fatalf("newHammerState() returned err = %v", err) } if err := test.fn(hs); err != nil { t.Fatalf("addChain() returned err = %v", err) } if got := len(s.addedCerts); got != 1 { t.Fatalf("unexpected number of certs (%d) added to server", got) } got := s.addedCerts[0].NotAfter temporal := startPB != nil || limitPB != nil fixed := test.wantNotAfter.UnixNano() > 0 if fixed { // Expect a fixed NotAfter in the generated cert. delta := got.Sub(test.wantNotAfter) if delta < 0 { delta = -delta } if delta > time.Second { t.Errorf("cert has NotAfter = %v, want = %v", got, test.wantNotAfter) } } else { // For a temporal log, expect the NotAfter in the generated cert to be in range. if temporal && (got.Before(test.notAfterStart) || got.After(test.notAfterLimit)) { t.Errorf("cert has NotAfter = %v, want %v <= NotAfter <= %v", got, test.notAfterStart, test.notAfterLimit) } } }) } } // fakeCTServer is a fake HTTP server that mimics a CT frontend. // It supports add-chain and add-pre-chain methods and saves the first certificate of the chain in // the addCerts field. // Callers should call reset() before usage to reset internal state and defer-call close() to ensure // the server is stopped and resources are freed. type fakeCTServer struct { lis net.Listener server *http.Server addedCerts []*x509.Certificate sthNow ct.SignedTreeHead getConsistencyCalled bool } func (s *fakeCTServer) addChain(w http.ResponseWriter, req *http.Request) { body, err := io.ReadAll(req.Body) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } addReq := &ct.AddChainRequest{} if err := json.Unmarshal(body, addReq); err != nil { writeErr(w, http.StatusBadRequest, err) return } cert, err := x509.ParseCertificate(addReq.Chain[0]) if err != nil { writeErr(w, http.StatusBadRequest, err) return } s.addedCerts = append(s.addedCerts, cert) dsBytes, err := tls.Marshal(tls.DigitallySigned{}) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } resp := &ct.AddChainResponse{ SCTVersion: ct.V1, Signature: dsBytes, } respBytes, err := json.Marshal(resp) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } w.WriteHeader(http.StatusOK) if _, err := w.Write(respBytes); err != nil { klog.Errorf("Write(): %v", err) } } func (s *fakeCTServer) close() { if s.server != nil { if err := s.server.Close(); err != nil { klog.Errorf("Operation to close server failed: %v", err) } } if s.lis != nil { if err := s.lis.Close(); err != nil { klog.Errorf("Operation to close server listener failed: %v", err) } } } func (s *fakeCTServer) reset() { s.addedCerts = nil } func (s *fakeCTServer) serve() { if err := s.server.Serve(s.lis); err != http.ErrServerClosed { panic(err) } } func (s *fakeCTServer) getSTH(w http.ResponseWriter, req *http.Request) { resp := &ct.GetSTHResponse{ TreeSize: s.sthNow.TreeSize, Timestamp: s.sthNow.Timestamp, SHA256RootHash: []byte(s.sthNow.SHA256RootHash[:]), } var err error resp.TreeHeadSignature, err = tls.Marshal(s.sthNow.TreeHeadSignature) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } respBytes, err := json.Marshal(resp) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } w.WriteHeader(http.StatusOK) if _, err := w.Write(respBytes); err != nil { klog.Errorf("Write(): %v", err) } } func (s *fakeCTServer) getConsistency(w http.ResponseWriter, req *http.Request) { cp := &ct.GetSTHConsistencyResponse{ Consistency: [][]byte{[]byte("bogus")}, } respBytes, err := json.Marshal(cp) if err != nil { writeErr(w, http.StatusInternalServerError, err) return } w.WriteHeader(http.StatusOK) if _, err := w.Write(respBytes); err != nil { klog.Errorf("Write(): %v", err) } s.getConsistencyCalled = true } func writeErr(w http.ResponseWriter, status int, err error) { w.WriteHeader(status) if _, err := io.WriteString(w, err.Error()); err != nil { klog.Errorf("WriteString(): %v", err) } } // newFakeCTServer creates and starts a fakeCTServer. // It returns the started server and a client to the same server. func newFakeCTServer(t *testing.T) (*fakeCTServer, *client.LogClient) { s := &fakeCTServer{} var err error s.lis, err = net.Listen("tcp", "") if err != nil { s.close() t.Fatalf("net.Listen() returned err = %v", err) } mux := http.NewServeMux() mux.HandleFunc("/ct/v1/add-chain", s.addChain) mux.HandleFunc("/ct/v1/add-pre-chain", s.addChain) mux.HandleFunc("/ct/v1/get-sth", s.getSTH) mux.HandleFunc("/ct/v1/get-sth-consistency", s.getConsistency) s.server = &http.Server{Handler: mux} go s.serve() lc, err := client.New(fmt.Sprintf("http://%s", s.lis.Addr()), nil, jsonclient.Options{}) if err != nil { t.Fatalf("client.New() returned err = %v", err) } return s, lc } // testKeys contains all keys and associated signer required for hammer tests. type testKeys struct { caChain, leafChain []ct.ASN1Cert caCert, leafCert *x509.Certificate signer crypto.Signer } // loadTestKeys loads the test keys from the testdata/ directory. func loadTestKeys(t *testing.T) *testKeys { t.Helper() const testdataPath = "../testdata/" caChain, err := GetChain(testdataPath, "int-ca.cert") if err != nil { t.Fatalf("GetChain() returned err = %v", err) } leafChain, err := GetChain(testdataPath, "leaf01.chain") if err != nil { t.Fatalf("GetChain() returned err = %v", err) } caCert, err := x509.ParseCertificate(caChain[0].Data) if err != nil { t.Fatalf("x509.ParseCertificate() returned err = %v", err) } leafCert, err := x509.ParseCertificate(leafChain[0].Data) if err != nil { t.Fatalf("x509.ParseCertificate() returned err = %v", err) } signer, err := MakeSigner(testdataPath) if err != nil { t.Fatalf("MakeSigner() returned err = %v", err) } return &testKeys{ caChain: caChain, leafChain: leafChain, caCert: caCert, leafCert: leafCert, signer: signer, } } func TestChooseCertToAdd(t *testing.T) { for _, test := range []struct { desc string dupeInN int wantNew bool wantOld bool }{ { desc: "all new", dupeInN: 0, wantNew: true, }, { desc: "all old", dupeInN: 1, wantOld: true, }, { desc: "mix", dupeInN: 2, wantNew: true, wantOld: true, }, } { t.Run(test.desc, func(t *testing.T) { state := hammerState{cfg: &HammerConfig{DuplicateChance: test.dupeInN}} var gotNew, gotOld bool for i := 0; i < 100; i++ { switch state.chooseCertToAdd() { case NewCert: gotNew = true case FirstCert, LastCert: gotOld = true } } if gotNew && !test.wantNew { t.Errorf("got NewCert but expected none") } if !gotNew && test.wantNew { t.Errorf("got no NewCerts but expected some") } if gotOld && !test.wantOld { t.Errorf("got First/Last cert but expected none") } if !gotOld && test.wantOld { t.Errorf("got no First/Last cert but expected some") } }) } } func TestStrictSTHConsistencySize(t *testing.T) { ctx := context.Background() for _, test := range []struct { name string strict bool sthNowSize uint64 wantSkip bool }{ {name: "strict", strict: true, wantSkip: true}, {name: "relaxed_too_small", sthNowSize: 1, wantSkip: true}, {name: "relaxed_invent_size", sthNowSize: 10, wantSkip: false}, } { t.Run(test.name, func(t *testing.T) { s, lc := newFakeCTServer(t) defer s.close() s.sthNow.TreeSize = test.sthNowSize hs, err := newHammerState(&HammerConfig{ StrictSTHConsistencySize: test.strict, ClientPool: RandomPool{lc}, LogCfg: &configpb.LogConfig{}, }) if err != nil { t.Fatalf("Failed to create HammerState: %v", err) } err = hs.getSTHConsistency(ctx) _, gotSkip := err.(errSkip) if gotSkip != test.wantSkip { t.Fatalf("got err %v, wanted Skip=%v", err, test.wantSkip) } if err != nil && !gotSkip { t.Fatalf("got unexpected err %v", err) } if test.wantSkip { return } if !s.getConsistencyCalled { t.Fatal("hammer failed to request a consistency proof for invented tree size") } }) } } google-certificate-transparency-go-2308f62/trillian/integration/integration_test.sh000077500000000000000000000010501462611535200307170ustar00rootroot00000000000000#!/bin/bash set -e # Import Trillian integration functions for kill_pid, pick_unused_port, etc., # but we no longer use the log prep/tear down stuff. . "$(go list -f '{{ .Dir }}' github.com/google/trillian)"/integration/functions.sh run_test "CT integration test" "$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go)/trillian/integration/ct_integration_test.sh" 1 run_test "CT multi-server integration test" "$(go list -f '{{ .Dir }}' github.com/google/certificate-transparency-go)/trillian/integration/ct_integration_test.sh" 3 google-certificate-transparency-go-2308f62/trillian/integration/logenv.go000066400000000000000000000101041462611535200266170ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "context" "fmt" "log" "net" "net/http" "sync" "time" "github.com/google/certificate-transparency-go/trillian/ctfe" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/trillian" "github.com/google/trillian/client" "github.com/google/trillian/monitoring/prometheus" "github.com/google/trillian/testonly/integration" "github.com/prometheus/client_golang/prometheus/promhttp" "k8s.io/klog/v2" stestonly "github.com/google/trillian/storage/testonly" ) // CTLogEnv is a test environment that contains both a log server and a CT personality // connected to it. type CTLogEnv struct { logEnv *integration.LogEnv wg *sync.WaitGroup ctListener net.Listener ctHTTPServer *http.Server CTAddr string } // NewCTLogEnv creates a fresh DB, log server, and CT personality. // testID should be unique to each unittest package so as to allow parallel tests. // Created logIDs will be set to cfgs. func NewCTLogEnv(ctx context.Context, cfgs []*configpb.LogConfig, numSequencers int, testID string) (*CTLogEnv, error) { // Start log server and signer. logEnv, err := integration.NewLogEnvWithGRPCOptions(ctx, numSequencers, nil, nil) if err != nil { return nil, fmt.Errorf("failed to create LogEnv: %v", err) } // Provision the logs. for _, cfg := range cfgs { tree, err := client.CreateAndInitTree(ctx, &trillian.CreateTreeRequest{Tree: stestonly.LogTree}, logEnv.Admin, logEnv.Log) if err != nil { return nil, fmt.Errorf("failed to provision log %d: %v", cfg.LogId, err) } cfg.LogId = tree.TreeId } // Start the CT personality. addr, listener, err := listen() if err != nil { return nil, fmt.Errorf("failed to find an unused port for CT personality: %v", err) } server := http.Server{Addr: addr, Handler: nil} var wg sync.WaitGroup wg.Add(1) go func(env *integration.LogEnv, server *http.Server, listener net.Listener, cfgs []*configpb.LogConfig) { defer wg.Done() cl := trillian.NewTrillianLogClient(env.ClientConn) for _, cfg := range cfgs { vCfg, err := ctfe.ValidateLogConfig(cfg) if err != nil { klog.Fatalf("ValidateLogConfig failed: %+v: %v", cfg, err) } opts := ctfe.InstanceOptions{ Validated: vCfg, Client: cl, Deadline: 10 * time.Second, MetricFactory: prometheus.MetricFactory{}, RequestLog: new(ctfe.DefaultRequestLog), } inst, err := ctfe.SetUpInstance(ctx, opts) if err != nil { klog.Fatalf("Failed to set up log instance for %+v: %v", cfg, err) } for path, handler := range inst.Handlers { http.Handle(path, handler) } } http.Handle("/metrics", promhttp.Handler()) if err := server.Serve(listener); err != http.ErrServerClosed { klog.Fatalf("server.Serve(): %v", err) } }(logEnv, &server, listener, cfgs) return &CTLogEnv{ logEnv: logEnv, wg: &wg, ctListener: listener, ctHTTPServer: &server, CTAddr: addr, }, nil } // Close shuts down the servers. func (env *CTLogEnv) Close() { if err := env.ctListener.Close(); err != nil { log.Fatalf("Operation to close listener failed: %v", err) } env.wg.Wait() env.logEnv.Close() } // listen opens a random high numbered port for listening. func listen() (string, net.Listener, error) { lis, err := net.Listen("tcp", ":0") if err != nil { return "", nil, err } _, port, err := net.SplitHostPort(lis.Addr().String()) if err != nil { return "", nil, err } addr := "localhost:" + port return addr, lis, nil } google-certificate-transparency-go-2308f62/trillian/integration/prometheus.yml000066400000000000000000000007231462611535200277220ustar00rootroot00000000000000global: scrape_interval: 15s external_labels: monitor: 'trillian' scrape_configs: - job_name: 'trillian' static_configs: - targets: ['localhost:8091'] # other targets can be added here. # this can be automated if a discovery tool is contributed. # See #948 for some context. - job_name: 'etcd' static_configs: - targets: ['localhost:2379'] - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] google-certificate-transparency-go-2308f62/trillian/migrillian/000077500000000000000000000000001462611535200246065ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/migrillian/README.md000066400000000000000000000007561462611535200260750ustar00rootroot00000000000000Migrillian Tool =============== *Status: in development, not ready for production use* **Migrillian** is a tool that transfers data from existing Certificate Transparency logs to Trillian *PREORDERED_LOG* trees. It can be used for: - One-off data migrations, e.g. from legacy CT implementation to the new Trillian-based [solution](https://github.com/google/certificate-transparency-go). - Continuous migration for keeping the copy up-to-date with the remote log, i.e. log mirroring. google-certificate-transparency-go-2308f62/trillian/migrillian/configpb/000077500000000000000000000000001462611535200263755ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/migrillian/configpb/config.pb.go000066400000000000000000000561011462611535200305740ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.1 // protoc v3.20.1 // source: trillian/migrillian/configpb/config.proto package configpb import ( configpb "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" keyspb "github.com/google/trillian/crypto/keyspb" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // IdentityFunction specifies how Trillian identity hash is computed. type IdentityFunction int32 const ( IdentityFunction_UNKNOWN_IDENTITY_FUNCTION IdentityFunction = 0 // Returns SHA256 hash of the certificate DER. This is the same function that // CTFE uses when submitting add-[pre-]chain entries to Trillian. // // For example, it can be used when migrating a CT log to Trillian. Using the // same function as CTFE makes any newly submitted entries compatible with the // ones that migrated from the source log. IdentityFunction_SHA256_CERT_DATA IdentityFunction = 1 // Returns SHA256 hash of the leaf index. // // For example, this function can be used for mirroring CT logs. Since the // source logs might have duplicates of different kinds (depends on the // operator), this function allows storing them all (unlike SHA256_CERT_DATA). // Note that the CTFE log must stay read-only (mirror), as CTFE's identity // hash is incompatible. IdentityFunction_SHA256_LEAF_INDEX IdentityFunction = 2 ) // Enum value maps for IdentityFunction. var ( IdentityFunction_name = map[int32]string{ 0: "UNKNOWN_IDENTITY_FUNCTION", 1: "SHA256_CERT_DATA", 2: "SHA256_LEAF_INDEX", } IdentityFunction_value = map[string]int32{ "UNKNOWN_IDENTITY_FUNCTION": 0, "SHA256_CERT_DATA": 1, "SHA256_LEAF_INDEX": 2, } ) func (x IdentityFunction) Enum() *IdentityFunction { p := new(IdentityFunction) *p = x return p } func (x IdentityFunction) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (IdentityFunction) Descriptor() protoreflect.EnumDescriptor { return file_trillian_migrillian_configpb_config_proto_enumTypes[0].Descriptor() } func (IdentityFunction) Type() protoreflect.EnumType { return &file_trillian_migrillian_configpb_config_proto_enumTypes[0] } func (x IdentityFunction) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use IdentityFunction.Descriptor instead. func (IdentityFunction) EnumDescriptor() ([]byte, []int) { return file_trillian_migrillian_configpb_config_proto_rawDescGZIP(), []int{0} } // MigrationConfig describes the configuration options for a single CT log // migration instance. type MigrationConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The URI of the source CT log, e.g. "https://ct.googleapis.com/pilot". SourceUri string `protobuf:"bytes,1,opt,name=source_uri,json=sourceUri,proto3" json:"source_uri,omitempty"` // The public key of the source log. PublicKey *keyspb.PublicKey `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // The name of the backend which this log migrates to. The name must be one of // those defined in the LogBackendSet. // // Deprecated. TODO(pavelkalinnikov): Remove it. // // Deprecated: Marked as deprecated in trillian/migrillian/configpb/config.proto. LogBackendName string `protobuf:"bytes,3,opt,name=log_backend_name,json=logBackendName,proto3" json:"log_backend_name,omitempty"` // The ID of a Trillian PREORDERED_LOG tree that stores the log data. LogId int64 `protobuf:"varint,4,opt,name=log_id,json=logId,proto3" json:"log_id,omitempty"` // Max number of entries per get-entries request from the source log. BatchSize int32 `protobuf:"varint,5,opt,name=batch_size,json=batchSize,proto3" json:"batch_size,omitempty"` // Determines whether the migration should run continuously, i.e. watch and // follow the updates of the source log's STH. For example, this mode can be // used to support a mirror CT log. IsContinuous bool `protobuf:"varint,6,opt,name=is_continuous,json=isContinuous,proto3" json:"is_continuous,omitempty"` // The log entry index to start fetching at. If negative, then it is assumed // equal to the current Trillian tree size. // Ignored in continuous mode which starts at the point where it stopped (e.g. // the current Trillian tree size in a simple case). StartIndex int64 `protobuf:"varint,7,opt,name=start_index,json=startIndex,proto3" json:"start_index,omitempty"` // The log index to end fetching at, non-inclusive. If zero, fetch up to the // source log's current STH. Ignored in continuous mode which keeps updating // STH and fetching up to that. EndIndex int64 `protobuf:"varint,8,opt,name=end_index,json=endIndex,proto3" json:"end_index,omitempty"` // The number of parallel get-entries fetchers. Assumed equal to 1 if not // specified. NumFetchers int32 `protobuf:"varint,9,opt,name=num_fetchers,json=numFetchers,proto3" json:"num_fetchers,omitempty"` // The number of parallel workers submitting entries to Trillian. Assumed // equal to 1 if not specified. NumSubmitters int32 `protobuf:"varint,10,opt,name=num_submitters,json=numSubmitters,proto3" json:"num_submitters,omitempty"` // Max number of batches in fetchers->submitters channel. ChannelSize int32 `protobuf:"varint,11,opt,name=channel_size,json=channelSize,proto3" json:"channel_size,omitempty"` // The function that computes LeafIdentityHash for Trillian log entries. IdentityFunction IdentityFunction `protobuf:"varint,12,opt,name=identity_function,json=identityFunction,proto3,enum=configpb.IdentityFunction" json:"identity_function,omitempty"` // If set to false (by default), then Migrillian verifies that the tree as // seen by Trillian is consistent with the current STH of the source CT log. // It invokes the get-sth-consistency endpoint (section 4.4 of RFC 6962) with // the corresponding tree sizes, and verifies the returned proof. NoConsistencyCheck bool `protobuf:"varint,13,opt,name=no_consistency_check,json=noConsistencyCheck,proto3" json:"no_consistency_check,omitempty"` } func (x *MigrationConfig) Reset() { *x = MigrationConfig{} if protoimpl.UnsafeEnabled { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MigrationConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*MigrationConfig) ProtoMessage() {} func (x *MigrationConfig) ProtoReflect() protoreflect.Message { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MigrationConfig.ProtoReflect.Descriptor instead. func (*MigrationConfig) Descriptor() ([]byte, []int) { return file_trillian_migrillian_configpb_config_proto_rawDescGZIP(), []int{0} } func (x *MigrationConfig) GetSourceUri() string { if x != nil { return x.SourceUri } return "" } func (x *MigrationConfig) GetPublicKey() *keyspb.PublicKey { if x != nil { return x.PublicKey } return nil } // Deprecated: Marked as deprecated in trillian/migrillian/configpb/config.proto. func (x *MigrationConfig) GetLogBackendName() string { if x != nil { return x.LogBackendName } return "" } func (x *MigrationConfig) GetLogId() int64 { if x != nil { return x.LogId } return 0 } func (x *MigrationConfig) GetBatchSize() int32 { if x != nil { return x.BatchSize } return 0 } func (x *MigrationConfig) GetIsContinuous() bool { if x != nil { return x.IsContinuous } return false } func (x *MigrationConfig) GetStartIndex() int64 { if x != nil { return x.StartIndex } return 0 } func (x *MigrationConfig) GetEndIndex() int64 { if x != nil { return x.EndIndex } return 0 } func (x *MigrationConfig) GetNumFetchers() int32 { if x != nil { return x.NumFetchers } return 0 } func (x *MigrationConfig) GetNumSubmitters() int32 { if x != nil { return x.NumSubmitters } return 0 } func (x *MigrationConfig) GetChannelSize() int32 { if x != nil { return x.ChannelSize } return 0 } func (x *MigrationConfig) GetIdentityFunction() IdentityFunction { if x != nil { return x.IdentityFunction } return IdentityFunction_UNKNOWN_IDENTITY_FUNCTION } func (x *MigrationConfig) GetNoConsistencyCheck() bool { if x != nil { return x.NoConsistencyCheck } return false } // MigrationConfigSet is a set of MigrationConfig messages. type MigrationConfigSet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Config []*MigrationConfig `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty"` } func (x *MigrationConfigSet) Reset() { *x = MigrationConfigSet{} if protoimpl.UnsafeEnabled { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MigrationConfigSet) String() string { return protoimpl.X.MessageStringOf(x) } func (*MigrationConfigSet) ProtoMessage() {} func (x *MigrationConfigSet) ProtoReflect() protoreflect.Message { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MigrationConfigSet.ProtoReflect.Descriptor instead. func (*MigrationConfigSet) Descriptor() ([]byte, []int) { return file_trillian_migrillian_configpb_config_proto_rawDescGZIP(), []int{1} } func (x *MigrationConfigSet) GetConfig() []*MigrationConfig { if x != nil { return x.Config } return nil } // MigrillianConfig holds configuration for multiple migration / mirroring jobs. type MigrillianConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The set of backends that this configuration will use to send requests to. // The names of the backends in the LogBackendSet must all be distinct. // // Deprecated. TODO(pavelkalinnikov): Remove it. // // Deprecated: Marked as deprecated in trillian/migrillian/configpb/config.proto. Backends *configpb.LogBackendSet `protobuf:"bytes,1,opt,name=backends,proto3" json:"backends,omitempty"` // The set of migrations that will use the above backends. All the protos in // it must set a valid log_backend_name for the config to be usable. MigrationConfigs *MigrationConfigSet `protobuf:"bytes,2,opt,name=migration_configs,json=migrationConfigs,proto3" json:"migration_configs,omitempty"` } func (x *MigrillianConfig) Reset() { *x = MigrillianConfig{} if protoimpl.UnsafeEnabled { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MigrillianConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*MigrillianConfig) ProtoMessage() {} func (x *MigrillianConfig) ProtoReflect() protoreflect.Message { mi := &file_trillian_migrillian_configpb_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MigrillianConfig.ProtoReflect.Descriptor instead. func (*MigrillianConfig) Descriptor() ([]byte, []int) { return file_trillian_migrillian_configpb_config_proto_rawDescGZIP(), []int{2} } // Deprecated: Marked as deprecated in trillian/migrillian/configpb/config.proto. func (x *MigrillianConfig) GetBackends() *configpb.LogBackendSet { if x != nil { return x.Backends } return nil } func (x *MigrillianConfig) GetMigrationConfigs() *MigrationConfigSet { if x != nil { return x.MigrationConfigs } return nil } var File_trillian_migrillian_configpb_config_proto protoreflect.FileDescriptor var file_trillian_migrillian_configpb_config_proto_rawDesc = []byte{ 0x0a, 0x29, 0x74, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x6d, 0x69, 0x67, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x1a, 0x23, 0x74, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x63, 0x74, 0x66, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x91, 0x04, 0x0a, 0x0f, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x72, 0x69, 0x12, 0x30, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x10, 0x6c, 0x6f, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x6c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x47, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0x47, 0x0a, 0x12, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x65, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x96, 0x01, 0x0a, 0x10, 0x4d, 0x69, 0x67, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2e, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x65, 0x74, 0x52, 0x10, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x2a, 0x5e, 0x0a, 0x10, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x5f, 0x46, 0x55, 0x4e, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x5f, 0x4c, 0x45, 0x41, 0x46, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x02, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x6d, 0x69, 0x67, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_trillian_migrillian_configpb_config_proto_rawDescOnce sync.Once file_trillian_migrillian_configpb_config_proto_rawDescData = file_trillian_migrillian_configpb_config_proto_rawDesc ) func file_trillian_migrillian_configpb_config_proto_rawDescGZIP() []byte { file_trillian_migrillian_configpb_config_proto_rawDescOnce.Do(func() { file_trillian_migrillian_configpb_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_trillian_migrillian_configpb_config_proto_rawDescData) }) return file_trillian_migrillian_configpb_config_proto_rawDescData } var file_trillian_migrillian_configpb_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_trillian_migrillian_configpb_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_trillian_migrillian_configpb_config_proto_goTypes = []interface{}{ (IdentityFunction)(0), // 0: configpb.IdentityFunction (*MigrationConfig)(nil), // 1: configpb.MigrationConfig (*MigrationConfigSet)(nil), // 2: configpb.MigrationConfigSet (*MigrillianConfig)(nil), // 3: configpb.MigrillianConfig (*keyspb.PublicKey)(nil), // 4: keyspb.PublicKey (*configpb.LogBackendSet)(nil), // 5: configpb.LogBackendSet } var file_trillian_migrillian_configpb_config_proto_depIdxs = []int32{ 4, // 0: configpb.MigrationConfig.public_key:type_name -> keyspb.PublicKey 0, // 1: configpb.MigrationConfig.identity_function:type_name -> configpb.IdentityFunction 1, // 2: configpb.MigrationConfigSet.config:type_name -> configpb.MigrationConfig 5, // 3: configpb.MigrillianConfig.backends:type_name -> configpb.LogBackendSet 2, // 4: configpb.MigrillianConfig.migration_configs:type_name -> configpb.MigrationConfigSet 5, // [5:5] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name } func init() { file_trillian_migrillian_configpb_config_proto_init() } func file_trillian_migrillian_configpb_config_proto_init() { if File_trillian_migrillian_configpb_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_trillian_migrillian_configpb_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MigrationConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_migrillian_configpb_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MigrationConfigSet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_trillian_migrillian_configpb_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MigrillianConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_trillian_migrillian_configpb_config_proto_rawDesc, NumEnums: 1, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_trillian_migrillian_configpb_config_proto_goTypes, DependencyIndexes: file_trillian_migrillian_configpb_config_proto_depIdxs, EnumInfos: file_trillian_migrillian_configpb_config_proto_enumTypes, MessageInfos: file_trillian_migrillian_configpb_config_proto_msgTypes, }.Build() File_trillian_migrillian_configpb_config_proto = out.File file_trillian_migrillian_configpb_config_proto_rawDesc = nil file_trillian_migrillian_configpb_config_proto_goTypes = nil file_trillian_migrillian_configpb_config_proto_depIdxs = nil } google-certificate-transparency-go-2308f62/trillian/migrillian/configpb/config.proto000066400000000000000000000112311462611535200307250ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; option go_package = "github.com/google/certificate-transparency-go/trillian/migrillian/configpb"; package configpb; import "trillian/ctfe/configpb/config.proto"; import "crypto/keyspb/keyspb.proto"; // IdentityFunction specifies how Trillian identity hash is computed. enum IdentityFunction { UNKNOWN_IDENTITY_FUNCTION = 0; // Returns SHA256 hash of the certificate DER. This is the same function that // CTFE uses when submitting add-[pre-]chain entries to Trillian. // // For example, it can be used when migrating a CT log to Trillian. Using the // same function as CTFE makes any newly submitted entries compatible with the // ones that migrated from the source log. SHA256_CERT_DATA = 1; // Returns SHA256 hash of the leaf index. // // For example, this function can be used for mirroring CT logs. Since the // source logs might have duplicates of different kinds (depends on the // operator), this function allows storing them all (unlike SHA256_CERT_DATA). // Note that the CTFE log must stay read-only (mirror), as CTFE's identity // hash is incompatible. SHA256_LEAF_INDEX = 2; } // MigrationConfig describes the configuration options for a single CT log // migration instance. message MigrationConfig { // The URI of the source CT log, e.g. "https://ct.googleapis.com/pilot". string source_uri = 1; // The public key of the source log. keyspb.PublicKey public_key = 2; // The name of the backend which this log migrates to. The name must be one of // those defined in the LogBackendSet. // // Deprecated. TODO(pavelkalinnikov): Remove it. string log_backend_name = 3 [deprecated=true]; // The ID of a Trillian PREORDERED_LOG tree that stores the log data. int64 log_id = 4; // Max number of entries per get-entries request from the source log. int32 batch_size = 5; // Determines whether the migration should run continuously, i.e. watch and // follow the updates of the source log's STH. For example, this mode can be // used to support a mirror CT log. bool is_continuous = 6; // The log entry index to start fetching at. If negative, then it is assumed // equal to the current Trillian tree size. // Ignored in continuous mode which starts at the point where it stopped (e.g. // the current Trillian tree size in a simple case). int64 start_index = 7; // The log index to end fetching at, non-inclusive. If zero, fetch up to the // source log's current STH. Ignored in continuous mode which keeps updating // STH and fetching up to that. int64 end_index = 8; // The number of parallel get-entries fetchers. Assumed equal to 1 if not // specified. int32 num_fetchers = 9; // The number of parallel workers submitting entries to Trillian. Assumed // equal to 1 if not specified. int32 num_submitters = 10; // Max number of batches in fetchers->submitters channel. int32 channel_size = 11; // The function that computes LeafIdentityHash for Trillian log entries. IdentityFunction identity_function = 12; // If set to false (by default), then Migrillian verifies that the tree as // seen by Trillian is consistent with the current STH of the source CT log. // It invokes the get-sth-consistency endpoint (section 4.4 of RFC 6962) with // the corresponding tree sizes, and verifies the returned proof. bool no_consistency_check = 13; // TODO(pavelkalinnikov): Fetch and push quotas, priorities, etc. } // MigrationConfigSet is a set of MigrationConfig messages. message MigrationConfigSet { repeated MigrationConfig config = 1; } // MigrillianConfig holds configuration for multiple migration / mirroring jobs. message MigrillianConfig { // The set of backends that this configuration will use to send requests to. // The names of the backends in the LogBackendSet must all be distinct. // // Deprecated. TODO(pavelkalinnikov): Remove it. LogBackendSet backends = 1 [deprecated=true]; // The set of migrations that will use the above backends. All the protos in // it must set a valid log_backend_name for the config to be usable. MigrationConfigSet migration_configs = 2; } google-certificate-transparency-go-2308f62/trillian/migrillian/core/000077500000000000000000000000001462611535200255365ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/migrillian/core/config.go000066400000000000000000000054511462611535200273370ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "errors" "fmt" "os" "github.com/google/certificate-transparency-go/trillian/migrillian/configpb" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) // LoadConfigFromFile reads MigrillianConfig from the given filename, which // should contain text-protobuf encoded configuration data. func LoadConfigFromFile(filename string) (*configpb.MigrillianConfig, error) { cfgBytes, err := os.ReadFile(filename) if err != nil { return nil, err } var cfg configpb.MigrillianConfig if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil { if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil { return nil, fmt.Errorf("failed to parse MigrillianConfig from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr) } } return &cfg, nil } // ValidateMigrationConfig verifies that the migration config is sane. func ValidateMigrationConfig(cfg *configpb.MigrationConfig) error { // TODO(pavelkalinnikov): Also try to parse the public key. switch { case len(cfg.SourceUri) == 0: return errors.New("missing CT log URI") case cfg.PublicKey == nil: return errors.New("missing public key") case cfg.LogId <= 0: return errors.New("log ID must be positive") case cfg.BatchSize <= 0: return errors.New("batch size must be positive") } switch idFunc := cfg.IdentityFunction; idFunc { case configpb.IdentityFunction_SHA256_CERT_DATA: case configpb.IdentityFunction_SHA256_LEAF_INDEX: default: return fmt.Errorf("unknown identity function: %v", idFunc) } return nil } // ValidateConfig verifies that MigrillianConfig is correct. In particular: // - Migration configs are valid (as per ValidateMigrationConfig). // - Each migration config has a unique log ID. func ValidateConfig(cfg *configpb.MigrillianConfig) error { // Validate each MigrationConfig, and ensure that log IDs are unique. logIDs := make(map[int64]bool) for _, mc := range cfg.MigrationConfigs.Config { if err := ValidateMigrationConfig(mc); err != nil { return fmt.Errorf("MigrationConfig: %v: %v", err, mc) } if ok := logIDs[mc.LogId]; ok { return fmt.Errorf("duplicate tree ID %d: %v", mc.LogId, mc) } logIDs[mc.LogId] = true } return nil } google-certificate-transparency-go-2308f62/trillian/migrillian/core/config_test.go000066400000000000000000000074741462611535200304050ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "encoding/pem" "strings" "testing" "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" "github.com/google/certificate-transparency-go/trillian/migrillian/configpb" "github.com/google/trillian/crypto/keyspb" ) const ctURI = "https://ct.googleapis.com/testtube" func TestLoadConfigFromFileValid(t *testing.T) { for _, tc := range []struct { desc string filename string wantItems int }{ {desc: "text proto", filename: "../testdata/config.textproto", wantItems: 2}, {desc: "binary proto", filename: "../testdata/config.pb", wantItems: 1}, } { t.Run(tc.desc, func(t *testing.T) { cfg, err := LoadConfigFromFile(tc.filename) if err != nil { t.Fatalf("LoadConfigFromFile(): %v", err) } if cfg == nil { t.Fatal("Config is nil") } if err := ValidateConfig(cfg); err != nil { t.Fatalf("Loaded invalid config: %v", err) } if got, want := len(cfg.MigrationConfigs.Config), tc.wantItems; got != want { t.Errorf("Wrong number of migration configs %d, want %d", got, want) } }) } } func TestLoadConfigFromFileErrors(t *testing.T) { for _, tc := range []struct { desc string filename string wantErr string }{ {desc: "no-such-file", filename: "does-not-exist", wantErr: "no such file"}, {desc: "wrong-format", filename: "../testdata/not-config.textproto", wantErr: "failed to parse"}, } { t.Run(tc.desc, func(t *testing.T) { cfg, err := LoadConfigFromFile(tc.filename) if err == nil || !strings.Contains(err.Error(), tc.wantErr) { t.Errorf("Expected error containing %q", tc.wantErr) } if cfg != nil { t.Error("Expected nil config") } }) } } func TestValidateMigrationConfig(t *testing.T) { block, _ := pem.Decode([]byte(testonly.CTLogPublicKeyPEM)) pubKey := &keyspb.PublicKey{Der: block.Bytes} for _, tc := range []struct { desc string cfg *configpb.MigrationConfig wantErr string }{ { desc: "missing-source-uri", cfg: &configpb.MigrationConfig{}, wantErr: "missing CT log URI", }, { desc: "missing-pub-key", cfg: &configpb.MigrationConfig{SourceUri: ctURI}, wantErr: "missing public key", }, { desc: "wrong-log-ID", cfg: &configpb.MigrationConfig{SourceUri: ctURI, PublicKey: pubKey}, wantErr: "log ID must be positive", }, { desc: "wrong-batch-size", cfg: &configpb.MigrationConfig{SourceUri: ctURI, PublicKey: pubKey, LogId: 10}, wantErr: "batch size must be positive", }, { desc: "unknown-identity-function", cfg: &configpb.MigrationConfig{SourceUri: ctURI, PublicKey: pubKey, LogId: 10, BatchSize: 100}, wantErr: "unknown identity function", }, { desc: "ok", cfg: &configpb.MigrationConfig{SourceUri: ctURI, PublicKey: pubKey, LogId: 10, BatchSize: 100, IdentityFunction: configpb.IdentityFunction_SHA256_CERT_DATA}, }, } { t.Run(tc.desc, func(t *testing.T) { err := ValidateMigrationConfig(tc.cfg) if len(tc.wantErr) == 0 && err != nil { t.Errorf("ValidateMigrationConfig()=%v, want nil", err) } if len(tc.wantErr) > 0 && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { t.Errorf("ValidateMigrationConfig()=%v, want err containing %q", err, tc.wantErr) } }) } } google-certificate-transparency-go-2308f62/trillian/migrillian/core/controller.go000066400000000000000000000313061462611535200302530ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package core provides transport-agnostic implementation of Migrillian tool. package core import ( "context" "fmt" "math/rand" "strconv" "sync" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/trillian/migrillian/configpb" "k8s.io/klog/v2" "github.com/google/trillian/monitoring" "github.com/google/trillian/util/clock" "github.com/google/trillian/util/election2" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" ) var ( metrics treeMetrics metricsOnce sync.Once ) // treeMetrics holds metrics keyed by Tree ID. type treeMetrics struct { masterRuns monitoring.Counter masterCancels monitoring.Counter controllerStarts monitoring.Counter isMaster monitoring.Gauge entriesFetched monitoring.Counter entriesSeen monitoring.Counter entriesStored monitoring.Counter sthTimestamp monitoring.Gauge sthTreeSize monitoring.Gauge } // initMetrics creates metrics using the factory, if not yet created. func initMetrics(mf monitoring.MetricFactory) { const treeID = "tree_id" metricsOnce.Do(func() { metrics = treeMetrics{ masterRuns: mf.NewCounter("master_runs", "Number of mastership runs.", treeID), masterCancels: mf.NewCounter("master_cancels", "Number of unexpected mastership cancelations.", treeID), controllerStarts: mf.NewCounter("controller_starts", "Number of Controller (re-)starts.", treeID), isMaster: mf.NewGauge("is_master", "The instance is currently the master.", treeID), entriesFetched: mf.NewCounter("entries_fetched", "Entries fetched from the source log.", treeID), entriesSeen: mf.NewCounter("entries_seen", "Entries seen by the submitters.", treeID), entriesStored: mf.NewCounter("entries_stored", "Entries successfully submitted to Trillian.", treeID), sthTimestamp: mf.NewGauge("sth_timestamp", "Timestamp of the last seen STH.", treeID), sthTreeSize: mf.NewGauge("sth_tree_size", "Tree size of the last seen STH.", treeID), } }) } // Options holds configuration for a Controller. type Options struct { scanner.FetcherOptions Submitters int ChannelSize int NoConsistencyCheck bool StartDelay time.Duration StopAfter time.Duration } // OptionsFromConfig returns Options created from the passed in config. func OptionsFromConfig(cfg *configpb.MigrationConfig) Options { opts := Options{ FetcherOptions: scanner.FetcherOptions{ BatchSize: int(cfg.BatchSize), ParallelFetch: int(cfg.NumFetchers), StartIndex: cfg.StartIndex, EndIndex: cfg.EndIndex, Continuous: cfg.IsContinuous, }, Submitters: int(cfg.NumSubmitters), ChannelSize: int(cfg.ChannelSize), NoConsistencyCheck: cfg.NoConsistencyCheck, } if cfg.NumFetchers == 0 { opts.ParallelFetch = 1 } if cfg.NumSubmitters == 0 { opts.Submitters = 1 } return opts } // Controller coordinates migration from a CT log to a Trillian tree. type Controller struct { opts Options ctClient *client.LogClient plClient *PreorderedLogClient ef election2.Factory label string } // NewController creates a Controller configured by the passed in options, CT // and Trillian clients, and a master election factory. // // The passed in MetricFactory is used to create per-tree metrics, and it // should be the same for all instances. However, it is used only once. func NewController( opts Options, ctClient *client.LogClient, plClient *PreorderedLogClient, ef election2.Factory, mf monitoring.MetricFactory, ) *Controller { initMetrics(mf) l := strconv.FormatInt(plClient.treeID, 10) return &Controller{opts: opts, ctClient: ctClient, plClient: plClient, ef: ef, label: l} } // RunWhenMasterWithRestarts calls RunWhenMaster, and, if the migration is // configured with continuous mode, restarts it whenever it returns. func (c *Controller) RunWhenMasterWithRestarts(ctx context.Context) { uri := c.ctClient.BaseURI() treeID := c.plClient.treeID for run := true; run; run = c.opts.Continuous && ctx.Err() == nil { klog.Infof("Starting migration Controller (%d<-%q)", treeID, uri) if err := c.RunWhenMaster(ctx); err != nil { klog.Errorf("Controller.RunWhenMaster(%d<-%q): %v", treeID, uri, err) continue } klog.Infof("Controller stopped (%d<-%q)", treeID, uri) } } // RunWhenMaster is a master-elected version of Run method. It executes Run // whenever this instance captures mastership of the tree ID. As soon as the // instance stops being the master, Run is canceled. The method returns if a // severe error occurs, the passed in context is canceled, or fetching is // completed (in non-Continuous mode). Releases mastership when terminates. func (c *Controller) RunWhenMaster(ctx context.Context) error { // Avoid thundering herd when starting multiple tasks on the same tree. if err := sleepRandom(ctx, 0, c.opts.StartDelay); err != nil { return err // The context has been canceled. } el, err := c.ef.NewElection(ctx, c.label) if err != nil { return err } metrics.isMaster.Set(0, c.label) defer func(ctx context.Context) { metrics.isMaster.Set(0, c.label) if err := el.Close(ctx); err != nil { klog.Warningf("%s: Election.Close(): %v", c.label, err) } }(ctx) for { if err := el.Await(ctx); err != nil { return err } metrics.isMaster.Set(1, c.label) mctx, err := el.WithMastership(ctx) if err != nil { return err } else if err := mctx.Err(); err != nil { return err } klog.Infof("%s: running as master", c.label) metrics.masterRuns.Inc(c.label) // Run while still master (or until an error). err = c.runWithRestarts(mctx) if ctx.Err() != nil { // We have been externally canceled, so return the current error (which // could be nil or a cancelation-related error). return err } else if mctx.Err() == nil { // We are still the master, so try to resign and emit the real error. if rerr := el.Resign(ctx); rerr != nil { klog.Errorf("%s: Election.Resign(): %v", c.label, rerr) } return err } // Otherwise the mastership has been canceled, retry. metrics.isMaster.Set(0, c.label) metrics.masterCancels.Inc(c.label) } } // runWithRestarts calls Run until it succeeds or the context is done, in // continuous mode. For non-continuous mode it is simply equivalent to Run. func (c *Controller) runWithRestarts(ctx context.Context) error { err := c.Run(ctx) if !c.opts.Continuous { return err } for err != nil && ctx.Err() == nil { klog.Errorf("%s: Controller.Run: %v", c.label, err) if slerr := sleepRandom(ctx, 0, c.opts.StartDelay); slerr == nil { err = c.Run(ctx) } } return ctx.Err() } // Run transfers CT log entries obtained via the CT log client to a Trillian // pre-ordered log via Trillian client. If Options.Continuous is true then the // migration process runs continuously trying to keep up with the target CT // log. Returns if an error occurs, the context is canceled, or all the entries // have been transferred (in non-Continuous mode). func (c *Controller) Run(ctx context.Context) error { metrics.controllerStarts.Inc(c.label) stopAfter := randDuration(c.opts.StopAfter, c.opts.StopAfter) start := time.Now() // Note: Non-continuous runs are not affected by StopAfter. pos, err := c.fetchTail(ctx, 0) if err != nil { return err } if !c.opts.Continuous { return nil } for stopAfter == 0 || time.Since(start) < stopAfter { // TODO(pavelkalinnikov): Integrate runWithRestarts here. next, err := c.fetchTail(ctx, pos) if err != nil { return err } if next == pos { // TODO(pavelkalinnikov): Pause with accordance to the rate of growth. // TODO(pavelkalinnikov): Make the duration configurable. if err := clock.SleepContext(ctx, 30*time.Second); err != nil { return err } } pos = next } return nil } // fetchTail transfers entries within the range specified in FetcherConfig, // with respect to the passed in minimal position to start from, and the // current tree size obtained from an STH. func (c *Controller) fetchTail(ctx context.Context, begin uint64) (uint64, error) { treeSize, rootHash, err := c.plClient.getRoot(ctx) if err != nil { return 0, err } fo := c.opts.FetcherOptions if fo.Continuous { // Ignore range parameters in continuous mode. fo.StartIndex, fo.EndIndex = int64(treeSize), 0 // Use non-continuous Fetcher, as we implement continuity in Controller. // TODO(pavelkalinnikov): Don't overload Fetcher's Continuous flag. fo.Continuous = false } else if fo.StartIndex < 0 { fo.StartIndex = int64(treeSize) } if int64(begin) > fo.StartIndex { fo.StartIndex = int64(begin) } klog.Infof("%s: fetching range [%d, %d)", c.label, fo.StartIndex, fo.EndIndex) fetcher := scanner.NewFetcher(c.ctClient, &fo) sth, err := fetcher.Prepare(ctx) if err != nil { return 0, err } metrics.sthTimestamp.Set(float64(sth.Timestamp), c.label) metrics.sthTreeSize.Set(float64(sth.TreeSize), c.label) if sth.TreeSize <= begin { return begin, nil } if err := c.verifyConsistency(ctx, treeSize, rootHash, sth); err != nil { return 0, err } var wg sync.WaitGroup batches := make(chan scanner.EntryBatch, c.opts.ChannelSize) cctx, cancel := context.WithCancel(ctx) defer cancel() for w, cnt := 0, c.opts.Submitters; w < cnt; w++ { wg.Add(1) go func() { defer wg.Done() if err := c.runSubmitter(cctx, batches); err != nil { klog.Errorf("%s: Stopping due to submitter error: %v", c.label, err) cancel() // Stop the other submitters and the Fetcher. } }() } handler := func(b scanner.EntryBatch) { metrics.entriesFetched.Add(float64(len(b.Entries)), c.label) select { case batches <- b: case <-cctx.Done(): // Avoid deadlock when shutting down. } } err = fetcher.Run(cctx, handler) close(batches) wg.Wait() if err != nil { return 0, err } // Run may have returned nil despite a cancel() call. if err := cctx.Err(); err != nil { return 0, fmt.Errorf("failed to fetch and submit the entire tail: %v", err) } return sth.TreeSize, nil } // verifyConsistency checks that the provided verified Trillian root is // consistent with the CT log's STH. func (c *Controller) verifyConsistency(ctx context.Context, treeSize uint64, rootHash []byte, sth *ct.SignedTreeHead) error { if treeSize == 0 { // Any head is consistent with empty root -- unnecessary to request empty proof. return nil } if c.opts.NoConsistencyCheck { klog.Warningf("%s: skipping consistency check", c.label) return nil } pf, err := c.ctClient.GetSTHConsistency(ctx, treeSize, sth.TreeSize) if err != nil { return err } return proof.VerifyConsistency(rfc6962.DefaultHasher, treeSize, sth.TreeSize, pf, rootHash, sth.SHA256RootHash[:]) } // runSubmitter obtains CT log entry batches from the controller's channel and // submits them through Trillian client. Returns when the channel is closed, or // the client returns a non-recoverable error (an example of a recoverable // error is when Trillian write quota is exceeded). func (c *Controller) runSubmitter(ctx context.Context, batches <-chan scanner.EntryBatch) error { for b := range batches { entries := float64(len(b.Entries)) metrics.entriesSeen.Add(entries, c.label) end := b.Start + int64(len(b.Entries)) if err := c.plClient.addSequencedLeaves(ctx, &b); err != nil { // addSequencedLeaves failed to submit entries despite retries. At this // point there is not much we can do. Seemingly the best strategy is to // shut down the Controller. return fmt.Errorf("failed to add batch [%d, %d): %v", b.Start, end, err) } klog.Infof("%s: added batch [%d, %d)", c.label, b.Start, end) metrics.entriesStored.Add(entries, c.label) } return nil } // sleepRandom sleeps for random duration in [base, base+spread). func sleepRandom(ctx context.Context, base, spread time.Duration) error { d := randDuration(base, spread) if d == 0 { return nil } return clock.SleepContext(ctx, d) } // randDuration returns a random duration in [base, base+spread). func randDuration(base, spread time.Duration) time.Duration { d := base if spread != 0 { d += time.Duration(rand.Int63n(int64(spread))) } return d } google-certificate-transparency-go-2308f62/trillian/migrillian/core/controller_test.go000066400000000000000000000017331462611535200313130ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "context" "testing" ct "github.com/google/certificate-transparency-go" ) func TestVerifyConsistencyEmptyHead(t *testing.T) { controller := new(Controller) if controller.verifyConsistency(context.Background(), 0, []byte("abc"), &ct.SignedTreeHead{TreeSize: 100}) != nil { t.Errorf("verifyConsistency should always succeed given empty root") } } google-certificate-transparency-go-2308f62/trillian/migrillian/core/multi.go000066400000000000000000000022261462611535200272210ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "context" "sync" ) // RunMigration migrates data from a number of CT logs to Trillian. Each log's // migration is coordinated by the corresponding Controller. This function // terminates when all Controllers are done (possibly with an error, or as a // result of canceling the passed in context). func RunMigration(ctx context.Context, ctrls []*Controller) { var wg sync.WaitGroup for _, ctrl := range ctrls { wg.Add(1) go func(ctrl *Controller) { defer wg.Done() ctrl.RunWhenMasterWithRestarts(ctx) }(ctrl) } wg.Wait() } google-certificate-transparency-go-2308f62/trillian/migrillian/core/trillian.go000066400000000000000000000133521462611535200277070ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "context" "crypto/sha256" "encoding/binary" "errors" "fmt" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/scanner" "github.com/google/certificate-transparency-go/trillian/migrillian/configpb" "github.com/google/certificate-transparency-go/x509" "github.com/google/trillian" "github.com/google/trillian/client/backoff" "github.com/google/trillian/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog/v2" ) var errRetry = errors.New("retry") // PreorderedLogClient is a means of communicating with a single Trillian // pre-ordered log tree. type PreorderedLogClient struct { cli trillian.TrillianLogClient treeID int64 idFunc func(int64, *ct.RawLogEntry) []byte prefix string // TODO(pavelkalinnikov): Get rid of this. } // NewPreorderedLogClient creates and initializes a pre-ordered log client. func NewPreorderedLogClient( cli trillian.TrillianLogClient, tree *trillian.Tree, idFuncType configpb.IdentityFunction, prefix string, ) (*PreorderedLogClient, error) { if tree == nil { return nil, errors.New("missing Tree") } if got, want := tree.TreeType, trillian.TreeType_PREORDERED_LOG; got != want { return nil, fmt.Errorf("tree %d is %v, want %v", tree.TreeId, got, want) } ret := PreorderedLogClient{cli: cli, treeID: tree.TreeId, prefix: prefix} switch idFuncType { case configpb.IdentityFunction_SHA256_CERT_DATA: ret.idFunc = idHashCertData case configpb.IdentityFunction_SHA256_LEAF_INDEX: ret.idFunc = idHashLeafIndex default: return nil, fmt.Errorf("unknown identity function: %v", idFuncType) } return &ret, nil } // getRoot returns the current root of the Trillian tree. func (c *PreorderedLogClient) getRoot(ctx context.Context) (uint64, []byte, error) { req := trillian.GetLatestSignedLogRootRequest{LogId: c.treeID} rsp, err := c.cli.GetLatestSignedLogRoot(ctx, &req) if err != nil { return 0, nil, err } else if rsp == nil || rsp.SignedLogRoot == nil { return 0, nil, errors.New("missing SignedLogRoot") } var logRoot types.LogRootV1 if err := logRoot.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { return 0, nil, err } return logRoot.TreeSize, logRoot.RootHash, nil } // addSequencedLeaves converts a batch of CT log entries into Trillian log // leaves and submits them to Trillian via AddSequencedLeaves API. // // If and while Trillian returns "quota exceeded" errors, the function will // retry the request with a limited exponential back-off. // // Returns an error if Trillian replies with a severe/unknown error. func (c *PreorderedLogClient) addSequencedLeaves(ctx context.Context, b *scanner.EntryBatch) error { // TODO(pavelkalinnikov): Verify range inclusion against the remote STH. leaves := make([]*trillian.LogLeaf, len(b.Entries)) for i, e := range b.Entries { var err error if leaves[i], err = c.buildLogLeaf(b.Start+int64(i), &e); err != nil { return err } } req := trillian.AddSequencedLeavesRequest{LogId: c.treeID, Leaves: leaves} // TODO(pavelkalinnikov): Make this strategy configurable. bo := backoff.Backoff{ Min: 1 * time.Second, Max: 1 * time.Minute, Factor: 3, Jitter: true, } var err error boerr := bo.Retry(ctx, func() error { var rsp *trillian.AddSequencedLeavesResponse rsp, err = c.cli.AddSequencedLeaves(ctx, &req) switch status.Code(err) { case codes.ResourceExhausted: // There was (probably) a quota error. end := b.Start + int64(len(b.Entries)) klog.Errorf("%d: retrying batch [%d, %d) due to error: %v", c.treeID, b.Start, end, err) return errRetry case codes.OK: if rsp == nil { err = errors.New("missing AddSequencedLeaves response") } // TODO(pavelkalinnikov): Check rsp.Results statuses. return nil default: // There was another (probably serious) error. return nil // Stop backing off, and return err as is below. } }) if err != nil { // Return the more specific error if available return err } // Return the timeout, or nil on success return boerr } func (c *PreorderedLogClient) buildLogLeaf(index int64, entry *ct.LeafEntry) (*trillian.LogLeaf, error) { rle, err := ct.RawLogEntryFromLeaf(index, entry) if err != nil { return nil, err } // Don't return on x509 parsing errors because we want to migrate this log // entry as is. But log the error so that it can be flagged by monitoring. if _, err = rle.ToLogEntry(); x509.IsFatal(err) { klog.Errorf("%s: index=%d: x509 fatal error: %v", c.prefix, index, err) } else if err != nil { klog.Infof("%s: index=%d: x509 non-fatal error: %v", c.prefix, index, err) } // TODO(pavelkalinnikov): Verify cert chain if error is nil or non-fatal. leafIDHash := c.idFunc(index, rle) return &trillian.LogLeaf{ LeafValue: entry.LeafInput, ExtraData: entry.ExtraData, LeafIndex: index, LeafIdentityHash: leafIDHash[:], }, nil } func idHashCertData(_ int64, entry *ct.RawLogEntry) []byte { hash := sha256.Sum256(entry.Cert.Data) return hash[:] } func idHashLeafIndex(index int64, _ *ct.RawLogEntry) []byte { data := make([]byte, 8) binary.LittleEndian.PutUint64(data, uint64(index)) hash := sha256.Sum256(data) return hash[:] } google-certificate-transparency-go-2308f62/trillian/migrillian/main.go000066400000000000000000000160311462611535200260620ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Migrillian tool transfers certs from CT logs to Trillian pre-ordered logs in // the same order. package main import ( "context" "errors" "flag" "fmt" "net/http" "os" "strings" "time" clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "k8s.io/klog/v2" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/trillian/migrillian/configpb" "github.com/google/certificate-transparency-go/trillian/migrillian/core" "github.com/google/trillian" "github.com/google/trillian/monitoring" "github.com/google/trillian/monitoring/prometheus" "github.com/google/trillian/util" "github.com/google/trillian/util/election2" etcdelect "github.com/google/trillian/util/election2/etcd" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( cfgPath = flag.String("config", "", "Path to migration config file") forceMaster = flag.Bool("force_master", false, "If true, assume master for all logs") etcdServers = flag.String("etcd_servers", "", "A comma-separated list of etcd servers; no etcd registration if empty") lockDir = flag.String("lock_file_path", "/migrillian/master", "etcd lock file directory path") electionDelay = flag.Duration("election_delay", 0, "Max random pause before participating in master election") backend = flag.String("backend", "", "GRPC endpoint to connect to Trillian logservers") metricsEndpoint = flag.String("metrics_endpoint", "localhost:8099", "Endpoint for serving metrics") maxIdleConnsPerHost = flag.Int("max_idle_conns_per_host", 10, "Max idle HTTP connections per host (0 = DefaultMaxIdleConnsPerHost)") maxIdleConns = flag.Int("max_idle_conns", 100, "Max number of idle HTTP connections across all hosts (0 = unlimited)") ) func main() { klog.InitFlags(nil) flag.Parse() klog.CopyStandardLogTo("WARNING") defer klog.Flush() if *backend == "" { klog.Exit("--backend flag must be specified") } cfg, err := getConfig() if err != nil { klog.Exitf("Failed to load MigrillianConfig: %v", err) } if err := core.ValidateConfig(cfg); err != nil { klog.Exitf("Failed to validate MigrillianConfig: %v", err) } klog.Infof("Dialling Trillian backend: %v", *backend) conn, err := grpc.Dial(*backend, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { klog.Exitf("Could not dial Trillian server: %v: %v", *backend, err) } defer func() { if err := conn.Close(); err != nil { klog.Errorf("Could not close RPC connection: %v", err) } }() httpClient := getHTTPClient() mf := prometheus.MetricFactory{} ef, closeFn := getElectionFactory() defer closeFn() ctx := context.Background() var ctrls []*core.Controller for _, mc := range cfg.MigrationConfigs.Config { ctrl, err := getController(ctx, mc, httpClient, mf, ef, conn) if err != nil { klog.Exitf("Failed to create Controller for %q: %v", mc.SourceUri, err) } ctrls = append(ctrls, ctrl) } // Handle metrics on the DefaultServeMux. http.Handle("/metrics", promhttp.Handler()) go func() { err := http.ListenAndServe(*metricsEndpoint, nil) klog.Fatalf("http.ListenAndServe(): %v", err) }() cctx, cancel := context.WithCancel(ctx) defer cancel() go util.AwaitSignal(cctx, cancel) core.RunMigration(cctx, ctrls) } // getController creates a single log migration Controller. func getController( ctx context.Context, cfg *configpb.MigrationConfig, httpClient *http.Client, mf monitoring.MetricFactory, ef election2.Factory, conn *grpc.ClientConn, ) (*core.Controller, error) { ctOpts := jsonclient.Options{PublicKeyDER: cfg.PublicKey.Der, UserAgent: "ct-go-migrillian/1.0"} ctClient, err := client.New(cfg.SourceUri, httpClient, ctOpts) if err != nil { return nil, fmt.Errorf("failed to create CT client: %v", err) } plClient, err := newPreorderedLogClient(ctx, conn, cfg) if err != nil { return nil, fmt.Errorf("failed to create PreorderedLogClient: %v", err) } opts := core.OptionsFromConfig(cfg) opts.StartDelay = *electionDelay return core.NewController(opts, ctClient, plClient, ef, mf), nil } // getConfig returns MigrillianConfig loaded from the file specified in flags. func getConfig() (*configpb.MigrillianConfig, error) { if len(*cfgPath) == 0 { return nil, errors.New("config file not specified") } cfg, err := core.LoadConfigFromFile(*cfgPath) if err != nil { return nil, err } return cfg, nil } // getHTTPClient returns an HTTP client created from flags. func getHTTPClient() *http.Client { transport := &http.Transport{ TLSHandshakeTimeout: 30 * time.Second, DisableKeepAlives: false, MaxIdleConns: *maxIdleConns, MaxIdleConnsPerHost: *maxIdleConnsPerHost, IdleConnTimeout: 90 * time.Second, ResponseHeaderTimeout: 30 * time.Second, ExpectContinueTimeout: 1 * time.Second, } // TODO(pavelkalinnikov): Make the timeout tunable. return &http.Client{ Timeout: 10 * time.Second, Transport: transport, } } // newPreorderedLogClient creates a PreorderedLogClient for the specified tree. func newPreorderedLogClient( ctx context.Context, conn *grpc.ClientConn, cfg *configpb.MigrationConfig, ) (*core.PreorderedLogClient, error) { admin := trillian.NewTrillianAdminClient(conn) gt := trillian.GetTreeRequest{TreeId: cfg.LogId} tree, err := admin.GetTree(ctx, >) if err != nil { return nil, err } log := trillian.NewTrillianLogClient(conn) pref := fmt.Sprintf("%d", cfg.LogId) return core.NewPreorderedLogClient(log, tree, cfg.IdentityFunction, pref) } // getElectionFactory returns an election factory based on flags, and a // function which releases the resources associated with the factory. func getElectionFactory() (election2.Factory, func()) { if *forceMaster { klog.Warning("Acting as master for all logs") return election2.NoopFactory{}, func() {} } if len(*etcdServers) == 0 { klog.Exit("Either --force_master or --etcd_servers must be supplied") } cli, err := clientv3.New(clientv3.Config{ Endpoints: strings.Split(*etcdServers, ","), DialTimeout: 5 * time.Second, }) if err != nil || cli == nil { klog.Exitf("Failed to create etcd client: %v", err) } closeFn := func() { if err := cli.Close(); err != nil { klog.Warningf("etcd client Close(): %v", err) } } hostname, _ := os.Hostname() instanceID := fmt.Sprintf("%s.%d", hostname, os.Getpid()) factory := etcdelect.NewFactory(instanceID, cli, *lockDir) return factory, closeFn } google-certificate-transparency-go-2308f62/trillian/migrillian/testdata/000077500000000000000000000000001462611535200264175ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/migrillian/testdata/config.pb000066400000000000000000000002431462611535200302060ustar00rootroot00000000000000  "https://ct.googleapis.com/testtube] [0Y0*H=*H=BȼKK=5{da뎙9=\FEάvH;~, ce%OC,ᑤ trillian_test (0`google-certificate-transparency-go-2308f62/trillian/migrillian/testdata/config.textproto000066400000000000000000000020241462611535200316540ustar00rootroot00000000000000migration_configs { config { source_uri: "https://ct.googleapis.com/testtube" public_key { der: "0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\303\310\274K\272\242\030K=5{\364d\221a\352\353\216\231\035\220\355\323\351\2579=\\\323F\221E\343\316\254vH;\321~,\nc\000e\215\365\256\216\214\307\021%OC,\235\031\241\341\221\244\263\376" } log_backend_name: "trillian_test" log_id: 100500 batch_size: 512 is_continuous: true identity_function: SHA256_CERT_DATA } config { source_uri: "https://ct.googleapis.com/testtube2" public_key { der: "0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\303\310\274K\272\242\030K=5{\364d\221a\352\353\216\231\035\220\355\323\351\2579=\\\323F\221E\343\316\254vH;\321~,\nc\000e\215\365\256\216\214\307\021%OC,\235\031\241\341\221\244\263\376" } log_backend_name: "trillian_test2" log_id: 200500 batch_size: 512 is_continuous: true identity_function: SHA256_CERT_DATA } } google-certificate-transparency-go-2308f62/trillian/migrillian/testdata/not-config.textproto000066400000000000000000000006461462611535200324620ustar00rootroot00000000000000source_uri: "https://ct.googleapis.com/testtube" public_key { der: "0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\303\310\274K\272\242\030K=5{\364d\221a\352\353\216\231\035\220\355\323\351\2579=\\\323F\221E\343\316\254vH;\321~,\nc\000e\215\365\256\216\214\307\021%OC,\235\031\241\341\221\244\263\376" } log_backend_name: "backend_name" log_id: 100500 batch_size: 512 is_continuous: true google-certificate-transparency-go-2308f62/trillian/mockclient/000077500000000000000000000000001462611535200246075ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/mockclient/gen.go000066400000000000000000000014771462611535200257200ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package mockclient provides a mockable version of the Trillian log client API. package mockclient //go:generate mockgen -package mockclient -destination mock_log_client.go github.com/google/trillian TrillianLogClient google-certificate-transparency-go-2308f62/trillian/mockclient/mock_log_client.go000066400000000000000000000223571462611535200302770ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: github.com/google/trillian (interfaces: TrillianLogClient) // Package mockclient is a generated GoMock package. package mockclient import ( context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" trillian "github.com/google/trillian" grpc "google.golang.org/grpc" ) // MockTrillianLogClient is a mock of TrillianLogClient interface. type MockTrillianLogClient struct { ctrl *gomock.Controller recorder *MockTrillianLogClientMockRecorder } // MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. type MockTrillianLogClientMockRecorder struct { mock *MockTrillianLogClient } // NewMockTrillianLogClient creates a new mock instance. func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { mock := &MockTrillianLogClient{ctrl: ctrl} mock.recorder = &MockTrillianLogClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { return m.recorder } // AddSequencedLeaves mocks base method. func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // AddSequencedLeaves indicates an expected call of AddSequencedLeaves. func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) } // GetConsistencyProof mocks base method. func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetConsistencyProof indicates an expected call of GetConsistencyProof. func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) } // GetEntryAndProof mocks base method. func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetEntryAndProof indicates an expected call of GetEntryAndProof. func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) } // GetInclusionProof mocks base method. func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInclusionProof indicates an expected call of GetInclusionProof. func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) } // GetInclusionProofByHash mocks base method. func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) } // GetLatestSignedLogRoot mocks base method. func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) } // GetLeavesByRange mocks base method. func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLeavesByRange indicates an expected call of GetLeavesByRange. func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) } // InitLog mocks base method. func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "InitLog", varargs...) ret0, _ := ret[0].(*trillian.InitLogResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // InitLog indicates an expected call of InitLog. func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) } // QueueLeaf mocks base method. func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "QueueLeaf", varargs...) ret0, _ := ret[0].(*trillian.QueueLeafResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // QueueLeaf indicates an expected call of QueueLeaf. func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) } google-certificate-transparency-go-2308f62/trillian/testdata/000077500000000000000000000000001462611535200242705ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/testdata/Makefile000066400000000000000000000160361462611535200257360ustar00rootroot00000000000000all: pubkeys ca leaves # The following private keys are never regenerated. SERVER_PRIVKEYS=ct-http-server.privkey.pem log-rpc-server.privkey.pem # Corresponding passwords: CT_HTTP_PWD=dirk LOG_RPC_PWD=towel MAP_RPC_PWD=towel # Server public keys are derived from the corresponding private keys. SERVER_PUBKEYS=$(subst .privkey,.pubkey,$(SERVER_PRIVKEYS)) # Build public keys from private keys pubkeys: $(SERVER_PUBKEYS) log-rpc-server.pubkey.pem: log-rpc-server.privkey.pem openssl ec -in $< -pubout -out $@ -passin pass:$(LOG_RPC_PWD) ct-http-server.pubkey.pem: ct-http-server.privkey.pem openssl ec -in $< -pubout -out $@ -passin pass:$(CT_HTTP_PWD) # We use a fake CA as a trust root for CT tests. This is its private key. CA_PRIVKEY=fake-ca.privkey.pem CA_PWD=gently # We also have an intermediate CA, with private key: INT_CA_PRIVKEY=int-ca.privkey.pem INT_CA_PWD=babelfish # All the leaf certificates share a private key: LEAF_PRIVKEY=leaf.privkey.pem LEAF_PWD=liff ca: fake-ca.cert int-ca.cert # Fake Root CA fake-ca.cert: $(CA_PRIVKEY) fake-ca.cfg openssl req -new -x509 -config fake-ca.cfg -set_serial 0x0406cafe -days 3650 -extensions v3_ca -inform pem -key $(CA_PRIVKEY) -passin pass:$(CA_PWD) -out $@ show-ca: fake-ca.cert openssl x509 -inform pem -in $< -text -noout # Fake Intermediate CA int-ca.csr.pem: $(INT_CA_PRIVKEY) int-ca.cfg openssl req -new -sha256 -config int-ca.cfg -key $(INT_CA_PRIVKEY) -passin pass:$(INT_CA_PWD) -out $@ show-int-csr: int-ca.csr.pem openssl req -in $< -text -noout int-ca.cert: int-ca.csr.pem $(CA_PRIVKEY) fake-ca.cert openssl x509 -req -in int-ca.csr.pem -sha256 -extfile fake-ca.cfg -extensions v3_int_ca -CA fake-ca.cert -CAkey $(CA_PRIVKEY) -passin pass:$(CA_PWD) -set_serial 0x42424242 -days 3600 -out $@ show-int-ca: int-ca.cert openssl x509 -inform pem -in $< -text -noout # Leaf Certificates LEAF_CERTS=leaf00.cert leaf01.cert leaf02.cert leaf03.cert leaf04.cert leaf05.cert leaf06.cert leaf07.cert leaf08.cert leaf09.cert leaf10.cert \ leaf11.cert leaf12.cert leaf13.cert leaf14.cert leaf15.cert leaf16.cert leaf17.cert leaf18.cert leaf19.cert leaf20.cert LEAF_CSRS=$(subst .cert,.csr.pem,$(LEAF_CERTS)) LEAF_CHAINS=$(subst .cert,.chain,$(LEAF_CERTS)) leaves: $(LEAF_CERTS) $(LEAF_CHAINS) leaf%.csr.pem: $(LEAF_PRIVKEY) openssl req -new -sha256 -key $(LEAF_PRIVKEY) -passin pass:$(LEAF_PWD) -subj "/C=GB/ST=London/O=Google/OU=Eng/CN=$@" -out $@ show-leaf%-csr: leaf%.csr.pem openssl req -in $< -text -noout leaf%.cert: leaf%.csr.pem int-ca.cert openssl x509 -req -in $< -sha256 -extfile int-ca.cfg -extensions v3_user -CA int-ca.cert -CAkey $(INT_CA_PRIVKEY) -passin pass:$(INT_CA_PWD) -set_serial 0xdeadbeef -days 2600 -out $@ show-leaf%: leaf%.cert openssl x509 -inform pem -in $< -text -noout leaf%.chain: leaf%.cert int-ca.cert cat $^ > $@ # Special case: include the root too leaf02.chain: leaf02.cert int-ca.cert fake-ca.cert cat $^ > $@ # Special case: add serverAuth EKU leaf00.cert: leaf00.csr.pem leaf.privkey.pem int-ca.cert openssl x509 -req -in $< -sha256 -extfile int-ca.cfg -extensions v3_user_serverAuth -CA int-ca.cert -CAkey $(INT_CA_PRIVKEY) -passin pass:$(INT_CA_PWD) -set_serial 0xdeadbeef -days 2600 -out $@ # Special case: add an unknown EKU leaf03.cert: leaf03.csr.pem leaf.privkey.pem int-ca.cert openssl x509 -req -in $< -sha256 -extfile int-ca.cfg -extensions v3_user_plus -CA int-ca.cert -CAkey $(INT_CA_PRIVKEY) -passin pass:$(INT_CA_PWD) -set_serial 0xdeadbeef -days 2600 -out $@ # Pair of intermediate CAs for a longer chain INT_CA_1_PRIVKEY=int-ca-1.privkey.pem INT_CA_2_PRIVKEY=int-ca-2.privkey.pem INT_CA_1_PWD=vogon INT_CA_2_PWD=vogon # Fake Intermediate CA 1 int-ca-1.csr.pem: $(INT_CA_PRIVKEY) int-ca.cfg openssl req -new -sha256 -config int-ca.cfg -key $(INT_CA_1_PRIVKEY) -passin pass:$(INT_CA_1_PWD) -out $@ int-ca-1.cert: int-ca-1.csr.pem $(CA_PRIVKEY) fake-ca.cert openssl x509 -req -in int-ca-1.csr.pem -sha256 -extfile fake-ca.cfg -extensions v3_int_ca_pair -CA fake-ca.cert -CAkey $(CA_PRIVKEY) -passin pass:$(CA_PWD) -set_serial 0x01010101 -days 3600 -out $@ # Fake 2nd-level Intermediate CA 2 int-ca-2.csr.pem: int-ca-2.privkey.pem int-ca-2.cfg openssl req -new -sha256 -config int-ca-2.cfg -key $(INT_CA_2_PRIVKEY) -passin pass:$(INT_CA_2_PWD) -out $@ int-ca-2.cert: int-ca-2.csr.pem $(INT_CA_PRIVKEY) int-ca-1.cert openssl x509 -req -in int-ca-2.csr.pem -sha256 -extfile fake-ca.cfg -extensions v3_int_ca_pair -CA int-ca-1.cert -CAkey $(INT_CA_1_PRIVKEY) -passin pass:$(INT_CA_1_PWD) -set_serial 0x12121212 -days 3600 -out $@ # Length 4 chain (to allow mis-ordering tests). subleaf.csr.pem: $(LEAF_PRIVKEY) openssl req -new -sha256 -key $(LEAF_PRIVKEY) -passin pass:$(LEAF_PWD) -subj "/C=GB/ST=London/O=Google/OU=Eng/CN=$@" -out $@ subleaf.cert: subleaf.csr.pem int-ca-2.cert openssl x509 -req -in $< -sha256 -extfile int-ca-2.cfg -extensions v3_user -CA int-ca-2.cert -CAkey $(INT_CA_2_PRIVKEY) -passin pass:$(INT_CA_2_PWD) -set_serial 0xdeadbeef -days 2600 -out $@ subleaf.chain: subleaf.cert int-ca-2.cert int-ca-1.cert fake-ca.cert cat $^ > $@ subleaf.misordered.chain: subleaf.cert int-ca-1.cert int-ca-2.cert fake-ca.cert cat $^ > $@ # Length 4 chain (to allow mis-ordering tests) for pre-cert. subleaf-pre.cert: subleaf.csr.pem int-ca-2.cert openssl x509 -req -in $< -sha256 -extfile int-ca-2.cfg -extensions v3_user_pre -CA int-ca-2.cert -CAkey $(INT_CA_2_PRIVKEY) -passin pass:$(INT_CA_2_PWD) -set_serial 0xdeadbeef -days 2600 -out $@ subleaf-pre.chain: subleaf-pre.cert int-ca-2.cert int-ca-1.cert fake-ca.cert cat $^ > $@ subleaf-pre.misordered.chain: subleaf-pre.cert int-ca-1.cert int-ca-2.cert fake-ca.cert cat $^ > $@ # clean removes things that regenerate exactly the same. clean: rm -f $(SERVER_PUBKEYS) # distclean removes things that regenerate with changes (e.g. timestamped, randomized). distclean: clean rm -f $(SERVER_PUBKEYS) fake-ca.cert int-ca.cert int-ca.csr.pem rm -f $(LEAF_CERTS) $(LEAF_CSRS) $(LEAF_CHAINS) # We also use a second fake CA as a trust root for CT tests. This is its private key. CA_1_PRIVKEY=fake-ca-1.privkey.pem CA_1_PWD=ahenny # Corresponding Leaf certificates. LEAF_1_PRIVKEY=leaf-1.privkey.pem LEAF_1_PWD=louth # Fake Root CA 1 fake-ca-1.cert: $(CA_1_PRIVKEY) fake-ca.cfg openssl req -new -x509 -config fake-ca.cfg -set_serial 0x0406efac -days 3650 -extensions v3_ca1 -inform pem -key $(CA_1_PRIVKEY) -passin pass:$(CA_1_PWD) -out $@ leaf-1.csr.pem: $(LEAF_1_PRIVKEY) openssl req -new -sha256 -key $(LEAF_1_PRIVKEY) -passin pass:$(LEAF_1_PWD) -subj "/C=GB/ST=London/O=Google/OU=Eng/CN=$@" -out $@ leaf-1.cert: leaf-1.csr.pem fake-ca-1.cert openssl x509 -req -in $< -sha256 -extfile fake-ca.cfg -extensions v3_user -CA fake-ca-1.cert -CAkey $(CA_1_PRIVKEY) -passin pass:$(CA_1_PWD) -set_serial 0xdeadbeaf -days 2600 -out $@ # Short chain on CA 1 leaf-1.chain: leaf-1.cert fake-ca-1.cert cat $^ > $@ # The newkey target creates a fresh private key; should never be needed. newkey: fresh.privkey.pem fresh.privkey.pem: openssl ecparam -genkey -name prime256v1 -noout -out $@.unencrypted openssl ec -in $@.unencrypted -out $@ -des # Prompts for password rm -f $@.unencrypted google-certificate-transparency-go-2308f62/trillian/testdata/README.md000066400000000000000000000020231462611535200255440ustar00rootroot00000000000000Directory Contents ================== This directory holds data files for testing; **under no circumstances should these files be used in production**. Some of the data files are generated from other data files; the [`Makefile`](Makefile) has commands for doing this, but the generated files are checked in for convenience. Trillian Server Keys -------------------- Files of the form `*-server.privkey.pem` hold private keys for Trillian servers, with the corresponding public keys stored in `*-server.pubkey.pem`. The following sets of files are available: - `ct-http-server`: CT Log personality; password `dirk`. X.509 Certificates ------------------ A fake certificate authority is used when testing a Certificate Transparency personality for Trillian, which uses: - `fake-ca.privkey.pem`: Private key for the CA; password `gently`. - `fake-ca.cert`: CA certificate. There is also an intermediate certificate authority, which uses: - `int-ca.privkey.pem`: Private key; password `babelfish`. - `int-ca.cert`: Certificate. google-certificate-transparency-go-2308f62/trillian/testdata/ct-http-server.privkey.pem000066400000000000000000000006501462611535200313530ustar00rootroot00000000000000# This key is for test purposes only and must not be used for # production under any circumstances. The key password is # 'dirk' -----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,46EB0095C2BEFB56 PmIaXRJaKvO09uZuKWqTqotLbH1lWVghuvju4s9BT1AaVmS8BtgPyVTQXwUTXE4Y bEAk2mWZJySTBUXAUg/NNQjz75TfId96IcFfv/PI0G3CZv4Nvf4IjiVYO3QrCHqr JZzCxc82/wZmtEntvtof9fXgVoWv1Bk87WE65T5cl0U= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/ct-http-server.pubkey.pem000066400000000000000000000002621462611535200311600ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEB/hRr6qMVoOQMbeA49Ya9y82BnHs 3Tu+fjZvDRwcYAt/9Z//5SRJNFbySxBfvwgf+Q7PNbWKioswClS3vx1NuQ== -----END PUBLIC KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/fake-ca-1.cert000066400000000000000000000014261462611535200265770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGzCCAcGgAwIBAgIEBAbvrDAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTkwMjA2MTEwNjM1WhcNMjkwMjAzMTEwNjM1WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPsk2T/AE8cEsL1xq3uA4i radTlnaEDAerb1uzm8jSD5zwAlCamQrHhTWJyNK3VD9R45gWLN6NocEu0XHckrRh o0cwRTANBgNVHQ4EBgQEERITFDAPBgNVHSMECDAGgAQREhMUMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNIADBFAiEA3dso y/LIFlyoyJf60rGNiGoHsErzcWymtfYExZmAFugCIFKHKOPEJHdIF6hlx5m0xSoB IBsTC/fk9MA4jptRtb81 -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/fake-ca-1.privkey.pem000066400000000000000000000004461462611535200301140ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,32133C9B65F298E5 JrhWhvBILlS9Tv1qS9705eXRR/1EVN8W2tnxehhq37sXKjqMtMs7lQqeRhEThLKU 3YGpzeb5Cn1V+r9j8541XKvmTbkZzPDOXWye41EeMtQgnQAe3Jhmo5g9e5YXbsaE DAwYum3ekI5kDbutgiNLTxiEUfHKKfehfVN/RlpD7N0= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/fake-ca.cert000066400000000000000000000014321462611535200264360ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/fake-ca.cfg000066400000000000000000000036521462611535200262460ustar00rootroot00000000000000# OpenSSL configuration file. [ req ] # Options for the `req` tool (`man req`). default_bits = 2048 distinguished_name = req_distinguished_name prompt = no # SHA-1 is deprecated, so use SHA-2 instead. default_md = sha256 # Extension to add when the -x509 option is used. x509_extensions = v3_ca # Try to force use of PrintableString throughout string_mask = pkix [ req_distinguished_name ] C=GB ST=London L=London O=Google OU=Eng CN=FakeCertificateAuthority [ v3_ca ] subjectKeyIdentifier = 01020304 authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:10 keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly [ v3_int_ca ] subjectKeyIdentifier = 05060708 authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:0 keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly extendedKeyUsage = serverAuth,clientAuth [ v3_int_ca_pair ] subjectKeyIdentifier = 0a0b0c0d authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly extendedKeyUsage = serverAuth,clientAuth [ v3_ca1 ] subjectKeyIdentifier = 11121314 authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:10 keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly [ v3_user ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, encipherOnly, decipherOnly google-certificate-transparency-go-2308f62/trillian/testdata/fake-ca.privkey.pem000066400000000000000000000004461462611535200277560ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,53C67AA311B73ED1 UgdxD/ThmtBjRklM1aU8qxCM3yvVYrl4NzudKE4NCQjYR7u0OhE3OD6XShPghtRU RM8ekP81zIPEUS6H/V5ysbwDtwibQ4/kw85lOVEoNdqDCMV++M3aEFiV8RA0hj+q x8ANosBgtEVsbC1LwOk0/yrvUFucrp5FuHSxJ3//9iI= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-1.cert000066400000000000000000000014761462611535200264700ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-1.csr.pem000066400000000000000000000007451462611535200271000ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBLDCB1AIBADByMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYD VQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2dsZTEMMAoGA1UECxMDRW5nMSIwIAYD VQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZI zj0DAQcDQgAEEgjivMuD8A9GCkFMrYcPxWS7aFP5VKV4Bm46cibTzCMuQZVBgSxg kZ3nMbPFo0fif2f84yrcum4ntUHVX3uVbKAAMAoGCCqGSM49BAMCA0cAMEQCIG9M 1Gegx2HIk7BAvCymFRBX/acx2qCXXDFFc5Zybd+xAiAefEVgsXBQj7in+jWPvhlc FA4JfhUjRLqBjauStjSrlQ== -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-1.privkey.pem000066400000000000000000000004461462611535200300000ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,ABC68602E3BCAD17 +u0+kcMeymXB6Ypd8NOSv235JtE/RpFqw08sXss7N2vk5BBIEXMor2Qq/UGZG3Ij ohgf0tTAUjTOqaCfuWiCgLBQ9ZtQ3Qtunl0KG6gM8vhnwUwipAQ76HSrWFc6uCMf UTdXliMNKyUHG09Lemhn5s6PknBJW3o1eQLEYIF3N1g= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-2.cert000066400000000000000000000015021462611535200264570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y aXR5MB4XDTE4MDQwNjExMDEwMVoXDTI4MDIxMzExMDEwMVowdTELMAkGA1UEBhMC R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBRmlMEQHyEenjH9eft8K/9Aj0s Q5TMzI8domdMGSTktwIhAKIJWSuJsn9C0QXKFAxlOVJsjlgIIuLuyUvrrbKKlz45 -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-2.cfg000066400000000000000000000012041462611535200262600ustar00rootroot00000000000000# OpenSSL configuration file. [ req ] # Options for the `req` tool (`man req`). default_bits = 2048 distinguished_name = req_distinguished_name prompt = no # SHA-1 is deprecated, so use SHA-2 instead. default_md = sha256 # Try to force use of PrintableString throughout string_mask = pkix [ req_distinguished_name ] C=GB ST=London L=London O=Google OU=Eng CN=FakeSubIntermediateAuthority [ v3_user ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, encipherOnly, decipherOnlygoogle-certificate-transparency-go-2308f62/trillian/testdata/int-ca-2.csr.pem000066400000000000000000000007511462611535200270760ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBLzCB1wIBADB1MQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYD VQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2dsZTEMMAoGA1UECxMDRW5nMSUwIwYD VQQDExxGYWtlU3ViSW50ZXJtZWRpYXRlQXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAEZWJNtdi2l1FnR/hCF0ejf0L2zzMK1YCZayce5vd3Q6WEnFrN VXW4dOAkYMTsS59/i/IfQPaPvGeOyZUYZ6dp86AAMAoGCCqGSM49BAMCA0cAMEQC IFUR3bw8UeqK5qXREjYP7TUJCp/SzdgbBxEPDbd7Qml1AiBfAJ89rj8v3LvP+Eh5 94aAVeFl+bPqVP9o3YRSjDKBfw== -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca-2.privkey.pem000066400000000000000000000004461462611535200300010ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,FD82DE23B8036583 3SXK+fj8xTKc+FO14XX11tEfZAJfgu/Cbm3PpTQ9PlbVrahxDvCGjN8Lps4FNYnT UyfzOSbj/WD+6ayS9UkB6X4pHeEmCakvnZv/4fagSrG5YbVcil1fYTJcttUW7MKD pZwKWMoZMIRmF5ZH/hSZg/O8WjbHJW8yWmicJPAL+nc= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca.cert000066400000000000000000000015021462611535200263200ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca.cfg000066400000000000000000000022301462611535200261210ustar00rootroot00000000000000# OpenSSL configuration file. [ req ] # Options for the `req` tool (`man req`). default_bits = 2048 distinguished_name = req_distinguished_name prompt = no # SHA-1 is deprecated, so use SHA-2 instead. default_md = sha256 # Try to force use of PrintableString throughout string_mask = pkix [ req_distinguished_name ] C=GB ST=London L=London O=Google OU=Eng CN=FakeIntermediateAuthority [ v3_user ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, encipherOnly, decipherOnly [ v3_user_serverAuth ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, encipherOnly, decipherOnly extendedKeyUsage = serverAuth [ v3_user_plus ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, encipherOnly, decipherOnly extendedKeyUsage = serverAuth,2.16.840.1.113741.1.2.3 google-certificate-transparency-go-2308f62/trillian/testdata/int-ca.csr.pem000066400000000000000000000007451462611535200267420ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBLDCB1AIBADByMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYD VQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2dsZTEMMAoGA1UECxMDRW5nMSIwIAYD VQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZI zj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkCy91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYy qtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9dXaAAMAoGCCqGSM49BAMCA0cAMEQCIDm4 gi/qixQxTyejb+cnBXYETUolQ3GH06wtBWXiH5avAiAJUKpzRfaIlZE9u0kP/jEX EyrYxxWrnfeoh8cvjZxgOA== -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/int-ca.privkey.pem000066400000000000000000000004461462611535200276420ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,DF34CBD047924DB7 cqDEU98jWJRczxD82DfFqxHyFinnoWAHP7O9WFHPkC/aC9OTMZN6gRofaB23PlZa nJhrv4hbLcUM7N0juxuOvjwddXMrx2e7RSAjAsMMSggkZtmbQE9f2CDJoxi2fp1e m3cWJVGIYVthMGsNPFCyLx4quid5CIovjFm4GDDAbRg= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/keys.go000066400000000000000000000030361462611535200255740ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testdata holds files, data and code for testing. // KEYS IN THIS FILE ARE ONLY FOR TESTING. They must not be used by production code. package testdata // DemoPrivateKeyPass is the password for DemoPrivateKey const DemoPrivateKeyPass string = "towel" // DemoPrivateKey is the private key itself; must only be used for testing purposes const DemoPrivateKey string = ` -----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,B71ECAB011EB4E8F +6cz455aVRHFX5UsxplyGvFXMcmuMH0My/nOWNmYCL+bX2PnHdsv3dRgpgPRHTWt IPI6kVHv0g2uV5zW8nRqacmikBFA40CIKp0SjRmi1CtfchzuqXQ3q40rFwCjeuiz t48+aoeFsfU6NnL5sP8mbFlPze+o7lovgAWEqHEcebU= -----END EC PRIVATE KEY-----` // DemoPublicKey is the public key that corresponds to DemoPrivateKey. const DemoPublicKey string = ` -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsAVg3YB0tOFf3DdC2YHPL2WiuCNR 1iywqGjjtu2dAdWktWqgRO4NTqPJXUggSQL3nvOupHB4WZFZ4j3QhtmWRg== -----END PUBLIC KEY-----` google-certificate-transparency-go-2308f62/trillian/testdata/leaf-1.cert000066400000000000000000000013651462611535200262210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICATCCAaagAwIBAgIFAN6tvq8wCgYIKoZIzj0EAwIwcTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRlQXV0aG9y aXR5MB4XDTE5MDIwNjExMDYzNVoXDTI2MDMyMTExMDYzNVowWTELMAkGA1UEBhMC R0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UECgwGR29vZ2xlMQwwCgYDVQQLDANF bmcxGjAYBgNVBAMMEXN1YmxlYWYtMS5jc3IucGVtMFkwEwYHKoZIzj0CAQYIKoZI zj0DAQcDQgAEci/oCF8zFWREx0KtnZU62+hr0oQIGjxZJDinBcVU5FarahtoD9Vx 7alPX2zswGrcFyLlrhgXSV3eCklP/JtyHKNDMEEwHQYDVR0OBBYEFP/w+hpFGIpB SDIESzCgONMj95tAMA8GA1UdIwQIMAaABBESExQwDwYDVR0PAQH/BAUDAwf5gDAK BggqhkjOPQQDAgNJADBGAiEAxJBWEtH5HhW9DFKkUFgqYFSNJvNAVV16DS1RyDG4 pOMCIQDzH7VTNH9CsCitPl79CS01UV8ZM2lv4+bVHwWYjvnabw== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf-1.chain000066400000000000000000000030131462611535200263360ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICATCCAaagAwIBAgIFAN6tvq8wCgYIKoZIzj0EAwIwcTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRlQXV0aG9y aXR5MB4XDTE5MDIwNjExMDYzNVoXDTI2MDMyMTExMDYzNVowWTELMAkGA1UEBhMC R0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UECgwGR29vZ2xlMQwwCgYDVQQLDANF bmcxGjAYBgNVBAMMEXN1YmxlYWYtMS5jc3IucGVtMFkwEwYHKoZIzj0CAQYIKoZI zj0DAQcDQgAEci/oCF8zFWREx0KtnZU62+hr0oQIGjxZJDinBcVU5FarahtoD9Vx 7alPX2zswGrcFyLlrhgXSV3eCklP/JtyHKNDMEEwHQYDVR0OBBYEFP/w+hpFGIpB SDIESzCgONMj95tAMA8GA1UdIwQIMAaABBESExQwDwYDVR0PAQH/BAUDAwf5gDAK BggqhkjOPQQDAgNJADBGAiEAxJBWEtH5HhW9DFKkUFgqYFSNJvNAVV16DS1RyDG4 pOMCIQDzH7VTNH9CsCitPl79CS01UV8ZM2lv4+bVHwWYjvnabw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICGzCCAcGgAwIBAgIEBAbvrDAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTkwMjA2MTEwNjM1WhcNMjkwMjAzMTEwNjM1WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPsk2T/AE8cEsL1xq3uA4i radTlnaEDAerb1uzm8jSD5zwAlCamQrHhTWJyNK3VD9R45gWLN6NocEu0XHckrRh o0cwRTANBgNVHQ4EBgQEERITFDAPBgNVHSMECDAGgAQREhMUMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNIADBFAiEA3dso y/LIFlyoyJf60rGNiGoHsErzcWymtfYExZmAFugCIFKHKOPEJHdIF6hlx5m0xSoB IBsTC/fk9MA4jptRtb81 -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf-1.csr.pem000066400000000000000000000007041462611535200266270ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBFDCBuwIBADBZMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYD VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEaMBgGA1UEAwwRc3VibGVhZi0xLmNz ci5wZW0wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARyL+gIXzMVZETHQq2dlTrb 6GvShAgaPFkkOKcFxVTkVqtqG2gP1XHtqU9fbOzAatwXIuWuGBdJXd4KSU/8m3Ic oAAwCgYIKoZIzj0EAwIDSAAwRQIhAOh2szT++YFUQmmH8tI0bBfCQ1mk3WpDhQ7V 91fQGuebAiAVXq9L6/KrhTNuj+h2xzvEVcyZTA3qbrZh0yjkSIfkJg== -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf-1.privkey.pem000066400000000000000000000004461462611535200275340ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,EA49B21F81CDE28D TUmErEJlHvGqAMnQPpFQ4ncxUwzs2fx6u3KJOAE4Ym2Y0Tsw5B603bsv9B025yMA m6fqJNMOSAhZusiD2IPXss9bdnXnY4tspbVDEg7e+OFbU0fC/iEo3NV9Ffsb2Mie Rm6eV+CGaMZT69RGidUAsklQGbMXRJdm/4dX2pBs0YE= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf.privkey.pem000066400000000000000000000004461462611535200273760ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,E0F9D31B363F30DB ebmTvJR570NF7n/ICJ1MuJZMjhYWxFh7iwVxDuOP7XTYrnDGz3lhhcxnFQCGThvC lbhJew1WHumQNsQ7x6+NM9cGTBD0P581gQcL23+AlwFhxbPgdgJ8lV7052QuYpr/ T8SUbrq0wMfMOWBC0hV7pXqMt+epsoS3m5mce+RpTrA= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf00.cert000066400000000000000000000014121462611535200262140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICEjCCAbmgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0yMjAzMTgxNTM5MjJaFw0yOTA0MzAxNTM5MjJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjWDBWMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwEwYD VR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZIzj0EAwIDRwAwRAIgM8Z4cFYwC7g0chnt rfPJtx8C/9kl8CFj2/7iJDu+jpUCIAbmfYcT5i6dID+CsQLI5PpvPf6yoOgvX3Sq dgsjXhzl -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf00.chain000066400000000000000000000031141462611535200263420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICEjCCAbmgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0yMjAzMTgxNTM5MjJaFw0yOTA0MzAxNTM5MjJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjWDBWMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwEwYD VR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZIzj0EAwIDRwAwRAIgM8Z4cFYwC7g0chnt rfPJtx8C/9kl8CFj2/7iJDu+jpUCIAbmfYcT5i6dID+CsQLI5PpvPf6yoOgvX3Sq dgsjXhzl -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf01.cert000066400000000000000000000013611462611535200262200ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAKGCQyZfZNHGSMIUhoqolQKBAwXLNh65KYN0cymENFMI AiEAo3AAgLEY7QuGN9CCepRt+2TdLhR6SUQW7Aspwx+zhB4= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf01.chain000066400000000000000000000030631462611535200263460ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAKGCQyZfZNHGSMIUhoqolQKBAwXLNh65KYN0cymENFMI AiEAo3AAgLEY7QuGN9CCepRt+2TdLhR6SUQW7Aspwx+zhB4= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf02.cert000066400000000000000000000013611462611535200262210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDIuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAJS01L7uT2i9db1TFQoQ8hNBceR2zK1zUj5UHU1kc6nm AiB3DRlixsVBvz6sKR7SoRbF+vcfKDFQqQcfitauAixRCQ== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf02.chain000066400000000000000000000045151462611535200263520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDIuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAJS01L7uT2i9db1TFQoQ8hNBceR2zK1zUj5UHU1kc6nm AiB3DRlixsVBvz6sKR7SoRbF+vcfKDFQqQcfitauAixRCQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf03.cert000066400000000000000000000014361462611535200262250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICHzCCAcWgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDMuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjZDBiMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwHwYD VR0lBBgwFgYIKwYBBQUHAwEGCmCGSAGG+E0BAgMwCgYIKoZIzj0EAwIDSAAwRQIh APCbBOmMlpYcYqXyOWeJkBuHOogZlXMi1ShqYF3iIerCAiAHq1L/nFNkDV5QZ3NY RDVpthjzrd8qG10jJbyzubasnQ== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf03.chain000066400000000000000000000031401462611535200263440ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICHzCCAcWgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDMuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjZDBiMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwHwYD VR0lBBgwFgYIKwYBBQUHAwEGCmCGSAGG+E0BAgMwCgYIKoZIzj0EAwIDSAAwRQIh APCbBOmMlpYcYqXyOWeJkBuHOogZlXMi1ShqYF3iIerCAiAHq1L/nFNkDV5QZ3NY RDVpthjzrd8qG10jJbyzubasnQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf03.csr.pem000066400000000000000000000007001462611535200266300ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBEjCBuAIBADBWMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYD VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEXMBUGA1UEAwwObGVhZjAzLmNzci5w ZW0wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCK aaAmXJ4vbrhrI2yE4UY6mDaCRKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIusoAAw CgYIKoZIzj0EAwIDSQAwRgIhANsf3eCOoL0ZHfuuw7IADqO+X+tSydfY0MkxJO7C ALndAiEAyuCSkAUewrVcLCaUnMlVp9jq8QOvdrcI5SC7WXrqodE= -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf04.cert000066400000000000000000000013611462611535200262230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDQuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAMcSQgjY7d4XSEUAOqHxyJ+mR87TUSzsVuqIDeugWg3B AiATRbp6mplc05GBh9D9dm8pZTZQoz3iDx8GYzAJ4wAWqw== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf04.chain000066400000000000000000000030631462611535200263510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDQuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAMcSQgjY7d4XSEUAOqHxyJ+mR87TUSzsVuqIDeugWg3B AiATRbp6mplc05GBh9D9dm8pZTZQoz3iDx8GYzAJ4wAWqw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf05.cert000066400000000000000000000013611462611535200262240ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDUuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgGeQiqGYL1PIW3l6Wf/yDpufD85ZKvV10j35W0a7o7rAC IQDHGS5HK8KFB0DVqhb9y+ICS2sxlJQmABcA+C+V7L5b/A== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf05.chain000066400000000000000000000030631462611535200263520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDUuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgGeQiqGYL1PIW3l6Wf/yDpufD85ZKvV10j35W0a7o7rAC IQDHGS5HK8KFB0DVqhb9y+ICS2sxlJQmABcA+C+V7L5b/A== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf06.cert000066400000000000000000000013611462611535200262250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDYuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgSOuhu0ipS0GF1+9LI6VvG3Fz0E2nraXrGcmHfiV4tvgC IQCUWxHVZG0goP2P3Dx7/R8vN5mbDKggBQwti3OH8h8qqw== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf06.chain000066400000000000000000000030631462611535200263530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDYuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgSOuhu0ipS0GF1+9LI6VvG3Fz0E2nraXrGcmHfiV4tvgC IQCUWxHVZG0goP2P3Dx7/R8vN5mbDKggBQwti3OH8h8qqw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf07.cert000066400000000000000000000013551462611535200262310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDcuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgOAXF6OFXr1JxhED7uGuJ4TGC3vaH1J5ERUu6r1ObLmcC IFZHTysGgFKMRaHkqLWOWdO9H3CGTVYFcc4sqS2IhAYU -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf07.chain000066400000000000000000000030571462611535200263570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDcuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgOAXF6OFXr1JxhED7uGuJ4TGC3vaH1J5ERUu6r1ObLmcC IFZHTysGgFKMRaHkqLWOWdO9H3CGTVYFcc4sqS2IhAYU -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf08.cert000066400000000000000000000013611462611535200262270ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDguY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAOC6yltv0ZBDfv0qGKY/GL2B8NnkXhrCzh0xXwOkAI0b AiEA07435licV8nDHWRbQ4GHbjAiBrjh4oNw3kAuJmAKUYM= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf08.chain000066400000000000000000000030631462611535200263550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDguY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAOC6yltv0ZBDfv0qGKY/GL2B8NnkXhrCzh0xXwOkAI0b AiEA07435licV8nDHWRbQ4GHbjAiBrjh4oNw3kAuJmAKUYM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf09.cert000066400000000000000000000013611462611535200262300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDkuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAJzE9MnYpbtwo5rxdFeDKHnSxmgq0cJzY7lrWdpVNpQQ AiEA22347s+5ZLW7g2CTVwyugey/rKyuRrowdJ7E+1K+pR0= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf09.chain000066400000000000000000000030631462611535200263560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMDkuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAJzE9MnYpbtwo5rxdFeDKHnSxmgq0cJzY7lrWdpVNpQQ AiEA22347s+5ZLW7g2CTVwyugey/rKyuRrowdJ7E+1K+pR0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf10.cert000066400000000000000000000013611462611535200262200ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhALkyDQdm9doJ+xHntvpHm9wXGCr4EXepe/VmZVuZamdm AiEAhfPX1K3npu0fPtDeeD9YxKeMijA+k7VzZcvFPmDDUwE= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf10.chain000066400000000000000000000030631462611535200263460ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhALkyDQdm9doJ+xHntvpHm9wXGCr4EXepe/VmZVuZamdm AiEAhfPX1K3npu0fPtDeeD9YxKeMijA+k7VzZcvFPmDDUwE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf11.cert000066400000000000000000000013611462611535200262210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgIGwGIBya1hSoRrz1IIm0H/d3ZBfwfFnTw1lenjNaR/4C IQCfaJHxcoX0nFr9TVmbOeMwQI8Yp8KlbSqfX8mnURfG4g== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf11.chain000066400000000000000000000030631462611535200263470ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgIGwGIBya1hSoRrz1IIm0H/d3ZBfwfFnTw1lenjNaR/4C IQCfaJHxcoX0nFr9TVmbOeMwQI8Yp8KlbSqfX8mnURfG4g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf12.cert000066400000000000000000000013611462611535200262220ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTIuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhALmXplDrBhBbQwBwCLcfJgG4HkY7vJOHeRiGn1YZvj84 AiEAtd49YYnZt9SJ2s5IiRVMrm7F7Ev5CC3pDRuByE7XZIE= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf12.chain000066400000000000000000000030631462611535200263500ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTIuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhALmXplDrBhBbQwBwCLcfJgG4HkY7vJOHeRiGn1YZvj84 AiEAtd49YYnZt9SJ2s5IiRVMrm7F7Ev5CC3pDRuByE7XZIE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf13.cert000066400000000000000000000013611462611535200262230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTMuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAI+/24nz/ec4duyXMdyCJl2s6182d8wvIRHnDwz4O9lk AiAikGMF9kmWar74TIXLiEYxDmeblywl2FjuKAJKWbTNqw== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf13.chain000066400000000000000000000030631462611535200263510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTMuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAI+/24nz/ec4duyXMdyCJl2s6182d8wvIRHnDwz4O9lk AiAikGMF9kmWar74TIXLiEYxDmeblywl2FjuKAJKWbTNqw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf14.cert000066400000000000000000000013611462611535200262240ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTQuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAMzNYutP41uZfQwqcnow3YQ8rzF2b4+ZpuqCEZPArKPH AiEAyjJTsOSsnEfa3/nrLyVxEpJf3wJZoAdD6Q9Z7zKO9Vg= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf14.chain000066400000000000000000000030631462611535200263520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTQuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAMzNYutP41uZfQwqcnow3YQ8rzF2b4+ZpuqCEZPArKPH AiEAyjJTsOSsnEfa3/nrLyVxEpJf3wJZoAdD6Q9Z7zKO9Vg= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf15.cert000066400000000000000000000013611462611535200262250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTUuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAKQlqVAR/mafFxxwPcKOtkn+advTFETsLSe+Lx0Pea+M AiAcdpidUahLs1K/koAIsNglXPjHTln1ADt+mRHYAJQ9lQ== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf15.chain000066400000000000000000000030631462611535200263530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTFaFw0yNTA1MTExNzA1MTFaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTUuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhAKQlqVAR/mafFxxwPcKOtkn+advTFETsLSe+Lx0Pea+M AiAcdpidUahLs1K/koAIsNglXPjHTln1ADt+mRHYAJQ9lQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf16.cert000066400000000000000000000013551462611535200262310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTYuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgI2mF8Hg+L+vtE5QaO3kT7qZyNBZ4IAwUGCBnn6uYsvcC IA6IQDvZ/GnIjk8w9AtZ/iSvxQF85AYIrjaQCI0REGuZ -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf16.chain000066400000000000000000000030571462611535200263570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTYuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgI2mF8Hg+L+vtE5QaO3kT7qZyNBZ4IAwUGCBnn6uYsvcC IA6IQDvZ/GnIjk8w9AtZ/iSvxQF85AYIrjaQCI0REGuZ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf17.cert000066400000000000000000000013611462611535200262270ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTcuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAIjGpuT8KkA502bW2iwSiYUV3WOmMu+Gw5jGpslA4qIr AiEA/zPdnHxVeXYliDEJzTnyK/s3bWzVkPNU4Ig+RMxLq7s= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf17.chain000066400000000000000000000030631462611535200263550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/zCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTcuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSQAwRgIhAIjGpuT8KkA502bW2iwSiYUV3WOmMu+Gw5jGpslA4qIr AiEA/zPdnHxVeXYliDEJzTnyK/s3bWzVkPNU4Ig+RMxLq7s= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf18.cert000066400000000000000000000013551462611535200262330ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTguY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgWLbrtK6mlLpPo+Xq1GVuOvDvCo9W9y9vOVWR1gAKvmsC IGvidc4sm2xlhQiE+JWNEUXQRgCDggkQRxOv7GvfRJp4 -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf18.chain000066400000000000000000000030571462611535200263610ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/TCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTguY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDRwAwRAIgWLbrtK6mlLpPo+Xq1GVuOvDvCo9W9y9vOVWR1gAKvmsC IGvidc4sm2xlhQiE+JWNEUXQRgCDggkQRxOv7GvfRJp4 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf19.cert000066400000000000000000000013611462611535200262310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTkuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgbe4+drswhTziGRrCXuPy+fCFC/3qdnI5sXQn0oVACA0C IQDKX2WXQmoviW18tX+yvFtQXkHghhfFtdTBTG6fFdlKwA== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf19.chain000066400000000000000000000030631462611535200263570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMTkuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIgbe4+drswhTziGRrCXuPy+fCFC/3qdnI5sXQn0oVACA0C IQDKX2WXQmoviW18tX+yvFtQXkHghhfFtdTBTG6fFdlKwA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf20.cert000066400000000000000000000013611462611535200262210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMjAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhANk26w0GqWlSCprSVnll/VVbk+tMAuakfVPUs5M+ATuR AiBz/BTFl/v3Un4VavmmFl+pVz3pHYpPgJR/dXtobw3u4A== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/leaf20.chain000066400000000000000000000030631462611535200263470ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB/jCCAaSgAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv cml0eTAeFw0xODAzMjkxNzA1MTJaFw0yNTA1MTExNzA1MTJaMFYxCzAJBgNVBAYT AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD RW5nMRcwFQYDVQQDDA5sZWFmMjAuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjQzBBMB0GA1UdDgQWBBQ/si9B/BGa042m hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQFBgcIMA8GA1UdDwEB/wQFAwMH+YAwCgYI KoZIzj0EAwIDSAAwRQIhANk26w0GqWlSCprSVnll/VVbk+tMAuakfVPUs5M+ATuR AiBz/BTFl/v3Un4VavmmFl+pVz3pHYpPgJR/dXtobw3u4A== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeGgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwMzI5MTcwNTExWhcNMjgwMjA1MTcwNTExWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d XaNmMGQwDQYDVR0OBAYEBAUGBwgwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E CDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCVv0izx2s5vY6Xa79LJt61gghF 0UQoA1CqCti0Iveh4AIhAPtkGMjAOBKqnF7ZLspyssaG6OCLebQrz3cz7GHfsMbc -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/log-rpc-server.privkey.pem000066400000000000000000000006511462611535200313340ustar00rootroot00000000000000# This key is for test purposes only and must not be used for # production under any circumstances. The key password is # 'towel' -----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,D95ECC664FF4BDEC Xy3zzHFwlFwjE8L1NCngJAFbu3zFf4IbBOCsz6Fa790utVNdulZncNCl2FMK3U2T sdoiTW8ymO+qgwcNrqvPVmjFRBtkN0Pn5lgbWhN/aK3TlS9IYJ/EShbMUzjgVzie S9+/31whWcH/FLeLJx4cBzvhgCtfquwA+s5ojeLYYsk= -----END EC PRIVATE KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/log-rpc-server.pubkey.pem000066400000000000000000000002621462611535200311400ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEywnWicNEQ8bn3GXcGpA+tiU4VL70 Ws9xezgQPrg96YGsFrF6KYG68iqyHDlQ+4FWuKfGKXHn3ooVtB/pfawb5Q== -----END PUBLIC KEY----- google-certificate-transparency-go-2308f62/trillian/testdata/signer.go000066400000000000000000000032231462611535200261060ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testdata import ( "crypto" "io" ) // signerStub returns a fixed signature and error, no matter the input. // It implements crypto.Signer. type signerStub struct { publicKey crypto.PublicKey signature []byte err error } // Public returns the public key associated with the signer that this stub is based on. func (s *signerStub) Public() crypto.PublicKey { return s.publicKey } // Sign will return the signature or error that the signerStub was created to provide. func (s *signerStub) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { return s.signature, s.err } // NewSignerWithErr creates a signer that always returns err when Sign() is called. func NewSignerWithErr(pubKey crypto.PublicKey, err error) crypto.Signer { return &signerStub{ publicKey: pubKey, err: err, } } // NewSignerWithFixedSig creates a signer that always return sig when Sign() is called. func NewSignerWithFixedSig(pubKey crypto.PublicKey, sig []byte) crypto.Signer { return &signerStub{ publicKey: pubKey, signature: sig, } } google-certificate-transparency-go-2308f62/trillian/testdata/subleaf-pre.cert000066400000000000000000000014221462611535200273530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICFzCCAb2gAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xOTA0MjUxNzA3NTdaFw0yNjA2MDcxNzA3NTdaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso1gwVjAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MBMGCisGAQQB1nkCBAMBAf8EAgUAMAoGCCqGSM49BAMCA0gAMEUCIB4p2cqQyNx0 IxFaBZJXrtS7kTAd5RqOeMaP8mv0f3E6AiEAhPOQ58BhVcUHHfzaFYMyGLLfccFQ VactvT3i5pDEAow= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf-pre.chain000066400000000000000000000062501462611535200275040ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGDCCAb2gAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xOTA0MjUxNjMzMjZaFw0yNjA2MDcxNjMzMjZaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso1gwVjAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MBMGCisGAQQB1nkCBAMBAf8EAgUAMAoGCCqGSM49BAMCA0kAMEYCIQDwkvt5wIdx 82wbN77cw0l1ueZDrL9X+0punu8jG/jcugIhAJYMSqPXJvD9eqj0w/qN8Mj7jbOE v8JPF+DRyd1/RWnV -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICOzCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y aXR5MB4XDTE5MDQyNTE2MzMyNloXDTI5MDMwMzE2MzMyNlowdTELMAkGA1UEBhMC R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAKBggqhkjOPQQDAgNHADBEAiA/b2LXusXoUw9VKsrkP3E3SdHl gk8hAbju0hr6+q3FpQIgEu0uTGWjcOQVwTvvUVjxProBCA8Q+3aRaDrQUnzp8Ic= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICNzCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTkwNDI1MTYzMjU3WhcNMjkwMzAzMTYzMjU3WjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMAoGCCqGSM49BAMCA0cAMEQCIQClrXuTuejOJsGYiYq9wx8hBO8v+ALZ Pb8PdoC3SEj4wQIfd/oq7smvdVOSgmSI3VaJ9maUqQ2AYevnfIDh3HSCUQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICGjCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTkwNDI1MTYzMjU3WhcNMjkwNDIyMTYzMjU3WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNHADBEAiAu8JGg nwbGen9HUwoqXQyAKyfqAyHb5CtP+0KtqRi8/wIgfOET6WbdgGesot436YM1Rh11 UPT4+dMAK2TvOIVRoVE= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf-pre.misordered.chain000066400000000000000000000062541462611535200316440ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICFzCCAb2gAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xOTA0MjUxNzA3NTdaFw0yNjA2MDcxNzA3NTdaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso1gwVjAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MBMGCisGAQQB1nkCBAMBAf8EAgUAMAoGCCqGSM49BAMCA0gAMEUCIB4p2cqQyNx0 IxFaBZJXrtS7kTAd5RqOeMaP8mv0f3E6AiEAhPOQ58BhVcUHHfzaFYMyGLLfccFQ VactvT3i5pDEAow= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y aXR5MB4XDTE5MDQyNTE3MDc1N1oXDTI5MDMwMzE3MDc1N1owdTELMAkGA1UEBhMC R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiAI0nATFBv8W/7yvXhl3AtMWVEr 8xRMvD6Y2hesrWe0+QIhAJoOExYIcjFjUTpfxtrLQ2yhg8RIJoQivylhorxm1QCK -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf.cert000066400000000000000000000013651462611535200265750ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICAjCCAaigAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xODA0MDYxMTAxMDFaFw0yNTA1MTkxMTAxMDFaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso0MwQTAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MAoGCCqGSM49BAMCA0gAMEUCIQDlNWeY4ia5oU7JhTPoDubhuORdfqgVwr0MFENf NLDQ/gIgOPLRom6vbqZC1EuT234zn1csFnSkDp9EFOS1z7URJPk= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf.chain000066400000000000000000000062171462611535200267230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICAjCCAaigAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xODA0MDYxMTAxMDFaFw0yNTA1MTkxMTAxMDFaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso0MwQTAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MAoGCCqGSM49BAMCA0gAMEUCIQDlNWeY4ia5oU7JhTPoDubhuORdfqgVwr0MFENf NLDQ/gIgOPLRom6vbqZC1EuT234zn1csFnSkDp9EFOS1z7URJPk= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y aXR5MB4XDTE4MDQwNjExMDEwMVoXDTI4MDIxMzExMDEwMVowdTELMAkGA1UEBhMC R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBRmlMEQHyEenjH9eft8K/9Aj0s Q5TMzI8domdMGSTktwIhAKIJWSuJsn9C0QXKFAxlOVJsjlgIIuLuyUvrrbKKlz45 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf.csr.pem000066400000000000000000000007001462611535200271770ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBEzCBuQIBADBXMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYD VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEYMBYGA1UEAwwPc3VibGVhZi5jc3Iu cGVtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6zdOUkWcRtWouMXtWLkwKaZw immgJlyeL264ayNshOFGOpg2gkSliheLQYIy9C3gCFt+BzhS/EdWKCeb7WCLrKAA MAoGCCqGSM49BAMCA0kAMEYCIQDTCWRAkzwTJRws02oT59T8QsVvJ3GMyGuc02ID rScR4QIhAIZwCw0wxScA3HWHCBwRMr7xANkDpfS+KD4c2xiazcKe -----END CERTIFICATE REQUEST----- google-certificate-transparency-go-2308f62/trillian/testdata/subleaf.misordered.chain000066400000000000000000000062171462611535200310570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICAjCCAaigAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 dGhvcml0eTAeFw0xODA0MDYxMTAxMDFaFw0yNTA1MTkxMTAxMDFaMFcxCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso0MwQTAdBgNVHQ4EFgQUP7IvQfwR mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA MAoGCCqGSM49BAMCA0gAMEUCIQDlNWeY4ia5oU7JhTPoDubhuORdfqgVwr0MFENf NLDQ/gIgOPLRom6vbqZC1EuT234zn1csFnSkDp9EFOS1z7URJPk= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y aXR5MB4XDTE4MDQwNjExMDEwMVoXDTI4MDIxMzExMDEwMVowdTELMAkGA1UEBhMC R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBRmlMEQHyEenjH9eft8K/9Aj0s Q5TMzI8domdMGSTktwIhAKIJWSuJsn9C0QXKFAxlOVJsjlgIIuLuyUvrrbKKlz45 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM iGuwIwcrxwwn3octloDVVg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/trillian/util/000077500000000000000000000000001462611535200234345ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/trillian/util/log_leaf.go000066400000000000000000000101231462611535200255300ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import ( "crypto/sha256" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian" "k8s.io/klog/v2" ) // BuildLogLeaf returns a Trillian LogLeaf structure for a (pre-)cert and the // chain of certificates leading it up to a known root. func BuildLogLeaf(logPrefix string, merkleLeaf ct.MerkleTreeLeaf, leafIndex int64, cert ct.ASN1Cert, chain []ct.ASN1Cert, isPrecert bool, ) (*trillian.LogLeaf, error) { return buildLogLeaf(logPrefix, merkleLeaf, leafIndex, cert, chain, nil, isPrecert) } // ExtraDataForChain creates the extra data associated with a log entry as // described in RFC6962 section 4.6. func ExtraDataForChain(cert ct.ASN1Cert, chain []ct.ASN1Cert, isPrecert bool) ([]byte, error) { var extra interface{} if isPrecert { // For a pre-cert, the extra data is a TLS-encoded PrecertChainEntry. extra = ct.PrecertChainEntry{ PreCertificate: cert, CertificateChain: chain, } } else { // For a certificate, the extra data is a TLS-encoded: // ASN.1Cert certificate_chain<0..2^24-1>; // containing the chain after the leaf. extra = ct.CertificateChain{Entries: chain} } return tls.Marshal(extra) } // BuildLogLeafWithChainHash returns a Trillian LogLeaf structure for a // (pre-)cert and the chain of certificates leading it up to a known root. func BuildLogLeafWithChainHash(logPrefix string, merkleLeaf ct.MerkleTreeLeaf, leafIndex int64, cert ct.ASN1Cert, chainHash []byte, isPrecert bool) (*trillian.LogLeaf, error) { return buildLogLeaf(logPrefix, merkleLeaf, leafIndex, cert, nil, chainHash, isPrecert) } // ExtraDataForChainHash creates the extra data associated with a log entry as // described in RFC6962 section 4.6 except the chain being replaced with its hash. func ExtraDataForChainHash(cert ct.ASN1Cert, chainHash []byte, isPrecert bool) ([]byte, error) { var extra any if isPrecert { // For a pre-cert, the extra data is a TLS-encoded PrecertChainEntry. extra = ct.PrecertChainEntryHash{ PreCertificate: cert, IssuanceChainHash: chainHash, } } else { // For a certificate, the extra data is a TLS-encoded: // ASN.1Cert certificate_chain<0..2^24-1>; // containing the chain after the leaf. extra = ct.CertificateChainHash{ IssuanceChainHash: chainHash, } } return tls.Marshal(extra) } // buildLogLeaf builds the trillian.LogLeaf. The chainHash argument controls // whether ExtraDataForChain or ExtraDataForChainHash method will be called. // If chainHash is not nil, but neither is chain, then chain will be ignored. func buildLogLeaf(logPrefix string, merkleLeaf ct.MerkleTreeLeaf, leafIndex int64, cert ct.ASN1Cert, chain []ct.ASN1Cert, chainHash []byte, isPrecert bool) (*trillian.LogLeaf, error) { leafData, err := tls.Marshal(merkleLeaf) if err != nil { klog.Warningf("%s: Failed to serialize Merkle leaf: %v", logPrefix, err) return nil, err } var extraData []byte if chainHash == nil { extraData, err = ExtraDataForChain(cert, chain, isPrecert) } else { extraData, err = ExtraDataForChainHash(cert, chainHash, isPrecert) } if err != nil { klog.Warningf("%s: Failed to serialize chain for ExtraData: %v", logPrefix, err) return nil, err } // leafIDHash allows Trillian to detect duplicate entries, so this should be // a hash over the cert data. leafIDHash := sha256.Sum256(cert.Data) return &trillian.LogLeaf{ LeafValue: leafData, ExtraData: extraData, LeafIndex: leafIndex, LeafIdentityHash: leafIDHash[:], }, nil } google-certificate-transparency-go-2308f62/trillian/util/timesource.go000066400000000000000000000030631462611535200261440ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package util provides general utility functions for the CT personality. package util import "time" // TimeSource can provide the current time, or be replaced by a mock in tests to return // specific values. type TimeSource interface { // Now returns the current time in real implementations or a suitable value in others Now() time.Time } // SystemTimeSource provides the current system local time type SystemTimeSource struct{} // Now returns the true current local time. func (s SystemTimeSource) Now() time.Time { return time.Now() } // FixedTimeSource provides a fixed time for use in tests. // It should not be used in production code. type FixedTimeSource struct { fakeTime time.Time } // NewFixedTimeSource creates a FixedTimeSource instance func NewFixedTimeSource(t time.Time) *FixedTimeSource { return &FixedTimeSource{fakeTime: t} } // Now returns the time value this instance contains func (f *FixedTimeSource) Now() time.Time { return f.fakeTime } google-certificate-transparency-go-2308f62/types.go000066400000000000000000000524131462611535200223410ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package ct holds core types and utilities for Certificate Transparency. package ct import ( "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) /////////////////////////////////////////////////////////////////////////////// // The following structures represent those outlined in RFC6962; any section // numbers mentioned refer to that RFC. /////////////////////////////////////////////////////////////////////////////// // LogEntryType represents the LogEntryType enum from section 3.1: // // enum { x509_entry(0), precert_entry(1), (65535) } LogEntryType; type LogEntryType tls.Enum // tls:"maxval:65535" // LogEntryType constants from section 3.1. const ( X509LogEntryType LogEntryType = 0 PrecertLogEntryType LogEntryType = 1 ) func (e LogEntryType) String() string { switch e { case X509LogEntryType: return "X509LogEntryType" case PrecertLogEntryType: return "PrecertLogEntryType" default: return fmt.Sprintf("UnknownEntryType(%d)", e) } } // RFC6962 section 2.1 requires a prefix byte on hash inputs for second preimage resistance. const ( TreeLeafPrefix = byte(0x00) TreeNodePrefix = byte(0x01) ) // MerkleLeafType represents the MerkleLeafType enum from section 3.4: // // enum { timestamped_entry(0), (255) } MerkleLeafType; type MerkleLeafType tls.Enum // tls:"maxval:255" // TimestampedEntryLeafType is the only defined MerkleLeafType constant from section 3.4. const TimestampedEntryLeafType MerkleLeafType = 0 // Entry type for an SCT func (m MerkleLeafType) String() string { switch m { case TimestampedEntryLeafType: return "TimestampedEntryLeafType" default: return fmt.Sprintf("UnknownLeafType(%d)", m) } } // Version represents the Version enum from section 3.2: // // enum { v1(0), (255) } Version; type Version tls.Enum // tls:"maxval:255" // CT Version constants from section 3.2. const ( V1 Version = 0 ) func (v Version) String() string { switch v { case V1: return "V1" default: return fmt.Sprintf("UnknownVersion(%d)", v) } } // SignatureType differentiates STH signatures from SCT signatures, see section 3.2. // // enum { certificate_timestamp(0), tree_hash(1), (255) } SignatureType; type SignatureType tls.Enum // tls:"maxval:255" // SignatureType constants from section 3.2. const ( CertificateTimestampSignatureType SignatureType = 0 TreeHashSignatureType SignatureType = 1 ) func (st SignatureType) String() string { switch st { case CertificateTimestampSignatureType: return "CertificateTimestamp" case TreeHashSignatureType: return "TreeHash" default: return fmt.Sprintf("UnknownSignatureType(%d)", st) } } // ASN1Cert type for holding the raw DER bytes of an ASN.1 Certificate // (section 3.1). type ASN1Cert struct { Data []byte `tls:"minlen:1,maxlen:16777215"` } // LogID holds the hash of the Log's public key (section 3.2). // TODO(pphaneuf): Users should be migrated to the one in the logid package. type LogID struct { KeyID [sha256.Size]byte } // PreCert represents a Precertificate (section 3.2). type PreCert struct { IssuerKeyHash [sha256.Size]byte TBSCertificate []byte `tls:"minlen:1,maxlen:16777215"` // DER-encoded TBSCertificate } // CTExtensions is a representation of the raw bytes of any CtExtension // structure (see section 3.2). // nolint: revive type CTExtensions []byte // tls:"minlen:0,maxlen:65535"` // MerkleTreeNode represents an internal node in the CT tree. type MerkleTreeNode []byte // ConsistencyProof represents a CT consistency proof (see sections 2.1.2 and // 4.4). type ConsistencyProof []MerkleTreeNode // AuditPath represents a CT inclusion proof (see sections 2.1.1 and 4.5). type AuditPath []MerkleTreeNode // LeafInput represents a serialized MerkleTreeLeaf structure. type LeafInput []byte // DigitallySigned is a local alias for tls.DigitallySigned so that we can // attach a MarshalJSON method. type DigitallySigned tls.DigitallySigned // FromBase64String populates the DigitallySigned structure from the base64 data passed in. // Returns an error if the base64 data is invalid. func (d *DigitallySigned) FromBase64String(b64 string) error { raw, err := base64.StdEncoding.DecodeString(b64) if err != nil { return fmt.Errorf("failed to unbase64 DigitallySigned: %v", err) } var ds tls.DigitallySigned if rest, err := tls.Unmarshal(raw, &ds); err != nil { return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) } else if len(rest) > 0 { return fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) } *d = DigitallySigned(ds) return nil } // Base64String returns the base64 representation of the DigitallySigned struct. func (d DigitallySigned) Base64String() (string, error) { b, err := tls.Marshal(d) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } // MarshalJSON implements the json.Marshaller interface. func (d DigitallySigned) MarshalJSON() ([]byte, error) { b64, err := d.Base64String() if err != nil { return []byte{}, err } return []byte(`"` + b64 + `"`), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (d *DigitallySigned) UnmarshalJSON(b []byte) error { var content string if err := json.Unmarshal(b, &content); err != nil { return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) } return d.FromBase64String(content) } // RawLogEntry represents the (TLS-parsed) contents of an entry in a CT log. type RawLogEntry struct { // Index is a position of the entry in the log. Index int64 // Leaf is a parsed Merkle leaf hash input. Leaf MerkleTreeLeaf // Cert is: // - A certificate if Leaf.TimestampedEntry.EntryType is X509LogEntryType. // - A precertificate if Leaf.TimestampedEntry.EntryType is // PrecertLogEntryType, in the form of a DER-encoded Certificate as // originally added (which includes the poison extension and a signature // generated over the pre-cert by the pre-cert issuer). // - Empty otherwise. Cert ASN1Cert // Chain is the issuing certificate chain starting with the issuer of Cert, // or an empty slice if Cert is empty. Chain []ASN1Cert } // LogEntry represents the (parsed) contents of an entry in a CT log. This is described // in section 3.1, but note that this structure does *not* match the TLS structure // defined there (the TLS structure is never used directly in RFC6962). type LogEntry struct { Index int64 Leaf MerkleTreeLeaf // Exactly one of the following three fields should be non-empty. X509Cert *x509.Certificate // Parsed X.509 certificate Precert *Precertificate // Extracted precertificate JSONData []byte // Chain holds the issuing certificate chain, starting with the // issuer of the leaf certificate / pre-certificate. Chain []ASN1Cert } // PrecertChainEntry holds an precertificate together with a validation chain // for it; see section 3.1. type PrecertChainEntry struct { PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"` CertificateChain []ASN1Cert `tls:"minlen:0,maxlen:16777215"` } // CertificateChain holds a chain of certificates, as returned as extra data // for get-entries (section 4.6). type CertificateChain struct { Entries []ASN1Cert `tls:"minlen:0,maxlen:16777215"` } // PrecertChainEntryHash is an extended PrecertChainEntry type with the // IssuanceChainHash field added to store the hash of the // CertificateChain field of PrecertChainEntry. type PrecertChainEntryHash struct { PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"` IssuanceChainHash []byte `tls:"minlen:0,maxlen:256"` } // CertificateChainHash is an extended CertificateChain type with the // IssuanceChainHash field added to store the hash of the // Entries field of CertificateChain. type CertificateChainHash struct { IssuanceChainHash []byte `tls:"minlen:0,maxlen:256"` } // JSONDataEntry holds arbitrary data. type JSONDataEntry struct { Data []byte `tls:"minlen:0,maxlen:1677215"` } // SHA256Hash represents the output from the SHA256 hash function. type SHA256Hash [sha256.Size]byte // FromBase64String populates the SHA256 struct with the contents of the base64 data passed in. func (s *SHA256Hash) FromBase64String(b64 string) error { bs, err := base64.StdEncoding.DecodeString(b64) if err != nil { return fmt.Errorf("failed to unbase64 LogID: %v", err) } if len(bs) != sha256.Size { return fmt.Errorf("invalid SHA256 length, expected 32 but got %d", len(bs)) } copy(s[:], bs) return nil } // Base64String returns the base64 representation of this SHA256Hash. func (s SHA256Hash) Base64String() string { return base64.StdEncoding.EncodeToString(s[:]) } // MarshalJSON implements the json.Marshaller interface for SHA256Hash. func (s SHA256Hash) MarshalJSON() ([]byte, error) { return []byte(`"` + s.Base64String() + `"`), nil } // UnmarshalJSON implements the json.Unmarshaller interface. func (s *SHA256Hash) UnmarshalJSON(b []byte) error { var content string if err := json.Unmarshal(b, &content); err != nil { return fmt.Errorf("failed to unmarshal SHA256Hash: %v", err) } return s.FromBase64String(content) } // SignedTreeHead represents the structure returned by the get-sth CT method // after base64 decoding; see sections 3.5 and 4.3. type SignedTreeHead struct { Version Version `json:"sth_version"` // The version of the protocol to which the STH conforms TreeSize uint64 `json:"tree_size"` // The number of entries in the new tree Timestamp uint64 `json:"timestamp"` // The time at which the STH was created SHA256RootHash SHA256Hash `json:"sha256_root_hash"` // The root hash of the log's Merkle tree TreeHeadSignature DigitallySigned `json:"tree_head_signature"` // Log's signature over a TLS-encoded TreeHeadSignature LogID SHA256Hash `json:"log_id"` // The SHA256 hash of the log's public key } func (s SignedTreeHead) String() string { sigStr, err := s.TreeHeadSignature.Base64String() if err != nil { sigStr = tls.DigitallySigned(s.TreeHeadSignature).String() } // If the LogID field in the SignedTreeHead is empty, don't include it in // the string. var logIDStr string if id, empty := s.LogID, (SHA256Hash{}); id != empty { logIDStr = fmt.Sprintf("LogID:%s, ", id.Base64String()) } return fmt.Sprintf("{%sTreeSize:%d, Timestamp:%d, SHA256RootHash:%q, TreeHeadSignature:%q}", logIDStr, s.TreeSize, s.Timestamp, s.SHA256RootHash.Base64String(), sigStr) } // TreeHeadSignature holds the data over which the signature in an STH is // generated; see section 3.5 type TreeHeadSignature struct { Version Version `tls:"maxval:255"` SignatureType SignatureType `tls:"maxval:255"` // == TreeHashSignatureType Timestamp uint64 TreeSize uint64 SHA256RootHash SHA256Hash } // SignedCertificateTimestamp represents the structure returned by the // add-chain and add-pre-chain methods after base64 decoding; see sections // 3.2, 4.1 and 4.2. type SignedCertificateTimestamp struct { SCTVersion Version `tls:"maxval:255"` LogID LogID Timestamp uint64 Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` Signature DigitallySigned // Signature over TLS-encoded CertificateTimestamp } // CertificateTimestamp is the collection of data that the signature in an // SCT is over; see section 3.2. type CertificateTimestamp struct { SCTVersion Version `tls:"maxval:255"` SignatureType SignatureType `tls:"maxval:255"` Timestamp uint64 EntryType LogEntryType `tls:"maxval:65535"` X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` } func (s SignedCertificateTimestamp) String() string { return fmt.Sprintf("{Version:%d LogId:%s Timestamp:%d Extensions:'%s' Signature:%v}", s.SCTVersion, base64.StdEncoding.EncodeToString(s.LogID.KeyID[:]), s.Timestamp, s.Extensions, s.Signature) } // TimestampedEntry is part of the MerkleTreeLeaf structure; see section 3.4. type TimestampedEntry struct { Timestamp uint64 EntryType LogEntryType `tls:"maxval:65535"` X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` } // MerkleTreeLeaf represents the deserialized structure of the hash input for the // leaves of a log's Merkle tree; see section 3.4. type MerkleTreeLeaf struct { Version Version `tls:"maxval:255"` LeafType MerkleLeafType `tls:"maxval:255"` TimestampedEntry *TimestampedEntry `tls:"selector:LeafType,val:0"` } // Precertificate represents the parsed CT Precertificate structure. type Precertificate struct { // DER-encoded pre-certificate as originally added, which includes a // poison extension and a signature generated over the pre-cert by // the pre-cert issuer (which might differ from the issuer of the final // cert, see RFC6962 s3.1). Submitted ASN1Cert // SHA256 hash of the issuing key IssuerKeyHash [sha256.Size]byte // Parsed TBSCertificate structure, held in an x509.Certificate for convenience. TBSCertificate *x509.Certificate } // X509Certificate returns the X.509 Certificate contained within the // MerkleTreeLeaf. func (m *MerkleTreeLeaf) X509Certificate() (*x509.Certificate, error) { if m.TimestampedEntry.EntryType != X509LogEntryType { return nil, fmt.Errorf("cannot call X509Certificate on a MerkleTreeLeaf that is not an X509 entry") } return x509.ParseCertificate(m.TimestampedEntry.X509Entry.Data) } // Precertificate returns the X.509 Precertificate contained within the MerkleTreeLeaf. // // The returned precertificate is embedded in an x509.Certificate, but is in the // form stored internally in the log rather than the original submitted form // (i.e. it does not include the poison extension and any changes to reflect the // final certificate's issuer have been made; see x509.BuildPrecertTBS). func (m *MerkleTreeLeaf) Precertificate() (*x509.Certificate, error) { if m.TimestampedEntry.EntryType != PrecertLogEntryType { return nil, fmt.Errorf("cannot call Precertificate on a MerkleTreeLeaf that is not a precert entry") } return x509.ParseTBSCertificate(m.TimestampedEntry.PrecertEntry.TBSCertificate) } // APIEndpoint is a string that represents one of the Certificate Transparency // Log API endpoints. type APIEndpoint string // Certificate Transparency Log API endpoints; see section 4. // WARNING: Should match the URI paths without the "/ct/v1/" prefix. If // changing these constants, may need to change those too. const ( AddChainStr APIEndpoint = "add-chain" AddPreChainStr APIEndpoint = "add-pre-chain" GetSTHStr APIEndpoint = "get-sth" GetEntriesStr APIEndpoint = "get-entries" GetProofByHashStr APIEndpoint = "get-proof-by-hash" GetSTHConsistencyStr APIEndpoint = "get-sth-consistency" GetRootsStr APIEndpoint = "get-roots" GetEntryAndProofStr APIEndpoint = "get-entry-and-proof" ) // URI paths for Log requests; see section 4. // WARNING: Should match the API endpoints, with the "/ct/v1/" prefix. If // changing these constants, may need to change those too. const ( AddChainPath = "/ct/v1/add-chain" AddPreChainPath = "/ct/v1/add-pre-chain" GetSTHPath = "/ct/v1/get-sth" GetEntriesPath = "/ct/v1/get-entries" GetProofByHashPath = "/ct/v1/get-proof-by-hash" GetSTHConsistencyPath = "/ct/v1/get-sth-consistency" GetRootsPath = "/ct/v1/get-roots" GetEntryAndProofPath = "/ct/v1/get-entry-and-proof" AddJSONPath = "/ct/v1/add-json" // Experimental addition ) // AddChainRequest represents the JSON request body sent to the add-chain and // add-pre-chain POST methods from sections 4.1 and 4.2. type AddChainRequest struct { Chain [][]byte `json:"chain"` } // AddChainResponse represents the JSON response to the add-chain and // add-pre-chain POST methods. // An SCT represents a Log's promise to integrate a [pre-]certificate into the // log within a defined period of time. type AddChainResponse struct { SCTVersion Version `json:"sct_version"` // SCT structure version ID []byte `json:"id"` // Log ID Timestamp uint64 `json:"timestamp"` // Timestamp of issuance Extensions string `json:"extensions"` // Holder for any CT extensions Signature []byte `json:"signature"` // Log signature for this SCT } // ToSignedCertificateTimestamp creates a SignedCertificateTimestamp from the // AddChainResponse. func (r *AddChainResponse) ToSignedCertificateTimestamp() (*SignedCertificateTimestamp, error) { sct := SignedCertificateTimestamp{ SCTVersion: r.SCTVersion, Timestamp: r.Timestamp, } if len(r.ID) != sha256.Size { return nil, fmt.Errorf("id is invalid length, expected %d got %d", sha256.Size, len(r.ID)) } copy(sct.LogID.KeyID[:], r.ID) exts, err := base64.StdEncoding.DecodeString(r.Extensions) if err != nil { return nil, fmt.Errorf("invalid base64 data in Extensions (%q): %v", r.Extensions, err) } sct.Extensions = CTExtensions(exts) var ds DigitallySigned if rest, err := tls.Unmarshal(r.Signature, &ds); err != nil { return nil, fmt.Errorf("tls.Unmarshal(): %s", err) } else if len(rest) > 0 { return nil, fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) } sct.Signature = ds return &sct, nil } // AddJSONRequest represents the JSON request body sent to the add-json POST method. // The corresponding response re-uses AddChainResponse. // This is an experimental addition not covered by RFC6962. type AddJSONRequest struct { Data interface{} `json:"data"` } // GetSTHResponse represents the JSON response to the get-sth GET method from section 4.3. type GetSTHResponse struct { TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree Timestamp uint64 `json:"timestamp"` // Time that the tree was created SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH } // ToSignedTreeHead creates a SignedTreeHead from the GetSTHResponse. func (r *GetSTHResponse) ToSignedTreeHead() (*SignedTreeHead, error) { sth := SignedTreeHead{ TreeSize: r.TreeSize, Timestamp: r.Timestamp, } if len(r.SHA256RootHash) != sha256.Size { return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(r.SHA256RootHash)) } copy(sth.SHA256RootHash[:], r.SHA256RootHash) var ds DigitallySigned if rest, err := tls.Unmarshal(r.TreeHeadSignature, &ds); err != nil { return nil, fmt.Errorf("tls.Unmarshal(): %s", err) } else if len(rest) > 0 { return nil, fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) } sth.TreeHeadSignature = ds return &sth, nil } // GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency // GET method from section 4.4. (The corresponding GET request has parameters 'first' and // 'second'.) type GetSTHConsistencyResponse struct { Consistency [][]byte `json:"consistency"` } // GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET // method from section 4.5. (The corresponding GET request has parameters 'hash' // and 'tree_size'.) type GetProofByHashResponse struct { LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter. AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate. } // LeafEntry represents a leaf in the Log's Merkle tree, as returned by the get-entries // GET method from section 4.6. type LeafEntry struct { // LeafInput is a TLS-encoded MerkleTreeLeaf LeafInput []byte `json:"leaf_input"` // ExtraData holds (unsigned) extra data, normally the cert validation chain. ExtraData []byte `json:"extra_data"` } // GetEntriesResponse represents the JSON response to the get-entries GET method // from section 4.6. type GetEntriesResponse struct { Entries []LeafEntry `json:"entries"` // the list of returned entries } // GetRootsResponse represents the JSON response to the get-roots GET method from section 4.7. type GetRootsResponse struct { Certificates []string `json:"certificates"` } // GetEntryAndProofResponse represents the JSON response to the get-entry-and-proof // GET method from section 4.8. (The corresponding GET request has parameters 'leaf_index' // and 'tree_size'.) type GetEntryAndProofResponse struct { LeafInput []byte `json:"leaf_input"` // the entry itself ExtraData []byte `json:"extra_data"` // any chain provided when the entry was added to the log AuditPath [][]byte `json:"audit_path"` // the corresponding proof } google-certificate-transparency-go-2308f62/types_test.go000066400000000000000000000311001462611535200233660ustar00rootroot00000000000000// Copyright 2015 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ct import ( "encoding/base64" "encoding/hex" "fmt" "strings" "testing" "github.com/google/certificate-transparency-go/tls" ) const ( CertEntry = "000000000149a6e03abe00000006513082064d30820535a003020102020c6a5d4161f5c9b68043270b0c300d06092a864886f70d0101050500305e310b300906035504061302424531193017060355040a1310476c6f62616c5369676e206e762d7361313430320603550403132b476c6f62616c5369676e20457874656e6465642056616c69646174696f6e204341202d2047322054455354301e170d3134313131333031353830315a170d3136313131333031353830315a3082011331183016060355040f0c0f427573696e65737320456e74697479311230100603550405130936363636363636363631133011060b2b0601040182373c0201031302444531293027060b2b0601040182373c02010113186576206a7572697364696374696f6e206c6f63616c69747931263024060b2b0601040182373c02010213156576206a7572697364696374696f6e207374617465310b3009060355040613024a50310a300806035504080c0153310a300806035504070c014c311530130603550409130c657620616464726573732033310c300a060355040b0c034f5531310c300a060355040b0c034f5532310a3008060355040a0c014f3117301506035504030c0e637372636e2e73736c32342e6a7030820122300d06092a864886f70d01010105000382010f003082010a02820101008db9f0d6b359466dffe95ba43dc1a5680eedc8f3cabbc573a236a109bf6e58df816c7bb8156147ab526eceaffd0576e6e1c09ea33433e114d7e5038c697298c7957f01a7e1142320847cf234995bbe42798340cb99e6a7e2cfa950277aef6e02f4d96ddceb0af9541171b0f8f1aa4f0d02453e6e654b25a13f2aff4357cae8177d3bd21855686591a2309d9ff5dead8240304e22eafcc5508587e6b6ad1d00b53c28e5b936269afbf214b73edbdc8a48a86c1c23f3dce55fcce60502c0908bca9bdb22c16c0b34d11b4fd27e9d7bcb56c5ec0fc4d52500fb06b0af5c4112e421022b78b31030cb73e9fd92ffc65919fd8f35e604fcaf025b9c77e3e5dff749a70203010001a38202523082024e300e0603551d0f0101ff0404030205a0304c0603551d2004453043304106092b06010401a03201013034303206082b06010505070201162668747470733a2f2f7777772e676c6f62616c7369676e2e636f6d2f7265706f7369746f72792f30480603551d1f0441303f303da03ba0398637687474703a2f2f63726c2e676c6f62616c7369676e2e636f6d2f67732f67736f7267616e697a6174696f6e76616c63617467322e63726c30819c06082b0601050507010104818f30818c304a06082b06010505073002863e687474703a2f2f7365637572652e676c6f62616c7369676e2e636f6d2f6361636572742f67736f7267616e697a6174696f6e76616c63617467322e637274303e06082b060105050730018632687474703a2f2f6f637370322e676c6f62616c7369676e2e636f6d2f67736f7267616e697a6174696f6e76616c6361746732301d0603551d250416301406082b0601050507030106082b0601050507030230190603551d1104123010820e637372636e2e73736c32342e6a70301d0603551d0e041604147f834b2903e35efff651619083a2efd69a6d70f4301f0603551d23041830168014ab30a406d972d0029ab2c7d3f4241be2fca5320230818a060a2b06010401d679020402047c047a0078007600b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c7700000149a6dc346b00000403004730450220469f4dc0553b7832bd56633c3b9d53faaec84df414b7a05ab1b2d544d146ac3e022100ee899419fd4f95544798f7883fe093692feb4c90e84d651600f7019166a43701300d06092a864886f70d010105050003820101007dcd3e228d68cdc0734c7629fd7d40cd742d0ed1d0d9f49a643af12dcdbc61394638b7c519bb7cae530ccdc3a5037d5cdd8a4d2c01abdc834daf1993f7a22ee2c223377a94da4e68ac69a0b50d2d473ec77651e001c5f71a23cc2defe7616fd6c6491aa7f9a2bb16b930ce3f8cc37cf6a47bfb04fd4eff7db8433cc6fdb05146a4a31fe65211875f2c51129bf0729ce2dc7ce1a5afc6eaa1eb3a36296cb9e091375edfc408c727f6d54bba408da60b46c496a364c504adf47ee0496a9260fe223c8b23c14832635c3dff0dba8a0c8cdd957a77f18443b7782a9b6c7636b7d66df426350b959537e911888e45b2c0b218e50d03fdcfa7f758e8e60dd1a1996bc00000" PrecertEntry = "00000000014b4981f0c800013760e2790f33a498f9b6c149fecfca3993954b536fbf36ad45d0a8415b79337d00047a30820476a00302010202100532298c396a3e25fcaa1977e827b5f3300d06092a864886f70d01010b0500306d310b300906035504061302555331163014060355040a130d47656f547275737420496e632e311f301d060355040b1316464f52205445535420505552504f534553204f4e4c59312530230603550403131c47656f54727573742045562053534c2054455354204341202d204734301e170d3135303230323030303030305a170d3136303232373233353935395a3081c331133011060b2b0601040182373c02010313024742311b3019060b2b0601040182373c020102140a43616c69666f726e6961311e301c060b2b0601040182373c0201010c0d4d6f756e7461696e2056696577310b30090603550406130247423113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e2056696577311d301b060355040a0c1453796d616e74656320436f72706f726174696f6e3116301406035504030c0d736466656473662e747275737430820122300d06092a864886f70d01010105000382010f003082010a0282010100b19d97def39ff829c65ea099a3257298b33ff675451fdc5641a222347aee4a56201f4c1a406f2f19815d86dec1a611768e7d556c8e33a7f1b4c78db19cceae540e97ae1f0660b2ee4f8cff2045b84a9da228349744406eceaed0b08d46fdab3543b3d86ea708627a61a529b793a76adc6b776bc8d5b3d4fe21e2c4aa92cfd33b45e7412068e0683a2beffad1df2fc320b8ddbf02ffb603d2cf74798277fd9656b5acd45659b0e5d761e02dcf95c53095555a931ad5bfa9b4967c045d5f12de2d6b537cd93af2ad8b45e5540bd43279876d13e376fb649778e10dfa56165b901bd37e9dee4e46027b4c0732ca7ed64491862abaf6a24a4aaed8f49a0922ca4fb50203010001a38201d1308201cd30470603551d110440303e820d6b6a61736468662e7472757374820b73736466732e7472757374820d736466656473662e747275737482117777772e736466656473662e747275737430090603551d1304023000300e0603551d0f0101ff0404030205a0302b0603551d1f042430223020a01ea01c861a687474703a2f2f676d2e73796d63622e636f6d2f676d2e63726c3081a00603551d2004819830819530819206092b06010401f0220106308184303f06082b06010505070201163368747470733a2f2f7777772e67656f74727573742e636f6d2f7265736f75726365732f7265706f7369746f72792f6c6567616c304106082b0601050507020230350c3368747470733a2f2f7777772e67656f74727573742e636f6d2f7265736f75726365732f7265706f7369746f72792f6c6567616c301d0603551d250416301406082b0601050507030106082b06010505070302301f0603551d23041830168014b1699461abe6cb0c4ce759af5a498b1833c1e147305706082b06010505070101044b3049301f06082b060105050730018613687474703a2f2f676d2e73796d63642e636f6d302606082b06010505073002861a687474703a2f2f676d2e73796d63622e636f6d2f676d2e6372740000" ) func TestUnmarshalMerkleTreeLeaf(t *testing.T) { var tests = []struct { in string // hex string want LogEntryType errstr string }{ {CertEntry, X509LogEntryType, ""}, {PrecertEntry, PrecertLogEntryType, ""}, {"001234", 0, "LeafType: unhandled value"}, } for _, test := range tests { inData, _ := hex.DecodeString(test.in) var got MerkleTreeLeaf _, err := tls.Unmarshal(inData, &got) if test.errstr != "" { if err == nil { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=%+v,nil; want error %q", test.in, got, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=nil,%q; want error %q", test.in, err.Error(), test.errstr) } continue } if err != nil { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=nil,%q; want type %v", test.in, err.Error(), test.want) continue } if got.Version != V1 { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=version=%v,nil; want version 1", test.in, got.Version) } if got.LeafType != TimestampedEntryLeafType { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=LeafType=%v,nil; want LeafType=%v", test.in, got.LeafType, TimestampedEntryLeafType) } if got.TimestampedEntry.EntryType != test.want { t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=EntryType=%v,nil; want LeafType=%v", test.in, got.TimestampedEntry.EntryType, test.want) } } } func mustB64Decode(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { fmt.Println(s) panic(err) } return b } func TestToSignedCertificateTimestamp(t *testing.T) { // From the sct: // {"sct_version":0,"id":"CEEUmABxUywWGQRgvPxH/cJlOvopLHKzf/hjrinMyfA=","timestamp":1512556025588,"extensions":"","signature":"BAMARjBEAiAJAPO7EKykH4eOQ81kTzKCb4IEWzcxTBdbdRCHLFPLFAIgBEoGXDUtcIaF3M5HWI+MxwkCQbvqR9TSGUHDCZoOr3Q="} validLogID := "CEEUmABxUywWGQRgvPxH/cJlOvopLHKzf/hjrinMyfA=" longLogID := "CEEUmABxUywWGQRgvPxH/cJlOvopLHKzf/hjrinMyfA0" shortLogID := "CEEUmABxUywWGQRgvPxH/cJlOvopLHKzf/hjrinMyf==" validSCTSignature := "BAMARjBEAiAJAPO7EKykH4eOQ81kTzKCb4IEWzcxTBdbdRCHLFPLFAIgBEoGXDUtcIaF3M5HWI+MxwkCQbvqR9TSGUHDCZoOr3Q=" longSCTSignature := "BAMARjBEAiAJAPO7EKykH4eOQ81kTzKCb4IEWzcxTBdbdRCHLFPLFAIgBEoGXDUtcIaF3M5HWI+MxwkCQbvqR9TSGUHDCZoOr3Q0" tests := []struct { desc string logID string exts string signature string wantErr bool }{ { desc: "success", logID: validLogID, signature: validSCTSignature, }, { desc: "log ID too long", logID: longLogID, signature: validSCTSignature, wantErr: true, }, { desc: "log ID too short", logID: shortLogID, signature: validSCTSignature, wantErr: true, }, { desc: "extensions not base64", logID: validLogID, exts: "This is not Base64", signature: validSCTSignature, wantErr: true, }, { desc: "signature trailing data", logID: validLogID, signature: longSCTSignature, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { sctResponse := &AddChainResponse{ SCTVersion: 0, ID: mustB64Decode(test.logID), Timestamp: 1512556025588, Extensions: test.exts, Signature: mustB64Decode(test.signature), } sct, err := sctResponse.ToSignedCertificateTimestamp() if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("AddChainResponse.ToSignedCertificateTimestamp() = %+v, %v, want err? %t", sct, err, test.wantErr) } }) } } const ( validRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20fc0" longRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20fc000" shortRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20f" validSignature = "040300473045022007fb5ae3cea8f076b534a01a9a19e60625c6cc70704c6c1a7c88b30d8f67d4af022100840d37b8f2f9ce134e74eefda6a0c2ad034d591b785cdc4973c4c4f5d03f0439" longSignature = "040300473045022007fb5ae3cea8f076b534a01a9a19e60625c6cc70704c6c1a7c88b30d8f67d4af022100840d37b8f2f9ce134e74eefda6a0c2ad034d591b785cdc4973c4c4f5d03f043900" ) func mustHexDecode(s string) []byte { h, err := hex.DecodeString(s) if err != nil { panic(err) } return h } func TestToSignedTreeHead(t *testing.T) { tests := []struct { desc string rootHash string signature string wantErr bool }{ { desc: "success", rootHash: validRootHash, signature: validSignature, }, { desc: "root hash too long", rootHash: longRootHash, signature: validSignature, wantErr: true, }, { desc: "root hash too short", rootHash: shortRootHash, signature: validSignature, wantErr: true, }, { desc: "signature trailing data", rootHash: validRootHash, signature: longSignature, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { sthResponse := &GetSTHResponse{ TreeSize: 278437663, Timestamp: 1527076172068, SHA256RootHash: mustHexDecode(test.rootHash), TreeHeadSignature: mustHexDecode(test.signature), } sth, err := sthResponse.ToSignedTreeHead() if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("GetSTHResponse.ToSignedTreeHead() = %+v, %v, want err? %t", sth, err, test.wantErr) } }) } } func TestSTHString(t *testing.T) { tests := []struct { desc string logID string }{ { desc: "no logID", }, { desc: "logID", logID: "aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q=", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { sthResponse := &GetSTHResponse{ TreeSize: 278437663, Timestamp: 1527076172068, SHA256RootHash: mustHexDecode(validRootHash), TreeHeadSignature: mustHexDecode(validSignature), } sth, err := sthResponse.ToSignedTreeHead() if err != nil { t.Fatalf("sthResponse.ToSignedTreeHead(): %s", err) } if test.logID != "" { if err := sth.LogID.FromBase64String(test.logID); err != nil { t.Fatalf("SHA256Hash.FromBase64String(%s) = %s", test.logID, err) } } sthStr := sth.String() if got, want := strings.Contains(sthStr, "LogID"), len(test.logID) != 0; got != want { t.Errorf("SignedTreeHead.String(): contains LogID: %t, want LogID: %t", got, want) } }) } } google-certificate-transparency-go-2308f62/x509/000077500000000000000000000000001462611535200213465ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/README.md000066400000000000000000000003701462611535200226250ustar00rootroot00000000000000# Important Notice This is a fork of the `crypto/x509` Go package. The original source can be found on [GitHub](https://github.com/golang/go). Be careful about making local modifications to this code as it will make maintenance harder in future. google-certificate-transparency-go-2308f62/x509/cert_pool.go000066400000000000000000000074011462611535200236650ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "encoding/pem" "errors" "runtime" ) // CertPool is a set of certificates. type CertPool struct { bySubjectKeyId map[string][]int byName map[string][]int certs []*Certificate } // NewCertPool returns a new, empty CertPool. func NewCertPool() *CertPool { return &CertPool{ bySubjectKeyId: make(map[string][]int), byName: make(map[string][]int), } } func (s *CertPool) copy() *CertPool { p := &CertPool{ bySubjectKeyId: make(map[string][]int, len(s.bySubjectKeyId)), byName: make(map[string][]int, len(s.byName)), certs: make([]*Certificate, len(s.certs)), } for k, v := range s.bySubjectKeyId { indexes := make([]int, len(v)) copy(indexes, v) p.bySubjectKeyId[k] = indexes } for k, v := range s.byName { indexes := make([]int, len(v)) copy(indexes, v) p.byName[k] = indexes } copy(p.certs, s.certs) return p } // SystemCertPool returns a copy of the system cert pool. // // Any mutations to the returned pool are not written to disk and do // not affect any other pool returned by SystemCertPool. // // New changes in the system cert pool might not be reflected // in subsequent calls. func SystemCertPool() (*CertPool, error) { if runtime.GOOS == "windows" { // Issue 16736, 18609: return nil, errors.New("crypto/x509: system root pool is not available on Windows") } if sysRoots := systemRootsPool(); sysRoots != nil { return sysRoots.copy(), nil } return loadSystemRoots() } // findPotentialParents returns the indexes of certificates in s which might // have signed cert. The caller must not modify the returned slice. func (s *CertPool) findPotentialParents(cert *Certificate) []int { if s == nil { return nil } var candidates []int if len(cert.AuthorityKeyId) > 0 { candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)] } if len(candidates) == 0 { candidates = s.byName[string(cert.RawIssuer)] } return candidates } func (s *CertPool) contains(cert *Certificate) bool { if s == nil { return false } candidates := s.byName[string(cert.RawSubject)] for _, c := range candidates { if s.certs[c].Equal(cert) { return true } } return false } // AddCert adds a certificate to a pool. func (s *CertPool) AddCert(cert *Certificate) { if cert == nil { panic("adding nil Certificate to CertPool") } // Check that the certificate isn't being added twice. if s.contains(cert) { return } n := len(s.certs) s.certs = append(s.certs, cert) if len(cert.SubjectKeyId) > 0 { keyId := string(cert.SubjectKeyId) s.bySubjectKeyId[keyId] = append(s.bySubjectKeyId[keyId], n) } name := string(cert.RawSubject) s.byName[name] = append(s.byName[name], n) } // AppendCertsFromPEM attempts to parse a series of PEM encoded certificates. // It appends any certificates found to s and reports whether any certificates // were successfully parsed. // // On many Linux systems, /etc/ssl/cert.pem will contain the system wide set // of root CAs in a format suitable for this function. func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) { for len(pemCerts) > 0 { var block *pem.Block block, pemCerts = pem.Decode(pemCerts) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := ParseCertificate(block.Bytes) if IsFatal(err) { continue } s.AddCert(cert) ok = true } return } // Subjects returns a list of the DER-encoded subjects of // all of the certificates in the pool. func (s *CertPool) Subjects() [][]byte { res := make([][]byte, len(s.certs)) for i, c := range s.certs { res[i] = c.RawSubject } return res } google-certificate-transparency-go-2308f62/x509/curves.go000066400000000000000000000022031462611535200232010ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto/elliptic" "math/big" "sync" ) // This file holds ECC curves that are not supported by the main Go crypto/elliptic // library, but which have been observed in certificates in the wild. var initonce sync.Once var p192r1 *elliptic.CurveParams func initAllCurves() { initSECP192R1() } func initSECP192R1() { // See SEC-2, section 2.2.2 p192r1 = &elliptic.CurveParams{Name: "P-192"} p192r1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF", 16) p192r1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831", 16) p192r1.B, _ = new(big.Int).SetString("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1", 16) p192r1.Gx, _ = new(big.Int).SetString("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", 16) p192r1.Gy, _ = new(big.Int).SetString("07192B95FFC8DA78631011ED6B24CDD573F977A11E794811", 16) p192r1.BitSize = 192 } func secp192r1() elliptic.Curve { initonce.Do(initAllCurves) return p192r1 } google-certificate-transparency-go-2308f62/x509/curves_test.go000066400000000000000000000231571462611535200242530ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "math/big" "testing" ) func TestSECP192R1(t *testing.T) { curve := secp192r1() vectors := []struct { k string // base 10 x, y string // base 16 }{ // Test vectors from http://point-at-infinity.org/ecc/nisttv { k: "1", x: "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", y: "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811", }, { k: "2", x: "DAFEBF5828783F2AD35534631588A3F629A70FB16982A888", y: "DD6BDA0D993DA0FA46B27BBC141B868F59331AFA5C7E93AB", }, { k: "3", x: "76E32A2557599E6EDCD283201FB2B9AADFD0D359CBB263DA", y: "782C37E372BA4520AA62E0FED121D49EF3B543660CFD05FD", }, { k: "4", x: "35433907297CC378B0015703374729D7A4FE46647084E4BA", y: "A2649984F2135C301EA3ACB0776CD4F125389B311DB3BE32", }, { k: "5", x: "10BB8E9840049B183E078D9C300E1605590118EBDD7FF590", y: "31361008476F917BADC9F836E62762BE312B72543CCEAEA1", }, { k: "6", x: "A37ABC6C431F9AC398BF5BD1AA6678320ACE8ECB93D23F2A", y: "851B3CAEC99908DBFED7040A1BBDA90E081F7C5710BC68F0", }, { k: "7", x: "8DA75A1F75DDCD7660F923243060EDCE5DE37F007011FCFD", y: "57CB5FCF6860B35418240DB8FDB3C01DD4B702F96409FFB5", }, { k: "8", x: "2FA1F92D1ECCE92014771993CC14899D4B5977883397EDDE", y: "A338AFDEF78B7214273B8B5978EF733FF2DD8A8E9738F6C0", }, { k: "9", x: "818A4D308B1CABB74E9E8F2BA8D27C9E1D9D375AB980388F", y: "01D1AA5E208D87CD7C292F7CBB457CDF30EA542176C8E739", }, { k: "10", x: "AA7C4F9EF99E3E96D1AEDE2BD9238842859BB150D1FE9D85", y: "3212A36547EDC62901EE3658B2F4859460EB5EB2491397B0", }, { k: "11", x: "1C995995EB76324F1844F7164D22B652280940370628A2AA", y: "EF1765CE37E9EB73029F556400FA77BDB34CB8611AAA9C04", }, { k: "12", x: "1061343F3D456D0ECA013877F8C9E7B28FCCDCDA67EEB8AB", y: "5A064CAA2EA6B03798FEF8E3E7A48648681EAC020B27293F", }, { k: "13", x: "112AF141D33EFB9F2F68821E051E4EA004144A363C4A090A", y: "6E0CBE3BFC5293F72A2C1726E081E09E7F10A094432B1C1E", }, { k: "14", x: "13B9310646EBC93B591746B3F7C64E05DEE08843DE1081C1", y: "1EDCEA63B44142DD15F3B427EC41A1EC4FBACA95E186E6B4", }, { k: "15", x: "8C9595E63B56B633BA3546B2B5414DE736DE4A9E7578B1E7", y: "266B762A934F00C17CF387993AA566B6AD7537CDD98FC7B1", }, { k: "16", x: "B7310B4548FBFDBD29005092A5355BFCD99473733048AFDF", y: "FF9EAE9EDCD27C1E42D8585C4546D9491845C56629CF2290", }, { k: "17", x: "44275CD2E1F46DC3F9F57636C2B4213B8BB445930510FF8A", y: "EFAD8348FDE30C87DE438612A818E98D9B76A67AD25DDFD0", }, { k: "18", x: "C1B4DB0227210613A6CA15C428024E40B6513365D72591A3", y: "1E26B286BCA1D08F4FE8F801267DF9FD7782EC3EC3F47F53", }, { k: "19", x: "C0626BCF247DE5D307FD839238D72688774FC97A1CF8AD1B", y: "9CDC99D753973DC197E12778E829C804EC1A6B4E71FAA20A", }, { k: "20", x: "BB6F082321D34DBD786A1566915C6DD5EDF879AB0F5ADD67", y: "91E4DD8A77C4531C8B76DEF2E5339B5EB95D5D9479DF4C8D", }, { k: "112233445566778899", x: "81E6E0F14C9302C8A8DCA8A038B73165E9687D0490CD9F85", y: "F58067119EED8579388C4281DC645A27DB7764750E812477", }, { k: "112233445566778899112233445566778899", x: "B357B10AC985C891B29FB37DA56661CCCF50CEC21128D4F6", y: "BA20DC2FA1CC228D3C2D8B538C2177C2921884C6B7F0D96F", }, { k: "1618292094200346491064154703205151664562462359653015613567", x: "74FEC215F253C6BD845831E059B318C87F727B136A700B91", y: "4B702B15B126A703E7A7CEC3E0EC81F8DFCA73A59F5D88B9", }, { k: "1484605055214526729816930749766694384906446681761906688", x: "0C40230F9C4B8C0FD91F2C604FCBA9B87C2DFA153F010B4F", y: "5FC4F5771F467971B2C82752413833A68CE00F4A9A692B02", }, { k: "1569275434166462877105627261392580354519833538813866540831", x: "28783BBF6208E1FF0F965FD8DC0C26FF1D8E02B433EDF2F7", y: "A5852BBC44FD8164C1ABA9A3EC7A88E461D5D77ABD743E87", }, { k: "3138550867681922400546388175470823984762234518836963313664", x: "45DAF0A306121BDB3B82E734CB44FDF65C9930F0E4FD2068", y: "F039FACE58EB7DE34E3374ADB28DF81F019C4548BAA75B64", }, { k: "3138550119404545973088374812479323842475901485681169401600", x: "1D5EC85004EA2ABA905CEF98A818A8C3516D7CB69A6FD575", y: "4008F35F5820F66C902195644162E5AA231DD69C9E1ECC97", }, { k: "24519928471166604179655321383971467003990211439919824896", x: "F063727C2EA4D358AB02F6B0BEEB14DBEAF2E8A1DB3208EE", y: "427418C015553361769B6A0C42923C4CA103740B6DCD9703", }, { k: "46756768218837031708063422466358611246556475572231", x: "DC81D33CA6604B1EFE49386CD492979EF807B8BAEB8566E3", y: "D454247FF478514556333B3901C9F1CCC18DBC9AB938CFA0", }, { k: "3138502977207688322901699644928655553044791844086883549215", x: "D932741DF6AA0E1EED24279150436C752AA5ADCFD0698D72", y: "9759B6D2EF21D885E94CDFF219F17004D8763401DAB021B5", }, { k: "47890485652059026491391979477371914515865621847605503", x: "571477E9D9F2A628780742257F7250C4224C483B30F3A97E", y: "1AD35EE3177D22DD5F01B5A46FFDEC547B6A41786EBB8C8F", }, { k: "3138549376958826959341570842566593375326996431013993775615", x: "4C69939642792776C826DB8B4EBF4BD8C03FC9DFA2AEC822", y: "29BF35BE52A6036E07EBA5741CFEB4C143310216EF1B9A2E", }, { k: "6277101735386680763835789423176059013767194773182842284061", x: "BB6F082321D34DBD786A1566915C6DD5EDF879AB0F5ADD67", y: "6E1B2275883BACE37489210D1ACC64A046A2A26B8620B372", }, { k: "6277101735386680763835789423176059013767194773182842284062", x: "C0626BCF247DE5D307FD839238D72688774FC97A1CF8AD1B", y: "63236628AC68C23E681ED88717D637FA13E594B18E055DF5", }, { k: "6277101735386680763835789423176059013767194773182842284063", x: "C1B4DB0227210613A6CA15C428024E40B6513365D72591A3", y: "E1D94D79435E2F70B01707FED9820601887D13C13C0B80AC", }, { k: "6277101735386680763835789423176059013767194773182842284064", x: "44275CD2E1F46DC3F9F57636C2B4213B8BB445930510FF8A", y: "10527CB7021CF37821BC79ED57E71671648959852DA2202F", }, { k: "6277101735386680763835789423176059013767194773182842284065", x: "B7310B4548FBFDBD29005092A5355BFCD99473733048AFDF", y: "00615161232D83E1BD27A7A3BAB926B5E7BA3A99D630DD6F", }, { k: "6277101735386680763835789423176059013767194773182842284066", x: "8C9595E63B56B633BA3546B2B5414DE736DE4A9E7578B1E7", y: "D99489D56CB0FF3E830C7866C55A9948528AC8322670384E", }, { k: "6277101735386680763835789423176059013767194773182842284067", x: "13B9310646EBC93B591746B3F7C64E05DEE08843DE1081C1", y: "E123159C4BBEBD22EA0C4BD813BE5E12B045356A1E79194B", }, { k: "6277101735386680763835789423176059013767194773182842284068", x: "112AF141D33EFB9F2F68821E051E4EA004144A363C4A090A", y: "91F341C403AD6C08D5D3E8D91F7E1F6080EF5F6BBCD4E3E1", }, { k: "6277101735386680763835789423176059013767194773182842284069", x: "1061343F3D456D0ECA013877F8C9E7B28FCCDCDA67EEB8AB", y: "A5F9B355D1594FC86701071C185B79B697E153FDF4D8D6C0", }, { k: "6277101735386680763835789423176059013767194773182842284070", x: "1C995995EB76324F1844F7164D22B652280940370628A2AA", y: "10E89A31C816148CFD60AA9BFF0588414CB3479EE55563FB", }, { k: "6277101735386680763835789423176059013767194773182842284071", x: "AA7C4F9EF99E3E96D1AEDE2BD9238842859BB150D1FE9D85", y: "CDED5C9AB81239D6FE11C9A74D0B7A6A9F14A14DB6EC684F", }, { k: "6277101735386680763835789423176059013767194773182842284072", x: "818A4D308B1CABB74E9E8F2BA8D27C9E1D9D375AB980388F", y: "FE2E55A1DF72783283D6D08344BA831FCF15ABDE893718C6", }, { k: "6277101735386680763835789423176059013767194773182842284073", x: "2FA1F92D1ECCE92014771993CC14899D4B5977883397EDDE", y: "5CC7502108748DEBD8C474A687108CBF0D22757168C7093F", }, { k: "6277101735386680763835789423176059013767194773182842284074", x: "8DA75A1F75DDCD7660F923243060EDCE5DE37F007011FCFD", y: "A834A030979F4CABE7DBF247024C3FE12B48FD069BF6004A", }, { k: "6277101735386680763835789423176059013767194773182842284075", x: "A37ABC6C431F9AC398BF5BD1AA6678320ACE8ECB93D23F2A", y: "7AE4C3513666F7240128FBF5E44256F0F7E083A8EF43970F", }, { k: "6277101735386680763835789423176059013767194773182842284076", x: "10BB8E9840049B183E078D9C300E1605590118EBDD7FF590", y: "CEC9EFF7B8906E84523607C919D89D40CED48DABC331515E", }, { k: "6277101735386680763835789423176059013767194773182842284077", x: "35433907297CC378B0015703374729D7A4FE46647084E4BA", y: "5D9B667B0DECA3CFE15C534F88932B0DDAC764CEE24C41CD", }, { k: "6277101735386680763835789423176059013767194773182842284078", x: "76E32A2557599E6EDCD283201FB2B9AADFD0D359CBB263DA", y: "87D3C81C8D45BADF559D1F012EDE2B600C4ABC99F302FA02", }, { k: "6277101735386680763835789423176059013767194773182842284079", x: "DAFEBF5828783F2AD35534631588A3F629A70FB16982A888", y: "229425F266C25F05B94D8443EBE4796FA6CCE505A3816C54", }, { k: "6277101735386680763835789423176059013767194773182842284080", x: "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", y: "F8E6D46A003725879CEFEE1294DB32298C06885EE186B7EE", }, } for _, test := range vectors { t.Run(test.k, func(t *testing.T) { k, ok := new(big.Int).SetString(test.k, 10) if !ok { t.Fatalf("failed to parse k=%s", test.k) } wantX, ok := new(big.Int).SetString(test.x, 16) if !ok { t.Fatalf("failed to parse x=%s", test.x) } wantY, ok := new(big.Int).SetString(test.y, 16) if !ok { t.Fatalf("failed to parse y=%s", test.y) } gotX, gotY := curve.ScalarBaseMult(k.Bytes()) if gotX.Cmp(wantX) != 0 || gotY.Cmp(wantY) != 0 { t.Errorf("ScalarBaseMult()=(%v,%v), want (%v,%v)", gotX, gotY, wantX, wantY) } }) } } google-certificate-transparency-go-2308f62/x509/error.go000066400000000000000000000132141462611535200230270ustar00rootroot00000000000000package x509 import ( "bytes" "fmt" "strconv" "strings" ) // Error implements the error interface and describes a single error in an X.509 certificate or CRL. type Error struct { ID ErrorID Category ErrCategory Summary string Field string SpecRef string SpecText string // Fatal indicates that parsing has been aborted. Fatal bool } func (err Error) Error() string { var msg bytes.Buffer if err.ID != ErrInvalidID { if err.Fatal { msg.WriteRune('E') } else { msg.WriteRune('W') } msg.WriteString(fmt.Sprintf("%03d: ", err.ID)) } msg.WriteString(err.Summary) return msg.String() } // VerboseError creates a more verbose error string, including spec details. func (err Error) VerboseError() string { var msg bytes.Buffer msg.WriteString(err.Error()) if len(err.Field) > 0 || err.Category != UnknownCategory || len(err.SpecRef) > 0 || len(err.SpecText) > 0 { msg.WriteString(" (") needSep := false if len(err.Field) > 0 { msg.WriteString(err.Field) needSep = true } if err.Category != UnknownCategory { if needSep { msg.WriteString(": ") } msg.WriteString(err.Category.String()) needSep = true } if len(err.SpecRef) > 0 { if needSep { msg.WriteString(": ") } msg.WriteString(err.SpecRef) needSep = true } if len(err.SpecText) > 0 { if needSep { if len(err.SpecRef) > 0 { msg.WriteString(", ") } else { msg.WriteString(": ") } } msg.WriteString("'") msg.WriteString(err.SpecText) msg.WriteString("'") } msg.WriteString(")") } return msg.String() } // ErrCategory indicates the category of an x509.Error. type ErrCategory int // ErrCategory values. const ( UnknownCategory ErrCategory = iota // Errors in ASN.1 encoding InvalidASN1Encoding InvalidASN1Content InvalidASN1DER // Errors in ASN.1 relative to schema InvalidValueRange InvalidASN1Type UnexpectedAdditionalData // Errors in X.509 PoorlyFormedCertificate // Fails a SHOULD clause MalformedCertificate // Fails a MUST clause PoorlyFormedCRL // Fails a SHOULD clause MalformedCRL // Fails a MUST clause // Errors relative to CA/Browser Forum guidelines BaselineRequirementsFailure EVRequirementsFailure // Other errors InsecureAlgorithm UnrecognizedValue ) func (category ErrCategory) String() string { switch category { case InvalidASN1Encoding: return "Invalid ASN.1 encoding" case InvalidASN1Content: return "Invalid ASN.1 content" case InvalidASN1DER: return "Invalid ASN.1 distinguished encoding" case InvalidValueRange: return "Invalid value for range given in schema" case InvalidASN1Type: return "Invalid ASN.1 type for schema" case UnexpectedAdditionalData: return "Unexpected additional data present" case PoorlyFormedCertificate: return "Certificate does not comply with SHOULD clause in spec" case MalformedCertificate: return "Certificate does not comply with MUST clause in spec" case PoorlyFormedCRL: return "Certificate Revocation List does not comply with SHOULD clause in spec" case MalformedCRL: return "Certificate Revocation List does not comply with MUST clause in spec" case BaselineRequirementsFailure: return "Certificate does not comply with CA/BF baseline requirements" case EVRequirementsFailure: return "Certificate does not comply with CA/BF EV requirements" case InsecureAlgorithm: return "Certificate uses an insecure algorithm" case UnrecognizedValue: return "Certificate uses an unrecognized value" default: return fmt.Sprintf("Unknown (%d)", category) } } // ErrorID is an identifier for an x509.Error, to allow filtering. type ErrorID int // Errors implements the error interface and holds a collection of errors found in a certificate or CRL. type Errors struct { Errs []Error } // Error converts to a string. func (e *Errors) Error() string { return e.combineErrors(Error.Error) } // VerboseError creates a more verbose error string, including spec details. func (e *Errors) VerboseError() string { return e.combineErrors(Error.VerboseError) } // Fatal indicates whether e includes a fatal error func (e *Errors) Fatal() bool { return (e.FirstFatal() != nil) } // Empty indicates whether e has no errors. func (e *Errors) Empty() bool { if e == nil { return true } return len(e.Errs) == 0 } // FirstFatal returns the first fatal error in e, or nil // if there is no fatal error. func (e *Errors) FirstFatal() error { if e == nil { return nil } for _, err := range e.Errs { if err.Fatal { return err } } return nil } // AddID adds the Error identified by the given id to an x509.Errors. func (e *Errors) AddID(id ErrorID, args ...interface{}) { e.Errs = append(e.Errs, NewError(id, args...)) } func (e Errors) combineErrors(errfn func(Error) string) string { if len(e.Errs) == 0 { return "" } if len(e.Errs) == 1 { return errfn((e.Errs)[0]) } var msg bytes.Buffer msg.WriteString("Errors:") for _, err := range e.Errs { msg.WriteString("\n ") msg.WriteString(errfn(err)) } return msg.String() } // Filter creates a new Errors object with any entries from the filtered // list of IDs removed. func (e Errors) Filter(filtered []ErrorID) Errors { var results Errors eloop: for _, v := range e.Errs { for _, f := range filtered { if v.ID == f { break eloop } } results.Errs = append(results.Errs, v) } return results } // ErrorFilter builds a list of error IDs (suitable for use with Errors.Filter) from a comma-separated string. func ErrorFilter(ignore string) []ErrorID { var ids []ErrorID filters := strings.Split(ignore, ",") for _, f := range filters { v, err := strconv.Atoi(f) if err != nil { continue } ids = append(ids, ErrorID(v)) } return ids } google-certificate-transparency-go-2308f62/x509/error_test.go000066400000000000000000000156301462611535200240720ustar00rootroot00000000000000package x509_test import ( "errors" "fmt" "reflect" "testing" "github.com/google/certificate-transparency-go/x509" ) func TestErrors(t *testing.T) { var tests = []struct { errs *x509.Errors want string wantVerbose string wantFatal bool wantFirstFatal error }{ { errs: &x509.Errors{Errs: []x509.Error{ {Summary: "Error", Field: "a.b.c"}, }}, want: "Error", wantVerbose: "Error (a.b.c)", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecRef: "RFC5280 s4.1.2.2", SpecText: "The serial number MUST be a positive integer", Category: x509.MalformedCertificate, }, }}, want: "Error", wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: RFC5280 s4.1.2.2, 'The serial number MUST be a positive integer')", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecRef: "RFC5280 s4.1.2.2", SpecText: "The serial number MUST be a positive integer", }, }}, want: "Error", wantVerbose: "Error (a.b.c: RFC5280 s4.1.2.2, 'The serial number MUST be a positive integer')", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecRef: "RFC5280 s4.1.2.2", Category: x509.MalformedCertificate, }, }}, want: "Error", wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: RFC5280 s4.1.2.2)", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecText: "The serial number MUST be a positive integer", Category: x509.MalformedCertificate, }, }}, want: "Error", wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: 'The serial number MUST be a positive integer')", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecRef: "RFC5280 s4.1.2.2", }, }}, want: "Error", wantVerbose: "Error (a.b.c: RFC5280 s4.1.2.2)", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", SpecText: "The serial number MUST be a positive integer", }, }}, want: "Error", wantVerbose: "Error (a.b.c: 'The serial number MUST be a positive integer')", }, { errs: &x509.Errors{Errs: []x509.Error{ { Summary: "Error", Field: "a.b.c", Category: x509.MalformedCertificate, }, }}, want: "Error", wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec)", }, { errs: &x509.Errors{Errs: []x509.Error{ {Summary: "Error"}, }}, want: "Error", wantVerbose: "Error", }, { errs: &x509.Errors{Errs: []x509.Error{ {Summary: "Error\nwith newline", Field: "x", Category: x509.InvalidASN1DER}, }}, want: "Error\nwith newline", wantVerbose: "Error\nwith newline (x: Invalid ASN.1 distinguished encoding)", }, { errs: &x509.Errors{Errs: []x509.Error{ {Summary: "Error1", Field: "a.b.c"}, {Summary: "Error2", Field: "a.b.c.d"}, {Summary: "Error3", Field: "x.y.z"}, }}, want: "Errors:\n Error1\n Error2\n Error3", wantVerbose: "Errors:\n Error1 (a.b.c)\n Error2 (a.b.c.d)\n Error3 (x.y.z)", }, { errs: &x509.Errors{Errs: []x509.Error{ {Summary: "Error1", Field: "a.b.c"}, {Summary: "Error2", Field: "a.b.c.d", Fatal: true}, {Summary: "Error3", Field: "x.y.z"}, }}, want: "Errors:\n Error1\n Error2\n Error3", wantVerbose: "Errors:\n Error1 (a.b.c)\n Error2 (a.b.c.d)\n Error3 (x.y.z)", wantFatal: true, }, } for _, test := range tests { if got := test.errs.Error(); got != test.want { t.Errorf("Errors(%+v).Error()=%q; want %q", test.errs, got, test.want) } if got := test.errs.VerboseError(); got != test.wantVerbose { t.Errorf("Errors(%+v).VerboseError()=%q; want %q", test.errs, got, test.wantVerbose) } if got := test.errs.Fatal(); got != test.wantFatal { t.Errorf("Errors(%+v).Fatal()=%v; want %v", test.errs, got, test.wantFatal) } } } func TestErrorsRawAppend(t *testing.T) { // Pointer receiver can be nil. var errs *x509.Errors if got, want := errs.Empty(), true; got != want { t.Errorf("Errors().Empty()=%t; want %t", got, want) } // Pointer receiver can point to empty struct. errs = &x509.Errors{} if got, want := errs.Error(), ""; got != want { t.Errorf("Errors().Error()=%q; want %q", got, want) } if got, want := errs.Empty(), true; got != want { t.Errorf("Errors().Empty()=%t; want %t", got, want) } errs.Errs = append(errs.Errs, x509.Error{ Summary: "Error", Field: "a.b.c", SpecRef: "RFC5280 s4.1.2.2"}) if got, want := errs.VerboseError(), "Error (a.b.c: RFC5280 s4.1.2.2)"; got != want { t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want) } if got, want := errs.Empty(), false; got != want { t.Errorf("Errors().Empty()=%t; want %t", got, want) } } func TestErrorsFilter(t *testing.T) { var errs x509.Errors id := x509.ErrMaxID + 2 errs.AddID(id, "arg1", 2, "arg3") baseErr := errs.Error() errs.AddID(x509.ErrMaxID + 1) if got, want := errs.Error(), fmt.Sprintf("Errors:\n %s\n E%03d: Unknown error ID %v: args []", baseErr, x509.ErrMaxID+1, x509.ErrMaxID+1); got != want { t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want) } errList := fmt.Sprintf("%d, %d", x509.ErrMaxID+1, x509.ErrMaxID+1) filter := x509.ErrorFilter(errList) errs2 := errs.Filter(filter) if got, want := errs2.Error(), baseErr; got != want { t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want) } } func TestErrorsAppend(t *testing.T) { errA := errors.New("A") errB := errors.New("B") errC := errors.New("C") errD := errors.New("D") tests := []struct { left, right, want *x509.NonFatalErrors }{ { left: &x509.NonFatalErrors{Errors: []error{errA}}, right: &x509.NonFatalErrors{Errors: []error{errB}}, want: &x509.NonFatalErrors{Errors: []error{errA, errB}}, }, { left: &x509.NonFatalErrors{Errors: []error{errA, errB}}, right: &x509.NonFatalErrors{Errors: []error{errC, errD}}, want: &x509.NonFatalErrors{Errors: []error{errA, errB, errC, errD}}, }, { left: nil, right: &x509.NonFatalErrors{Errors: []error{errC, errD}}, want: &x509.NonFatalErrors{Errors: []error{errC, errD}}, }, { left: &x509.NonFatalErrors{Errors: []error{errC, errD}}, right: nil, want: &x509.NonFatalErrors{Errors: []error{errC, errD}}, }, { left: nil, right: nil, want: nil, }, { left: &x509.NonFatalErrors{Errors: []error{}}, right: nil, want: &x509.NonFatalErrors{Errors: []error{}}, }, } for _, test := range tests { got := test.left.Append(test.right) if !reflect.DeepEqual(got, test.want) { t.Errorf("(%+v).Append(%+v)=%v, want %v", test.left, test.right, got, test.want) } } } google-certificate-transparency-go-2308f62/x509/errors.go000066400000000000000000000235561462611535200232240ustar00rootroot00000000000000package x509 import "fmt" // To preserve error IDs, only append to this list, never insert. const ( ErrInvalidID ErrorID = iota ErrInvalidCertList ErrTrailingCertList ErrUnexpectedlyCriticalCertListExtension ErrUnexpectedlyNonCriticalCertListExtension ErrInvalidCertListAuthKeyID ErrTrailingCertListAuthKeyID ErrInvalidCertListIssuerAltName ErrInvalidCertListCRLNumber ErrTrailingCertListCRLNumber ErrNegativeCertListCRLNumber ErrInvalidCertListDeltaCRL ErrTrailingCertListDeltaCRL ErrNegativeCertListDeltaCRL ErrInvalidCertListIssuingDP ErrTrailingCertListIssuingDP ErrCertListIssuingDPMultipleTypes ErrCertListIssuingDPInvalidFullName ErrInvalidCertListFreshestCRL ErrInvalidCertListAuthInfoAccess ErrTrailingCertListAuthInfoAccess ErrUnhandledCriticalCertListExtension ErrUnexpectedlyCriticalRevokedCertExtension ErrUnexpectedlyNonCriticalRevokedCertExtension ErrInvalidRevocationReason ErrTrailingRevocationReason ErrInvalidRevocationInvalidityDate ErrTrailingRevocationInvalidityDate ErrInvalidRevocationIssuer ErrUnhandledCriticalRevokedCertExtension ErrMaxID ) // idToError gives a template x509.Error for each defined ErrorID; where the Summary // field may hold format specifiers that take field parameters. var idToError map[ErrorID]Error var errorInfo = []Error{ { ID: ErrInvalidCertList, Summary: "x509: failed to parse CertificateList: %v", Field: "CertificateList", SpecRef: "RFC 5280 s5.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertList, Summary: "x509: trailing data after CertificateList", Field: "CertificateList", SpecRef: "RFC 5280 s5.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrUnexpectedlyCriticalCertListExtension, Summary: "x509: certificate list extension %v marked critical but expected to be non-critical", Field: "tbsCertList.crlExtensions.*.critical", SpecRef: "RFC 5280 s5.2", Category: MalformedCRL, }, { ID: ErrUnexpectedlyNonCriticalCertListExtension, Summary: "x509: certificate list extension %v marked non-critical but expected to be critical", Field: "tbsCertList.crlExtensions.*.critical", SpecRef: "RFC 5280 s5.2", Category: MalformedCRL, }, { ID: ErrInvalidCertListAuthKeyID, Summary: "x509: failed to unmarshal certificate-list authority key-id: %v", Field: "tbsCertList.crlExtensions.*.AuthorityKeyIdentifier", SpecRef: "RFC 5280 s5.2.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertListAuthKeyID, Summary: "x509: trailing data after certificate list auth key ID", Field: "tbsCertList.crlExtensions.*.AuthorityKeyIdentifier", SpecRef: "RFC 5280 s5.2.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidCertListIssuerAltName, Summary: "x509: failed to parse CRL issuer alt name: %v", Field: "tbsCertList.crlExtensions.*.IssuerAltName", SpecRef: "RFC 5280 s5.2.2", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidCertListCRLNumber, Summary: "x509: failed to unmarshal certificate-list crl-number: %v", Field: "tbsCertList.crlExtensions.*.CRLNumber", SpecRef: "RFC 5280 s5.2.3", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertListCRLNumber, Summary: "x509: trailing data after certificate list crl-number", Field: "tbsCertList.crlExtensions.*.CRLNumber", SpecRef: "RFC 5280 s5.2.3", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrNegativeCertListCRLNumber, Summary: "x509: negative certificate list crl-number: %d", Field: "tbsCertList.crlExtensions.*.CRLNumber", SpecRef: "RFC 5280 s5.2.3", Category: MalformedCRL, Fatal: true, }, { ID: ErrInvalidCertListDeltaCRL, Summary: "x509: failed to unmarshal certificate-list delta-crl: %v", Field: "tbsCertList.crlExtensions.*.BaseCRLNumber", SpecRef: "RFC 5280 s5.2.4", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertListDeltaCRL, Summary: "x509: trailing data after certificate list delta-crl", Field: "tbsCertList.crlExtensions.*.BaseCRLNumber", SpecRef: "RFC 5280 s5.2.4", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrNegativeCertListDeltaCRL, Summary: "x509: negative certificate list base-crl-number: %d", Field: "tbsCertList.crlExtensions.*.BaseCRLNumber", SpecRef: "RFC 5280 s5.2.4", Category: MalformedCRL, Fatal: true, }, { ID: ErrInvalidCertListIssuingDP, Summary: "x509: failed to unmarshal certificate list issuing distribution point: %v", Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint", SpecRef: "RFC 5280 s5.2.5", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertListIssuingDP, Summary: "x509: trailing data after certificate list issuing distribution point", Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint", SpecRef: "RFC 5280 s5.2.5", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrCertListIssuingDPMultipleTypes, Summary: "x509: multiple cert types set in issuing-distribution-point: user:%v CA:%v attr:%v", Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint", SpecRef: "RFC 5280 s5.2.5", SpecText: "at most one of onlyContainsUserCerts, onlyContainsCACerts, and onlyContainsAttributeCerts may be set to TRUE.", Category: MalformedCRL, Fatal: true, }, { ID: ErrCertListIssuingDPInvalidFullName, Summary: "x509: failed to parse CRL issuing-distribution-point fullName: %v", Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint.distributionPoint", SpecRef: "RFC 5280 s5.2.5", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidCertListFreshestCRL, Summary: "x509: failed to unmarshal certificate list freshestCRL: %v", Field: "tbsCertList.crlExtensions.*.FreshestCRL", SpecRef: "RFC 5280 s5.2.6", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidCertListAuthInfoAccess, Summary: "x509: failed to unmarshal certificate list authority info access: %v", Field: "tbsCertList.crlExtensions.*.AuthorityInfoAccess", SpecRef: "RFC 5280 s5.2.7", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingCertListAuthInfoAccess, Summary: "x509: trailing data after certificate list authority info access", Field: "tbsCertList.crlExtensions.*.AuthorityInfoAccess", SpecRef: "RFC 5280 s5.2.7", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrUnhandledCriticalCertListExtension, Summary: "x509: unhandled critical extension in certificate list: %v", Field: "tbsCertList.revokedCertificates.crlExtensions.*", SpecRef: "RFC 5280 s5.2", SpecText: "If a CRL contains a critical extension that the application cannot process, then the application MUST NOT use that CRL to determine the status of certificates.", Category: MalformedCRL, Fatal: true, }, { ID: ErrUnexpectedlyCriticalRevokedCertExtension, Summary: "x509: revoked certificate extension %v marked critical but expected to be non-critical", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.critical", SpecRef: "RFC 5280 s5.3", Category: MalformedCRL, }, { ID: ErrUnexpectedlyNonCriticalRevokedCertExtension, Summary: "x509: revoked certificate extension %v marked non-critical but expected to be critical", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.critical", SpecRef: "RFC 5280 s5.3", Category: MalformedCRL, }, { ID: ErrInvalidRevocationReason, Summary: "x509: failed to parse revocation reason: %v", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CRLReason", SpecRef: "RFC 5280 s5.3.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingRevocationReason, Summary: "x509: trailing data after revoked certificate reason", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CRLReason", SpecRef: "RFC 5280 s5.3.1", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidRevocationInvalidityDate, Summary: "x509: failed to parse revoked certificate invalidity date: %v", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.InvalidityDate", SpecRef: "RFC 5280 s5.3.2", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrTrailingRevocationInvalidityDate, Summary: "x509: trailing data after revoked certificate invalidity date", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.InvalidityDate", SpecRef: "RFC 5280 s5.3.2", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrInvalidRevocationIssuer, Summary: "x509: failed to parse revocation issuer %v", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CertificateIssuer", SpecRef: "RFC 5280 s5.3.3", Category: InvalidASN1Content, Fatal: true, }, { ID: ErrUnhandledCriticalRevokedCertExtension, Summary: "x509: unhandled critical extension in revoked certificate: %v", Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*", SpecRef: "RFC 5280 s5.3", SpecText: "If a CRL contains a critical CRL entry extension that the application cannot process, then the application MUST NOT use that CRL to determine the status of any certificates.", Category: MalformedCRL, Fatal: true, }, } func init() { idToError = make(map[ErrorID]Error, len(errorInfo)) for _, info := range errorInfo { idToError[info.ID] = info } } // NewError builds a new x509.Error based on the template for the given id. func NewError(id ErrorID, args ...interface{}) Error { var err Error if id >= ErrMaxID { err.ID = id err.Summary = fmt.Sprintf("Unknown error ID %v: args %+v", id, args) err.Fatal = true } else { err = idToError[id] err.Summary = fmt.Sprintf(err.Summary, args...) } return err } google-certificate-transparency-go-2308f62/x509/errors_test.go000066400000000000000000000003241462611535200242470ustar00rootroot00000000000000package x509 import ( "testing" ) func TestTemplateIDs(t *testing.T) { for id, template := range idToError { if template.ID != id { t.Errorf("idToError[%v].id=%v; want %v", id, template.ID, id) } } } google-certificate-transparency-go-2308f62/x509/example_test.go000066400000000000000000000125721462611535200243760ustar00rootroot00000000000000// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509_test import ( "crypto/dsa" "crypto/ecdsa" "crypto/rsa" "encoding/pem" "fmt" "github.com/google/certificate-transparency-go/x509" "golang.org/x/crypto/ed25519" ) func ExampleCertificate_Verify() { // Verifying with a custom list of root certificates. const rootPEM = ` -----BEGIN CERTIFICATE----- MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7 qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY /iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/ zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6 yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx -----END CERTIFICATE-----` const certPEM = ` -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfRrObuSW5T7q 5CnSEqefEmtH4CCv6+5EckuriNr1CjfVvqzwfAhopXkLrq45EQm8vkmf7W96XJhC 7ZM0dYi1/qOCAU8wggFLMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAa BgNVHREEEzARgg9tYWlsLmdvb2dsZS5jb20wCwYDVR0PBAQDAgeAMGgGCCsGAQUF BwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcy LmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5jb20vb2Nz cDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/BAIwADAf BgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHSAEEDAOMAwGCisG AQQB1nkCBQEwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29nbGUuY29t L0dJQUcyLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAH6RYHxHdcGpMpFE3oxDoFnP+ gtuBCHan2yE2GRbJ2Cw8Lw0MmuKqHlf9RSeYfd3BXeKkj1qO6TVKwCh+0HdZk283 TZZyzmEOyclm3UGFYe82P/iDFt+CeQ3NpmBg+GoaVCuWAARJN/KfglbLyyYygcQq 0SgeDh8dRKUiaW3HQSoYvTvdTuqzwK4CXsr3b5/dAOY8uMuG/IAR3FgwTbZ1dtoW RvOTa8hYiU6A475WuZKyEHcwnGYe57u2I2KbMgcKjPniocj4QzgYsVAVKW3IwaOh yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA== -----END CERTIFICATE-----` // First, create the set of root certificates. For this example we only // have one. It's also possible to omit this in order to use the // default root set of the current operating system. roots := x509.NewCertPool() ok := roots.AppendCertsFromPEM([]byte(rootPEM)) if !ok { panic("failed to parse root certificate") } block, _ := pem.Decode([]byte(certPEM)) if block == nil { panic("failed to parse certificate PEM") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { panic("failed to parse certificate: " + err.Error()) } opts := x509.VerifyOptions{ DNSName: "mail.google.com", Roots: roots, } if _, err := cert.Verify(opts); err != nil { panic("failed to verify certificate: " + err.Error()) } } func ExampleParsePKIXPublicKey() { const pubPEM = ` -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlRuRnThUjU8/prwYxbty WPT9pURI3lbsKMiB6Fn/VHOKE13p4D8xgOCADpdRagdT6n4etr9atzDKUSvpMtR3 CP5noNc97WiNCggBjVWhs7szEe8ugyqF23XwpHQ6uV1LKH50m92MbOWfCtjU9p/x qhNpQQ1AZhqNy5Gevap5k8XzRmjSldNAFZMY7Yv3Gi+nyCwGwpVtBUwhuLzgNFK/ yDtw2WcWmUU7NuC8Q6MWvPebxVtCfVp/iQU6q60yyt6aGOBkhAX0LpKAEhKidixY nP9PNVBvxgu3XZ4P36gZV6+ummKdBVnc3NqwBLu5+CcdRdusmHPHd5pHf4/38Z3/ 6qU2a/fPvWzceVTEgZ47QjFMTCTmCwNt29cvi7zZeQzjtwQgn4ipN9NibRH/Ax/q TbIzHfrJ1xa2RteWSdFjwtxi9C20HUkjXSeI4YlzQMH0fPX6KCE7aVePTOnB69I/ a9/q96DiXZajwlpq3wFctrs1oXqBp5DVrCIj8hU2wNgB7LtQ1mCtsYz//heai0K9 PhE4X6hiE0YmeAZjR0uHl8M/5aW9xCoJ72+12kKpWAa0SFRWLy6FejNYCYpkupVJ yecLk/4L1W0l6jQQZnWErXZYe0PNFcmwGXy1Rep83kfBRNKRy5tvocalLlwXLdUk AIU+2GKjyT3iMuzZxxFxPFMCAwEAAQ== -----END PUBLIC KEY-----` block, _ := pem.Decode([]byte(pubPEM)) if block == nil { panic("failed to parse PEM block containing the public key") } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { panic("failed to parse DER encoded public key: " + err.Error()) } switch pub := pub.(type) { case *rsa.PublicKey: fmt.Println("pub is of type RSA:", pub) case *dsa.PublicKey: fmt.Println("pub is of type DSA:", pub) case *ecdsa.PublicKey: fmt.Println("pub is of type ECDSA:", pub) case ed25519.PublicKey: fmt.Println("pub is of type Ed25519:", pub) default: panic("unknown type of public key") } } google-certificate-transparency-go-2308f62/x509/fuzz_test.go000066400000000000000000000044461462611535200237420ustar00rootroot00000000000000// Copyright 2024 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509 import ( "testing" ) func FuzzParseECPrivateKeyTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseECPrivateKey(data) }) } func FuzzParsePKIXPublicKeyTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParsePKIXPublicKey(data) }) } func FuzzParseTBSCertificateTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseTBSCertificate(data) }) } func FuzzParseCertificateTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCertificate(data) }) } func FuzzParseCertificatesTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCertificates(data) }) } func FuzzParseCRLTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCRL(data) }) } func FuzzParseDERCRLTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseDERCRL(data) }) } func FuzzParseCertificateRequestTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCertificateRequest(data) }) } func FuzzParsePKCS8PrivateKeyest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParsePKCS8PrivateKey(data) }) } func FuzzParsePKCS1PrivateKeyTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParsePKCS1PrivateKey(data) }) } func FuzzParsePKCS1PublicKeyTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParsePKCS1PublicKey(data) }) } func FuzzParseCertificateListTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCertificateList(data) }) } func FuzzParseCertificateListDERTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _, _ = ParseCertificateListDER(data) }) } google-certificate-transparency-go-2308f62/x509/name_constraints_test.go000066400000000000000000001317701462611535200263140ustar00rootroot00000000000000// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/hex" "encoding/pem" "fmt" "math/big" "net" "net/url" "os" "os/exec" "strconv" "strings" "sync" "testing" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" ) const ( // testNameConstraintsAgainstOpenSSL can be set to true to run tests // against the system OpenSSL. This is disabled by default because Go // cannot depend on having OpenSSL installed at testing time. testNameConstraintsAgainstOpenSSL = false // debugOpenSSLFailure can be set to true, when // testNameConstraintsAgainstOpenSSL is also true, to cause // intermediate files to be preserved for debugging. debugOpenSSLFailure = false ) type nameConstraintsTest struct { roots []constraintsSpec intermediates [][]constraintsSpec leaf leafSpec requestedEKUs []ExtKeyUsage expectedError string noOpenSSL bool ignoreCN bool } type constraintsSpec struct { ok []string bad []string ekus []string cn string } type leafSpec struct { sans []string ekus []string cn string } var nameConstraintsTests = []nameConstraintsTest{ // #0: dummy test for the certificate generation process itself. { roots: make([]constraintsSpec, 1), leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #1: dummy test for the certificate generation process itself: single // level of intermediate. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #2: dummy test for the certificate generation process itself: two // levels of intermediates. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #3: matching DNS constraint in root { roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #4: matching DNS constraint in intermediate. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ok: []string{"dns:example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #5: .example.com only matches subdomains. { roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, expectedError: "\"example.com\" is not permitted", }, // #6: .example.com matches subdomains. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ok: []string{"dns:.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.example.com"}, }, }, // #7: .example.com matches multiple levels of subdomains { roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:foo.bar.example.com"}, }, }, // #8: specifying a permitted list of names does not exclude other name // types { roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:10.1.1.1"}, }, }, // #9: specifying a permitted list of names does not exclude other name // types { roots: []constraintsSpec{ { ok: []string{"ip:10.0.0.0/8"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #10: intermediates can try to permit other names, which isn't // forbidden if the leaf doesn't mention them. I.e. name constraints // apply to names, not constraints themselves. { roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, }, }, intermediates: [][]constraintsSpec{ { { ok: []string{"dns:example.com", "dns:foo.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #11: intermediates cannot add permitted names that the root doesn't // grant them. { roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, }, }, intermediates: [][]constraintsSpec{ { { ok: []string{"dns:example.com", "dns:foo.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.com"}, }, expectedError: "\"foo.com\" is not permitted", }, // #12: intermediates can further limit their scope if they wish. { roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { { ok: []string{"dns:.bar.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.bar.example.com"}, }, }, // #13: intermediates can further limit their scope and that limitation // is effective { roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { { ok: []string{"dns:.bar.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.notbar.example.com"}, }, expectedError: "\"foo.notbar.example.com\" is not permitted", }, // #14: roots can exclude subtrees and that doesn't affect other names. { roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:foo.com"}, }, }, // #15: roots exclusions are effective. { roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:foo.example.com"}, }, expectedError: "\"foo.example.com\" is excluded", }, // #16: intermediates can also exclude names and that doesn't affect // other names. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { bad: []string{"dns:.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.com"}, }, }, // #17: intermediate exclusions are effective. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { bad: []string{"dns:.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.example.com"}, }, expectedError: "\"foo.example.com\" is excluded", }, // #18: having an exclusion doesn't prohibit other types of names. { roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:foo.com", "ip:10.1.1.1"}, }, }, // #19: IP-based exclusions are permitted and don't affect unrelated IP // addresses. { roots: []constraintsSpec{ { bad: []string{"ip:10.0.0.0/8"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:192.168.1.1"}, }, }, // #20: IP-based exclusions are effective { roots: []constraintsSpec{ { bad: []string{"ip:10.0.0.0/8"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:10.0.0.1"}, }, expectedError: "\"10.0.0.1\" is excluded", }, // #21: intermediates can further constrain IP ranges. { roots: []constraintsSpec{ { bad: []string{"ip:0.0.0.0/1"}, }, }, intermediates: [][]constraintsSpec{ { { bad: []string{"ip:11.0.0.0/8"}, }, }, }, leaf: leafSpec{ sans: []string{"ip:11.0.0.1"}, }, expectedError: "\"11.0.0.1\" is excluded", }, // #22: when multiple intermediates are present, chain building can // avoid intermediates with incompatible constraints. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ok: []string{"dns:.foo.com"}, }, { ok: []string{"dns:.example.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.example.com"}, }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, // #23: (same as the previous test, but in the other order in ensure // that we don't pass it by luck.) { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ok: []string{"dns:.example.com"}, }, { ok: []string{"dns:.foo.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:foo.example.com"}, }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, // #24: when multiple roots are valid, chain building can avoid roots // with incompatible constraints. { roots: []constraintsSpec{ {}, { ok: []string{"dns:foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, // #25: (same as the previous test, but in the other order in ensure // that we don't pass it by luck.) { roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, }, {}, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, // #26: chain building can find a valid path even with multiple levels // of alternative intermediates and alternative roots. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, }, { ok: []string{"dns:example.com"}, }, {}, }, intermediates: [][]constraintsSpec{ { {}, { ok: []string{"dns:foo.com"}, }, }, { {}, { ok: []string{"dns:foo.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:bar.com"}, }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, // #27: chain building doesn't get stuck when there is no valid path. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, }, { ok: []string{"dns:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, { ok: []string{"dns:foo.com"}, }, }, { { ok: []string{"dns:bar.com"}, }, { ok: []string{"dns:foo.com"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:bar.com"}, }, expectedError: "\"bar.com\" is not permitted", }, // #28: unknown name types don't cause a problem without constraints. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"unknown:"}, }, }, // #29: unknown name types are allowed even in constrained chains. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"unknown:"}, }, }, // #30: without SANs, a certificate with a CN is rejected in a constrained chain. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{}, cn: "foo.com", }, expectedError: "leaf doesn't have a SAN extension", }, // #31: IPv6 addresses work in constraints: roots can permit them as // expected. { roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:abcd:1234::"}, }, }, // #32: IPv6 addresses work in constraints: root restrictions are // effective. { roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:1234:abcd::"}, }, expectedError: "\"2000:1234:abcd::\" is not permitted", }, // #33: An IPv6 permitted subtree doesn't affect DNS names. { roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:abcd::", "dns:foo.com"}, }, }, // #34: IPv6 exclusions don't affect unrelated addresses. { roots: []constraintsSpec{ { bad: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:1234::"}, }, }, // #35: IPv6 exclusions are effective. { roots: []constraintsSpec{ { bad: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:abcd::"}, }, expectedError: "\"2000:abcd::\" is excluded", }, // #36: IPv6 constraints do not permit IPv4 addresses. { roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:10.0.0.1"}, }, expectedError: "\"10.0.0.1\" is not permitted", }, // #37: IPv4 constraints do not permit IPv6 addresses. { roots: []constraintsSpec{ { ok: []string{"ip:10.0.0.0/8"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:2000:abcd::"}, }, expectedError: "\"2000:abcd::\" is not permitted", }, // #38: an exclusion of an unknown type doesn't affect other names. { roots: []constraintsSpec{ { bad: []string{"unknown:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #39: a permitted subtree of an unknown type doesn't affect other // name types. { roots: []constraintsSpec{ { ok: []string{"unknown:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #40: exact email constraints work { roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, }, // #41: exact email constraints are effective { roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:bar@example.com"}, }, expectedError: "\"bar@example.com\" is not permitted", }, // #42: email canonicalisation works. { roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:\"\\f\\o\\o\"@example.com"}, }, noOpenSSL: true, // OpenSSL doesn't canonicalise email addresses before matching }, // #43: limiting email addresses to a host works. { roots: []constraintsSpec{ { ok: []string{"email:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, }, // #44: a leading dot matches hosts one level deep { roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@sub.example.com"}, }, }, // #45: a leading dot does not match the host itself { roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, expectedError: "\"foo@example.com\" is not permitted", }, // #46: a leading dot also matches two (or more) levels deep. { roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@sub.sub.example.com"}, }, }, // #47: the local part of an email is case-sensitive { roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:Foo@example.com"}, }, expectedError: "\"Foo@example.com\" is not permitted", }, // #48: the domain part of an email is not case-sensitive { roots: []constraintsSpec{ { ok: []string{"email:foo@EXAMPLE.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, }, // #49: the domain part of a DNS constraint is also not case-sensitive. { roots: []constraintsSpec{ { ok: []string{"dns:EXAMPLE.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #50: URI constraints only cover the host part of the URI { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{ "uri:http://example.com/bar", "uri:http://example.com:8080/", "uri:https://example.com/wibble#bar", }, }, }, // #51: URIs with IPs are rejected { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://1.2.3.4/"}, }, expectedError: "URI with IP", }, // #52: URIs with IPs and ports are rejected { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://1.2.3.4:43/"}, }, expectedError: "URI with IP", }, // #53: URIs with IPv6 addresses are also rejected { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://[2006:abcd::1]/"}, }, expectedError: "URI with IP", }, // #54: URIs with IPv6 addresses with ports are also rejected { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://[2006:abcd::1]:16/"}, }, expectedError: "URI with IP", }, // #55: URI constraints are effective { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://bar.com/"}, }, expectedError: "\"http://bar.com/\" is not permitted", }, // #56: URI constraints are effective { roots: []constraintsSpec{ { bad: []string{"uri:foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://foo.com/"}, }, expectedError: "\"http://foo.com/\" is excluded", }, // #57: URI constraints can allow subdomains { roots: []constraintsSpec{ { ok: []string{"uri:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:http://www.foo.com/"}, }, }, // #58: excluding an IPv4-mapped-IPv6 address doesn't affect the IPv4 // version of that address. { roots: []constraintsSpec{ { bad: []string{"ip:::ffff:1.2.3.4/128"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:1.2.3.4"}, }, }, // #59: a URI constraint isn't matched by a URN. { roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:urn:example"}, }, expectedError: "URI with empty host", }, // #60: excluding all IPv6 addresses doesn't exclude all IPv4 addresses // too, even though IPv4 is mapped into the IPv6 range. { roots: []constraintsSpec{ { ok: []string{"ip:1.2.3.0/24"}, bad: []string{"ip:::0/0"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"ip:1.2.3.4"}, }, }, // #61: omitting extended key usage in a CA certificate implies that // any usage is ok. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth", "other"}, }, }, // #62: The “any” EKU also means that any usage is ok. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ekus: []string{"any"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth", "other"}, }, }, // #63: An intermediate with enumerated EKUs causes a failure if we // test for an EKU not in that set. (ServerAuth is required by // default.) { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ekus: []string{"email"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth"}, }, expectedError: "incompatible key usage", }, // #64: an unknown EKU in the leaf doesn't break anything, even if it's not // correctly nested. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ekus: []string{"email"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"other"}, }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageAny}, }, // #65: trying to add extra permitted key usages in an intermediate // (after a limitation in the root) is acceptable so long as the leaf // certificate doesn't use them. { roots: []constraintsSpec{ { ekus: []string{"serverAuth"}, }, }, intermediates: [][]constraintsSpec{ { { ekus: []string{"serverAuth", "email"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth"}, }, }, // #66: EKUs in roots are not ignored. { roots: []constraintsSpec{ { ekus: []string{"email"}, }, }, intermediates: [][]constraintsSpec{ { { ekus: []string{"serverAuth"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth"}, }, expectedError: "incompatible key usage", }, // #67: in order to support COMODO chains, SGC key usages permit // serverAuth and clientAuth. { roots: []constraintsSpec{ {}, }, intermediates: [][]constraintsSpec{ { { ekus: []string{"netscapeSGC"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth", "clientAuth"}, }, }, // #68: in order to support COMODO chains, SGC key usages permit // serverAuth and clientAuth. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ekus: []string{"msSGC"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth", "clientAuth"}, }, }, // #69: an empty DNS constraint should allow anything. { roots: []constraintsSpec{ { ok: []string{"dns:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, // #70: an empty DNS constraint should also reject everything. { roots: []constraintsSpec{ { bad: []string{"dns:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, }, expectedError: "\"example.com\" is excluded", }, // #71: an empty email constraint should allow anything { roots: []constraintsSpec{ { ok: []string{"email:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, }, // #72: an empty email constraint should also reject everything. { roots: []constraintsSpec{ { bad: []string{"email:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:foo@example.com"}, }, expectedError: "\"foo@example.com\" is excluded", }, // #73: an empty URI constraint should allow anything { roots: []constraintsSpec{ { ok: []string{"uri:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:https://example.com/test"}, }, }, // #74: an empty URI constraint should also reject everything. { roots: []constraintsSpec{ { bad: []string{"uri:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"uri:https://example.com/test"}, }, expectedError: "\"https://example.com/test\" is excluded", }, // #75: serverAuth in a leaf shouldn't permit clientAuth when requested in // VerifyOptions. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"serverAuth"}, }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth}, expectedError: "incompatible key usage", }, // #76: However, MSSGC in a leaf should match a request for serverAuth. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"msSGC"}, }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, // An invalid DNS SAN should be detected only at validation time so // that we can process CA certificates in the wild that have invalid SANs. // See https://github.com/golang/go/issues/23995 // #77: an invalid DNS or mail SAN will not be detected if name constraint // checking is not triggered. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:this is invalid", "email:this @ is invalid"}, }, }, // #78: an invalid DNS SAN will be detected if any name constraint checking // is triggered. { roots: []constraintsSpec{ { bad: []string{"uri:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:this is invalid"}, }, expectedError: "cannot parse dnsName", }, // #79: an invalid email SAN will be detected if any name constraint // checking is triggered. { roots: []constraintsSpec{ { bad: []string{"uri:"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"email:this @ is invalid"}, }, expectedError: "cannot parse rfc822Name", }, // #80: if several EKUs are requested, satisfying any of them is sufficient. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, ekus: []string{"email"}, }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, }, // #81: EKUs that are not asserted in VerifyOpts are not required to be // nested. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { { ekus: []string{"serverAuth"}, }, }, }, leaf: leafSpec{ sans: []string{"dns:example.com"}, // There's no email EKU in the intermediate. This would be rejected if // full nesting was required. ekus: []string{"email", "serverAuth"}, }, }, // #82: a certificate without SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{}, }, }, // #83: a certificate without SANs and with a CN that does not parse as a // hostname is accepted in a constrained chain. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{}, cn: "foo,bar", }, }, // #84: a certificate with SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{"dns:foo.com"}, cn: "foo.bar", }, }, // #85: without SANs, a certificate with a valid CN is accepted in a // constrained chain if x509ignoreCN is set. { roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, }, }, intermediates: [][]constraintsSpec{ { {}, }, }, leaf: leafSpec{ sans: []string{}, cn: "foo.com", }, ignoreCN: true, }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { var serialBytes [16]byte rand.Read(serialBytes[:]) template := &Certificate{ SerialNumber: new(big.Int).SetBytes(serialBytes[:]), Subject: pkix.Name{ CommonName: name, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(2000, 0), KeyUsage: KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, } if err := addConstraintsToTemplate(constraints, template); err != nil { return nil, err } if parent == nil { parent = template } derBytes, err := CreateCertificate(rand.Reader, template, parent, &key.PublicKey, parentKey) if err != nil { return nil, err } return ParseCertificate(derBytes) } func makeConstraintsLeafCert(leaf leafSpec, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { var serialBytes [16]byte rand.Read(serialBytes[:]) template := &Certificate{ SerialNumber: new(big.Int).SetBytes(serialBytes[:]), Subject: pkix.Name{ OrganizationalUnit: []string{"Leaf"}, CommonName: leaf.cn, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(2000, 0), KeyUsage: KeyUsageDigitalSignature, BasicConstraintsValid: true, IsCA: false, } for _, name := range leaf.sans { switch { case strings.HasPrefix(name, "dns:"): template.DNSNames = append(template.DNSNames, name[4:]) case strings.HasPrefix(name, "ip:"): ip := net.ParseIP(name[3:]) if ip == nil { return nil, fmt.Errorf("cannot parse IP %q", name[3:]) } template.IPAddresses = append(template.IPAddresses, ip) case strings.HasPrefix(name, "invalidip:"): ipBytes, err := hex.DecodeString(name[10:]) if err != nil { return nil, fmt.Errorf("cannot parse invalid IP: %s", err) } template.IPAddresses = append(template.IPAddresses, net.IP(ipBytes)) case strings.HasPrefix(name, "email:"): template.EmailAddresses = append(template.EmailAddresses, name[6:]) case strings.HasPrefix(name, "uri:"): uri, err := url.Parse(name[4:]) if err != nil { return nil, fmt.Errorf("cannot parse URI %q: %s", name[4:], err) } template.URIs = append(template.URIs, uri) case strings.HasPrefix(name, "unknown:"): // This is a special case for testing unknown // name types. A custom SAN extension is // injected into the certificate. if len(leaf.sans) != 1 { panic("when using unknown name types, it must be the sole name") } template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{ Id: []int{2, 5, 29, 17}, Value: []byte{ 0x30, // SEQUENCE 3, // three bytes 9, // undefined GeneralName type 9 1, 1, }, }) default: return nil, fmt.Errorf("unknown name type %q", name) } } var err error if template.ExtKeyUsage, template.UnknownExtKeyUsage, err = parseEKUs(leaf.ekus); err != nil { return nil, err } if parent == nil { parent = template } derBytes, err := CreateCertificate(rand.Reader, template, parent, &key.PublicKey, parentKey) if err != nil { return nil, err } return ParseCertificate(derBytes) } func customConstraintsExtension(typeNum int, constraint []byte, isExcluded bool) pkix.Extension { appendConstraint := func(contents []byte, tag uint8) []byte { contents = append(contents, tag|32 /* constructed */ |0x80 /* context-specific */) contents = append(contents, byte(4+len(constraint)) /* length */) contents = append(contents, 0x30 /* SEQUENCE */) contents = append(contents, byte(2+len(constraint)) /* length */) contents = append(contents, byte(typeNum) /* GeneralName type */) contents = append(contents, byte(len(constraint))) return append(contents, constraint...) } var contents []byte if !isExcluded { contents = appendConstraint(contents, 0 /* tag 0 for permitted */) } else { contents = appendConstraint(contents, 1 /* tag 1 for excluded */) } var value []byte value = append(value, 0x30 /* SEQUENCE */) value = append(value, byte(len(contents))) value = append(value, contents...) return pkix.Extension{ Id: []int{2, 5, 29, 30}, Value: value, } } func addConstraintsToTemplate(constraints constraintsSpec, template *Certificate) error { parse := func(constraints []string) (dnsNames []string, ips []*net.IPNet, emailAddrs []string, uriDomains []string, err error) { for _, constraint := range constraints { switch { case strings.HasPrefix(constraint, "dns:"): dnsNames = append(dnsNames, constraint[4:]) case strings.HasPrefix(constraint, "ip:"): _, ipNet, err := net.ParseCIDR(constraint[3:]) if err != nil { return nil, nil, nil, nil, err } ips = append(ips, ipNet) case strings.HasPrefix(constraint, "email:"): emailAddrs = append(emailAddrs, constraint[6:]) case strings.HasPrefix(constraint, "uri:"): uriDomains = append(uriDomains, constraint[4:]) default: return nil, nil, nil, nil, fmt.Errorf("unknown constraint %q", constraint) } } return dnsNames, ips, emailAddrs, uriDomains, err } handleSpecialConstraint := func(constraint string, isExcluded bool) bool { switch { case constraint == "unknown:": template.ExtraExtensions = append(template.ExtraExtensions, customConstraintsExtension(9 /* undefined GeneralName type */, []byte{1}, isExcluded)) default: return false } return true } if len(constraints.ok) == 1 && len(constraints.bad) == 0 { if handleSpecialConstraint(constraints.ok[0], false) { return nil } } if len(constraints.bad) == 1 && len(constraints.ok) == 0 { if handleSpecialConstraint(constraints.bad[0], true) { return nil } } var err error template.PermittedDNSDomains, template.PermittedIPRanges, template.PermittedEmailAddresses, template.PermittedURIDomains, err = parse(constraints.ok) if err != nil { return err } template.ExcludedDNSDomains, template.ExcludedIPRanges, template.ExcludedEmailAddresses, template.ExcludedURIDomains, err = parse(constraints.bad) if err != nil { return err } if template.ExtKeyUsage, template.UnknownExtKeyUsage, err = parseEKUs(constraints.ekus); err != nil { return err } return nil } func parseEKUs(ekuStrs []string) (ekus []ExtKeyUsage, unknowns []asn1.ObjectIdentifier, err error) { for _, s := range ekuStrs { switch s { case "serverAuth": ekus = append(ekus, ExtKeyUsageServerAuth) case "clientAuth": ekus = append(ekus, ExtKeyUsageClientAuth) case "email": ekus = append(ekus, ExtKeyUsageEmailProtection) case "netscapeSGC": ekus = append(ekus, ExtKeyUsageNetscapeServerGatedCrypto) case "msSGC": ekus = append(ekus, ExtKeyUsageMicrosoftServerGatedCrypto) case "any": ekus = append(ekus, ExtKeyUsageAny) case "other": unknowns = append(unknowns, asn1.ObjectIdentifier{2, 4, 1, 2, 3}) default: return nil, nil, fmt.Errorf("unknown EKU %q", s) } } return } func TestConstraintCases(t *testing.T) { defer func(savedIgnoreCN bool) { ignoreCN = savedIgnoreCN }(ignoreCN) privateKeys := sync.Pool{ New: func() interface{} { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { panic(err) } return priv }, } for i, test := range nameConstraintsTests { rootPool := NewCertPool() rootKey := privateKeys.Get().(*ecdsa.PrivateKey) rootName := "Root " + strconv.Itoa(i) // keys keeps track of all the private keys used in a given // test and puts them back in the privateKeys pool at the end. keys := []*ecdsa.PrivateKey{rootKey} // At each level (root, intermediate(s), leaf), parent points to // an example parent certificate and parentKey the key for the // parent level. Since all certificates at a given level have // the same name and public key, any parent certificate is // sufficient to get the correct issuer name and authority // key ID. var parent *Certificate parentKey := rootKey for _, root := range test.roots { rootCert, err := makeConstraintsCACert(root, rootName, rootKey, nil, rootKey) if IsFatal(err) { t.Fatalf("#%d: failed to create root: %s", i, err) } parent = rootCert rootPool.AddCert(rootCert) } intermediatePool := NewCertPool() for level, intermediates := range test.intermediates { levelKey := privateKeys.Get().(*ecdsa.PrivateKey) keys = append(keys, levelKey) levelName := "Intermediate level " + strconv.Itoa(level) var last *Certificate for _, intermediate := range intermediates { caCert, err := makeConstraintsCACert(intermediate, levelName, levelKey, parent, parentKey) if IsFatal(err) { t.Fatalf("#%d: failed to create %q: %s", i, levelName, err) } last = caCert intermediatePool.AddCert(caCert) } parent = last parentKey = levelKey } leafKey := privateKeys.Get().(*ecdsa.PrivateKey) keys = append(keys, leafKey) leafCert, err := makeConstraintsLeafCert(test.leaf, leafKey, parent, parentKey) if err != nil { t.Fatalf("#%d: cannot create leaf: %s", i, err) } // Skip tests with CommonName set because OpenSSL will try to match it // against name constraints, while we ignore it when it's not hostname-looking. if !test.noOpenSSL && testNameConstraintsAgainstOpenSSL && test.leaf.cn == "" { output, err := testChainAgainstOpenSSL(leafCert, intermediatePool, rootPool) if err == nil && len(test.expectedError) > 0 { t.Errorf("#%d: unexpectedly succeeded against OpenSSL", i) if debugOpenSSLFailure { return } } if err != nil { if _, ok := err.(*exec.ExitError); !ok { t.Errorf("#%d: OpenSSL failed to run: %s", i, err) } else if len(test.expectedError) == 0 { t.Errorf("#%d: OpenSSL unexpectedly failed: %v", i, output) if debugOpenSSLFailure { return } } } } ignoreCN = test.ignoreCN verifyOpts := VerifyOptions{ Roots: rootPool, Intermediates: intermediatePool, CurrentTime: time.Unix(1500, 0), KeyUsages: test.requestedEKUs, } _, err = leafCert.Verify(verifyOpts) logInfo := true if len(test.expectedError) == 0 { if err != nil { t.Errorf("#%d: unexpected failure: %s", i, err) } else { logInfo = false } } else { if err == nil { t.Errorf("#%d: unexpected success", i) } else if !strings.Contains(err.Error(), test.expectedError) { t.Errorf("#%d: expected error containing %q, but got: %s", i, test.expectedError, err) } else { logInfo = false } } if logInfo { certAsPEM := func(cert *Certificate) string { var buf bytes.Buffer pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) return buf.String() } t.Errorf("#%d: root:\n%s", i, certAsPEM(rootPool.certs[0])) t.Errorf("#%d: leaf:\n%s", i, certAsPEM(leafCert)) } for _, key := range keys { privateKeys.Put(key) } keys = keys[:0] } } func writePEMsToTempFile(certs []*Certificate) *os.File { file, err := os.CreateTemp("", "name_constraints_test") if err != nil { panic("cannot create tempfile") } pemBlock := &pem.Block{Type: "CERTIFICATE"} for _, cert := range certs { pemBlock.Bytes = cert.Raw pem.Encode(file, pemBlock) } return file } func testChainAgainstOpenSSL(leaf *Certificate, intermediates, roots *CertPool) (string, error) { args := []string{"verify", "-no_check_time"} rootsFile := writePEMsToTempFile(roots.certs) if debugOpenSSLFailure { println("roots file:", rootsFile.Name()) } else { defer os.Remove(rootsFile.Name()) } args = append(args, "-CAfile", rootsFile.Name()) if len(intermediates.certs) > 0 { intermediatesFile := writePEMsToTempFile(intermediates.certs) if debugOpenSSLFailure { println("intermediates file:", intermediatesFile.Name()) } else { defer os.Remove(intermediatesFile.Name()) } args = append(args, "-untrusted", intermediatesFile.Name()) } leafFile := writePEMsToTempFile([]*Certificate{leaf}) if debugOpenSSLFailure { println("leaf file:", leafFile.Name()) } else { defer os.Remove(leafFile.Name()) } args = append(args, leafFile.Name()) var output bytes.Buffer cmd := exec.Command("openssl", args...) cmd.Stdout = &output cmd.Stderr = &output err := cmd.Run() return output.String(), err } var rfc2821Tests = []struct { in string localPart, domain string }{ {"foo@example.com", "foo", "example.com"}, {"@example.com", "", ""}, {"\"@example.com", "", ""}, {"\"\"@example.com", "", "example.com"}, {"\"a\"@example.com", "a", "example.com"}, {"\"\\a\"@example.com", "a", "example.com"}, {"a\"@example.com", "", ""}, {"foo..bar@example.com", "", ""}, {".foo.bar@example.com", "", ""}, {"foo.bar.@example.com", "", ""}, {"|{}?'@example.com", "|{}?'", "example.com"}, // Examples from RFC 3696 {"Abc\\@def@example.com", "Abc@def", "example.com"}, {"Fred\\ Bloggs@example.com", "Fred Bloggs", "example.com"}, {"Joe.\\\\Blow@example.com", "Joe.\\Blow", "example.com"}, {"\"Abc@def\"@example.com", "Abc@def", "example.com"}, {"\"Fred Bloggs\"@example.com", "Fred Bloggs", "example.com"}, {"customer/department=shipping@example.com", "customer/department=shipping", "example.com"}, {"$A12345@example.com", "$A12345", "example.com"}, {"!def!xyz%abc@example.com", "!def!xyz%abc", "example.com"}, {"_somename@example.com", "_somename", "example.com"}, } func TestRFC2821Parsing(t *testing.T) { for i, test := range rfc2821Tests { mailbox, ok := parseRFC2821Mailbox(test.in) expectedFailure := len(test.localPart) == 0 && len(test.domain) == 0 if ok && expectedFailure { t.Errorf("#%d: %q unexpectedly parsed as (%q, %q)", i, test.in, mailbox.local, mailbox.domain) continue } if !ok && !expectedFailure { t.Errorf("#%d: unexpected failure for %q", i, test.in) continue } if !ok { continue } if mailbox.local != test.localPart || mailbox.domain != test.domain { t.Errorf("#%d: %q parsed as (%q, %q), but wanted (%q, %q)", i, test.in, mailbox.local, mailbox.domain, test.localPart, test.domain) } } } func TestBadNamesInConstraints(t *testing.T) { constraintParseError := func(err error) bool { str := err.Error() return strings.Contains(str, "failed to parse ") && strings.Contains(str, "constraint") } encodingError := func(err error) bool { return strings.Contains(err.Error(), "cannot be encoded as an IA5String") } // Bad names in constraints should not parse. badNames := []struct { name string matcher func(error) bool }{ {"dns:foo.com.", constraintParseError}, {"email:abc@foo.com.", constraintParseError}, {"email:foo.com.", constraintParseError}, {"uri:example.com.", constraintParseError}, {"uri:1.2.3.4", constraintParseError}, {"uri:ffff::1", constraintParseError}, {"dns:not–hyphen.com", encodingError}, {"email:foo@not–hyphen.com", encodingError}, {"uri:not–hyphen.com", encodingError}, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { panic(err) } for _, test := range badNames { _, err := makeConstraintsCACert(constraintsSpec{ ok: []string{test.name}, }, "TestAbsoluteNamesInConstraints", priv, nil, priv) if err == nil { t.Errorf("bad name %q unexpectedly accepted in name constraint", test.name) continue } else { if !test.matcher(err) { t.Errorf("bad name %q triggered unrecognised error: %s", test.name, err) } } } } func TestBadNamesInSANs(t *testing.T) { // Bad names in URI and IP SANs should not parse. Bad DNS and email SANs // will parse and are tested in name constraint tests at the top of this // file. badNames := []string{ "uri:https://example.com./dsf", "invalidip:0102", "invalidip:0102030405", } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { panic(err) } for _, badName := range badNames { _, err := makeConstraintsLeafCert(leafSpec{sans: []string{badName}}, priv, nil, priv) if err == nil { t.Errorf("bad name %q unexpectedly accepted in SAN", badName) continue } if str := err.Error(); !strings.Contains(str, "cannot parse ") { t.Errorf("bad name %q triggered unrecognised error: %s", badName, str) } } } google-certificate-transparency-go-2308f62/x509/names.go000066400000000000000000000124011462611535200227760ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "fmt" "net" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" ) const ( // GeneralName tag values from RFC 5280, 4.2.1.6 tagOtherName = 0 tagRFC822Name = 1 tagDNSName = 2 tagX400Address = 3 tagDirectoryName = 4 tagEDIPartyName = 5 tagURI = 6 tagIPAddress = 7 tagRegisteredID = 8 ) // OtherName describes a name related to a certificate which is not in one // of the standard name formats. RFC 5280, 4.2.1.6: // // OtherName ::= SEQUENCE { // type-id OBJECT IDENTIFIER, // value [0] EXPLICIT ANY DEFINED BY type-id } type OtherName struct { TypeID asn1.ObjectIdentifier Value asn1.RawValue } // GeneralNames holds a collection of names related to a certificate. type GeneralNames struct { DNSNames []string EmailAddresses []string DirectoryNames []pkix.Name URIs []string IPNets []net.IPNet RegisteredIDs []asn1.ObjectIdentifier OtherNames []OtherName } // Len returns the total number of names in a GeneralNames object. func (gn GeneralNames) Len() int { return (len(gn.DNSNames) + len(gn.EmailAddresses) + len(gn.DirectoryNames) + len(gn.URIs) + len(gn.IPNets) + len(gn.RegisteredIDs) + len(gn.OtherNames)) } // Empty indicates whether a GeneralNames object is empty. func (gn GeneralNames) Empty() bool { return gn.Len() == 0 } func parseGeneralNames(value []byte, gname *GeneralNames) error { // RFC 5280, 4.2.1.6 // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER } var seq asn1.RawValue var rest []byte if rest, err := asn1.Unmarshal(value, &seq); err != nil { return fmt.Errorf("x509: failed to parse GeneralNames: %v", err) } else if len(rest) != 0 { return fmt.Errorf("x509: trailing data after GeneralNames") } if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal { return fmt.Errorf("x509: failed to parse GeneralNames sequence, tag %+v", seq) } rest = seq.Bytes for len(rest) > 0 { var err error rest, err = parseGeneralName(rest, gname, false) if err != nil { return fmt.Errorf("x509: failed to parse GeneralName: %v", err) } } return nil } func parseGeneralName(data []byte, gname *GeneralNames, withMask bool) ([]byte, error) { var v asn1.RawValue var rest []byte var err error rest, err = asn1.Unmarshal(data, &v) if err != nil { return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames: %v", err) } switch v.Tag { case tagOtherName: if !v.IsCompound { return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.otherName: not compound") } var other OtherName v.FullBytes = append([]byte{}, v.FullBytes...) v.FullBytes[0] = asn1.TagSequence | 0x20 _, err = asn1.Unmarshal(v.FullBytes, &other) if err != nil { return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.otherName: %v", err) } gname.OtherNames = append(gname.OtherNames, other) case tagRFC822Name: gname.EmailAddresses = append(gname.EmailAddresses, string(v.Bytes)) case tagDNSName: dns := string(v.Bytes) gname.DNSNames = append(gname.DNSNames, dns) case tagDirectoryName: var rdnSeq pkix.RDNSequence if _, err := asn1.Unmarshal(v.Bytes, &rdnSeq); err != nil { return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.directoryName: %v", err) } var dirName pkix.Name dirName.FillFromRDNSequence(&rdnSeq) gname.DirectoryNames = append(gname.DirectoryNames, dirName) case tagURI: gname.URIs = append(gname.URIs, string(v.Bytes)) case tagIPAddress: vlen := len(v.Bytes) if withMask { switch vlen { case (2 * net.IPv4len), (2 * net.IPv6len): ipNet := net.IPNet{IP: v.Bytes[0 : vlen/2], Mask: v.Bytes[vlen/2:]} gname.IPNets = append(gname.IPNets, ipNet) default: return nil, fmt.Errorf("x509: invalid IP/mask length %d in GeneralNames.iPAddress", vlen) } } else { switch vlen { case net.IPv4len, net.IPv6len: ipNet := net.IPNet{IP: v.Bytes} gname.IPNets = append(gname.IPNets, ipNet) default: return nil, fmt.Errorf("x509: invalid IP length %d in GeneralNames.iPAddress", vlen) } } case tagRegisteredID: var oid asn1.ObjectIdentifier v.FullBytes = append([]byte{}, v.FullBytes...) v.FullBytes[0] = asn1.TagOID _, err = asn1.Unmarshal(v.FullBytes, &oid) if err != nil { return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.registeredID: %v", err) } gname.RegisteredIDs = append(gname.RegisteredIDs, oid) default: return nil, fmt.Errorf("x509: failed to unmarshal GeneralName: unknown tag %d", v.Tag) } return rest, nil } google-certificate-transparency-go-2308f62/x509/names_test.go000066400000000000000000000152711462611535200240450ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "encoding/hex" "net" "reflect" "strings" "testing" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" ) func TestParseGeneralNames(t *testing.T) { var tests = []struct { data string // as hex want GeneralNames wantErr string }{ { data: ("3012" + ("8210" + "7777772e676f6f676c652e636f2e756b")), want: GeneralNames{ DNSNames: []string{"www.google.co.uk"}, }, }, { data: ("3024" + ("8210" + "7777772e676f6f676c652e636f2e756b") + ("8610" + "7777772e676f6f676c652e636f2e756b")), want: GeneralNames{ DNSNames: []string{"www.google.co.uk"}, URIs: []string{"www.google.co.uk"}, }, }, { data: "0a0101", wantErr: "failed to parse GeneralNames sequence", }, { data: "0a", wantErr: "failed to parse GeneralNames:", }, { data: "03000a0101", wantErr: "trailing data", }, { data: ("3005" + ("8703" + "010203")), wantErr: "invalid IP length", }, } for _, test := range tests { inData := fromHex(test.data) var got GeneralNames err := parseGeneralNames(inData, &got) if err != nil { if test.wantErr == "" { t.Errorf("parseGeneralNames(%s)=%v; want nil", test.data, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("parseGeneralNames(%s)=%v; want %q", test.data, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("parseGeneralNames(%s)=%+v,nil; want %q", test.data, got, test.wantErr) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("parseGeneralNames(%s)=%+v; want %+v", test.data, got, test.want) } } } func TestParseGeneralName(t *testing.T) { var tests = []struct { data string // as hex withMask bool want GeneralNames wantErr string }{ { data: ("a008" + ("0603" + "551d0e") + // OID: subject-key-id ("0a01" + "01")), // enum=1 want: GeneralNames{ OtherNames: []OtherName{ { TypeID: OIDExtensionSubjectKeyId, Value: asn1.RawValue{ Class: asn1.ClassUniversal, Tag: asn1.TagEnum, IsCompound: false, Bytes: fromHex("01"), FullBytes: fromHex("0a0101"), }, }, }, }, }, { data: ("8008" + ("0603" + "551d0e") + // OID: subject-key-id ("0a01" + "01")), // enum=1 wantErr: "not compound", }, { data: ("a005" + ("0603" + "551d0e")), // OID: subject-key-id wantErr: "sequence truncated", }, { data: ("8110" + "77777740676f6f676c652e636f2e756b"), want: GeneralNames{ EmailAddresses: []string{"www@google.co.uk"}, }, }, { data: ("8210" + "7777772e676f6f676c652e636f2e756b"), want: GeneralNames{ DNSNames: []string{"www.google.co.uk"}, }, }, { data: ("844b" + ("3049" + ("310b" + ("3009" + ("0603" + "550406") + ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732"))))), // "GoogleInternet Authority G2" want: GeneralNames{ DirectoryNames: []pkix.Name{ { Country: []string{"US"}, Organization: []string{"Google Inc"}, CommonName: "Google Internet Authority G2", Names: []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, {Type: pkix.OIDOrganization, Value: "Google Inc"}, {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, }, }, }, { data: ("8410" + "7777772e676f6f676c652e636f2e756b"), wantErr: "failed to unmarshal GeneralNames.directoryName", }, { data: ("8610" + "7777772e676f6f676c652e636f2e756b"), want: GeneralNames{ URIs: []string{"www.google.co.uk"}, }, }, { data: ("8704" + "01020304"), want: GeneralNames{ IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}}}, }, }, { data: ("8708" + "01020304ffffff00"), withMask: true, want: GeneralNames{ IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{0xff, 0xff, 0xff, 0x00}}}, }, }, { data: ("8710" + "01020304111213142122232431323334"), want: GeneralNames{ IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4, 0x11, 0x12, 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34}}}, }, }, { data: ("8703" + "010203"), wantErr: "invalid IP length", }, { data: ("8707" + "01020304ffffff"), withMask: true, wantErr: "invalid IP/mask length", }, { data: ("8803" + "551d0e"), // OID: subject-key-id want: GeneralNames{ RegisteredIDs: []asn1.ObjectIdentifier{OIDExtensionSubjectKeyId}, }, }, { data: ("8803" + "551d8e"), wantErr: "syntax error", }, { data: ("9003" + "551d8e"), wantErr: "unknown tag", }, { data: ("8803"), wantErr: "data truncated", }, } for _, test := range tests { inData := fromHex(test.data) var got GeneralNames _, err := parseGeneralName(inData, &got, test.withMask) if err != nil { if test.wantErr == "" { t.Errorf("parseGeneralName(%s)=%v; want nil", test.data, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("parseGeneralName(%s)=%v; want %q", test.data, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("parseGeneralName(%s)=%+v,nil; want %q", test.data, got, test.wantErr) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("parseGeneralName(%s)=%+v; want %+v", test.data, got, test.want) } if got.Empty() { t.Errorf("parseGeneralName(%s).Empty(%+v)=true; want false", test.data, got) } if gotLen, wantLen := got.Len(), 1; gotLen != wantLen { t.Errorf("parseGeneralName(%s).Len(%+v)=%d; want %d", test.data, got, gotLen, wantLen) } if !bytes.Equal(inData, fromHex(test.data)) { t.Errorf("parseGeneralName(%s) modified data to %x", test.data, inData) } // Wrap the GeneralName up in a SEQUENCE and check that we get the same result using parseGeneralNames. if test.withMask { continue } seqData := append([]byte{0x30, byte(len(inData))}, inData...) var gotSeq GeneralNames err = parseGeneralNames(seqData, &gotSeq) if err != nil { t.Errorf("parseGeneralNames(%x)=%v; want nil", seqData, err) continue } if !reflect.DeepEqual(gotSeq, test.want) { t.Errorf("parseGeneralNames(%x)=%+v; want %+v", seqData, gotSeq, test.want) } } } func fromHex(s string) []byte { d, _ := hex.DecodeString(s) return d } google-certificate-transparency-go-2308f62/x509/pem_decrypt.go000066400000000000000000000150011462611535200242050ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 // RFC 1423 describes the encryption of PEM blocks. The algorithm used to // generate a key from the password was derived by looking at the OpenSSL // implementation. import ( "crypto/aes" "crypto/cipher" "crypto/des" "crypto/md5" "encoding/hex" "encoding/pem" "errors" "io" "strings" ) type PEMCipher int // Possible values for the EncryptPEMBlock encryption algorithm. const ( _ PEMCipher = iota PEMCipherDES PEMCipher3DES PEMCipherAES128 PEMCipherAES192 PEMCipherAES256 ) // rfc1423Algo holds a method for enciphering a PEM block. type rfc1423Algo struct { cipher PEMCipher name string cipherFunc func(key []byte) (cipher.Block, error) keySize int blockSize int } // rfc1423Algos holds a slice of the possible ways to encrypt a PEM // block. The ivSize numbers were taken from the OpenSSL source. var rfc1423Algos = []rfc1423Algo{{ cipher: PEMCipherDES, name: "DES-CBC", cipherFunc: des.NewCipher, keySize: 8, blockSize: des.BlockSize, }, { cipher: PEMCipher3DES, name: "DES-EDE3-CBC", cipherFunc: des.NewTripleDESCipher, keySize: 24, blockSize: des.BlockSize, }, { cipher: PEMCipherAES128, name: "AES-128-CBC", cipherFunc: aes.NewCipher, keySize: 16, blockSize: aes.BlockSize, }, { cipher: PEMCipherAES192, name: "AES-192-CBC", cipherFunc: aes.NewCipher, keySize: 24, blockSize: aes.BlockSize, }, { cipher: PEMCipherAES256, name: "AES-256-CBC", cipherFunc: aes.NewCipher, keySize: 32, blockSize: aes.BlockSize, }, } // deriveKey uses a key derivation function to stretch the password into a key // with the number of bits our cipher requires. This algorithm was derived from // the OpenSSL source. func (c rfc1423Algo) deriveKey(password, salt []byte) []byte { hash := md5.New() out := make([]byte, c.keySize) var digest []byte for i := 0; i < len(out); i += len(digest) { hash.Reset() hash.Write(digest) hash.Write(password) hash.Write(salt) digest = hash.Sum(digest[:0]) copy(out[i:], digest) } return out } // IsEncryptedPEMBlock returns if the PEM block is password encrypted. func IsEncryptedPEMBlock(b *pem.Block) bool { _, ok := b.Headers["DEK-Info"] return ok } // IncorrectPasswordError is returned when an incorrect password is detected. var IncorrectPasswordError = errors.New("x509: decryption password incorrect") // DecryptPEMBlock takes a password encrypted PEM block and the password used to // encrypt it and returns a slice of decrypted DER encoded bytes. It inspects // the DEK-Info header to determine the algorithm used for decryption. If no // DEK-Info header is present, an error is returned. If an incorrect password // is detected an IncorrectPasswordError is returned. Because of deficiencies // in the encrypted-PEM format, it's not always possible to detect an incorrect // password. In these cases no error will be returned but the decrypted DER // bytes will be random noise. func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) { dek, ok := b.Headers["DEK-Info"] if !ok { return nil, errors.New("x509: no DEK-Info header in block") } idx := strings.Index(dek, ",") if idx == -1 { return nil, errors.New("x509: malformed DEK-Info header") } mode, hexIV := dek[:idx], dek[idx+1:] ciph := cipherByName(mode) if ciph == nil { return nil, errors.New("x509: unknown encryption mode") } iv, err := hex.DecodeString(hexIV) if err != nil { return nil, err } if len(iv) != ciph.blockSize { return nil, errors.New("x509: incorrect IV size") } // Based on the OpenSSL implementation. The salt is the first 8 bytes // of the initialization vector. key := ciph.deriveKey(password, iv[:8]) block, err := ciph.cipherFunc(key) if err != nil { return nil, err } if len(b.Bytes)%block.BlockSize() != 0 { return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size") } data := make([]byte, len(b.Bytes)) dec := cipher.NewCBCDecrypter(block, iv) dec.CryptBlocks(data, b.Bytes) // Blocks are padded using a scheme where the last n bytes of padding are all // equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423. // For example: // [x y z 2 2] // [x y 7 7 7 7 7 7 7] // If we detect a bad padding, we assume it is an invalid password. dlen := len(data) if dlen == 0 || dlen%ciph.blockSize != 0 { return nil, errors.New("x509: invalid padding") } last := int(data[dlen-1]) if dlen < last { return nil, IncorrectPasswordError } if last == 0 || last > ciph.blockSize { return nil, IncorrectPasswordError } for _, val := range data[dlen-last:] { if int(val) != last { return nil, IncorrectPasswordError } } return data[:dlen-last], nil } // EncryptPEMBlock returns a PEM block of the specified type holding the // given DER-encoded data encrypted with the specified algorithm and // password. func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) { ciph := cipherByKey(alg) if ciph == nil { return nil, errors.New("x509: unknown encryption mode") } iv := make([]byte, ciph.blockSize) if _, err := io.ReadFull(rand, iv); err != nil { return nil, errors.New("x509: cannot generate IV: " + err.Error()) } // The salt is the first 8 bytes of the initialization vector, // matching the key derivation in DecryptPEMBlock. key := ciph.deriveKey(password, iv[:8]) block, err := ciph.cipherFunc(key) if err != nil { return nil, err } enc := cipher.NewCBCEncrypter(block, iv) pad := ciph.blockSize - len(data)%ciph.blockSize encrypted := make([]byte, len(data), len(data)+pad) // We could save this copy by encrypting all the whole blocks in // the data separately, but it doesn't seem worth the additional // code. copy(encrypted, data) // See RFC 1423, Section 1.1. for i := 0; i < pad; i++ { encrypted = append(encrypted, byte(pad)) } enc.CryptBlocks(encrypted, encrypted) return &pem.Block{ Type: blockType, Headers: map[string]string{ "Proc-Type": "4,ENCRYPTED", "DEK-Info": ciph.name + "," + hex.EncodeToString(iv), }, Bytes: encrypted, }, nil } func cipherByName(name string) *rfc1423Algo { for i := range rfc1423Algos { alg := &rfc1423Algos[i] if alg.name == name { return alg } } return nil } func cipherByKey(key PEMCipher) *rfc1423Algo { for i := range rfc1423Algos { alg := &rfc1423Algos[i] if alg.cipher == key { return alg } } return nil } google-certificate-transparency-go-2308f62/x509/pem_decrypt_test.go000066400000000000000000000220641462611535200252530ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "crypto/rand" "encoding/base64" "encoding/pem" "strings" "testing" ) func TestDecrypt(t *testing.T) { for i, data := range testData { t.Logf("test %v. %v", i, data.kind) block, rest := pem.Decode(data.pemData) if len(rest) > 0 { t.Error("extra data") } der, err := DecryptPEMBlock(block, data.password) if err != nil { t.Error("decrypt failed: ", err) continue } if _, err := ParsePKCS1PrivateKey(der); err != nil { t.Error("invalid private key: ", err) } plainDER, err := base64.StdEncoding.DecodeString(data.plainDER) if err != nil { t.Fatal("cannot decode test DER data: ", err) } if !bytes.Equal(der, plainDER) { t.Error("data mismatch") } } } func TestEncrypt(t *testing.T) { for i, data := range testData { t.Logf("test %v. %v", i, data.kind) plainDER, err := base64.StdEncoding.DecodeString(data.plainDER) if err != nil { t.Fatal("cannot decode test DER data: ", err) } password := []byte("kremvax1") block, err := EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", plainDER, password, data.kind) if err != nil { t.Error("encrypt: ", err) continue } if !IsEncryptedPEMBlock(block) { t.Error("PEM block does not appear to be encrypted") } if block.Type != "RSA PRIVATE KEY" { t.Errorf("unexpected block type; got %q want %q", block.Type, "RSA PRIVATE KEY") } if block.Headers["Proc-Type"] != "4,ENCRYPTED" { t.Errorf("block does not have correct Proc-Type header") } der, err := DecryptPEMBlock(block, password) if err != nil { t.Error("decrypt: ", err) continue } if !bytes.Equal(der, plainDER) { t.Errorf("data mismatch") } } } var testData = []struct { kind PEMCipher password []byte pemData []byte plainDER string }{ { kind: PEMCipherDES, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,34F09A4FC8DE22B5 WXxy8kbZdiZvANtKvhmPBLV7eVFj2A5z6oAxvI9KGyhG0ZK0skfnt00C24vfU7m5 ICXeoqP67lzJ18xCzQfHjDaBNs53DSDT+Iz4e8QUep1xQ30+8QKX2NA2coee3nwc 6oM1cuvhNUDemBH2i3dKgMVkfaga0zQiiOq6HJyGSncCMSruQ7F9iWEfRbFcxFCx qtHb1kirfGKEtgWTF+ynyco6+2gMXNu70L7nJcnxnV/RLFkHt7AUU1yrclxz7eZz XOH9VfTjb52q/I8Suozq9coVQwg4tXfIoYUdT//O+mB7zJb9HI9Ps77b9TxDE6Gm 4C9brwZ3zg2vqXcwwV6QRZMtyll9rOpxkbw6NPlpfBqkc3xS51bbxivbO/Nve4KD r12ymjFNF4stXCfJnNqKoZ50BHmEEUDu5Wb0fpVn82XrGw7CYc4iug== -----END RSA TESTING KEY-----`)), plainDER: ` MIIBPAIBAAJBAPASZe+tCPU6p80AjHhDkVsLYa51D35e/YGa8QcZyooeZM8EHozo KD0fNiKI+53bHdy07N+81VQ8/ejPcRoXPlsCAwEAAQJBAMTxIuSq27VpR+zZ7WJf c6fvv1OBvpMZ0/d1pxL/KnOAgq2rD5hDtk9b0LGhTPgQAmrrMTKuSeGoIuYE+gKQ QvkCIQD+GC1m+/do+QRurr0uo46Kx1LzLeSCrjBk34wiOp2+dwIhAPHfTLRXS2fv 7rljm0bYa4+eDZpz+E8RcXEgzhhvcQQ9AiAI5eHZJGOyml3MXnQjiPi55WcDOw0w glcRgT6QCEtz2wIhANSyqaFtosIkHKqrDUGfz/bb5tqMYTAnBruVPaf/WEOBAiEA 9xORWeRG1tRpso4+dYy4KdDkuLPIO01KY6neYGm3BCM=`, }, { kind: PEMCipher3DES, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,C1F4A6A03682C2C7 0JqVdBEH6iqM7drTkj+e2W/bE3LqakaiWhb9WUVonFkhyu8ca/QzebY3b5gCvAZQ YwBvDcT/GHospKqPx+cxDHJNsUASDZws6bz8ZXWJGwZGExKzr0+Qx5fgXn44Ms3x 8g1ENFuTXtxo+KoNK0zuAMAqp66Llcds3Fjl4XR18QaD0CrVNAfOdgATWZm5GJxk Fgx5f84nT+/ovvreG+xeOzWgvtKo0UUZVrhGOgfKLpa57adumcJ6SkUuBtEFpZFB ldw5w7WC7d13x2LsRkwo8ZrDKgIV+Y9GNvhuCCkTzNP0V3gNeJpd201HZHR+9n3w 3z0VjR/MGqsfcy1ziEWMNOO53At3zlG6zP05aHMnMcZoVXadEK6L1gz++inSSDCq gI0UJP4e3JVB7AkgYymYAwiYALAkoEIuanxoc50njJk= -----END RSA TESTING KEY-----`)), plainDER: ` MIIBOwIBAAJBANOCXKdoNS/iP/MAbl9cf1/SF3P+Ns7ZeNL27CfmDh0O6Zduaax5 NBiumd2PmjkaCu7lQ5JOibHfWn+xJsc3kw0CAwEAAQJANX/W8d1Q/sCqzkuAn4xl B5a7qfJWaLHndu1QRLNTRJPn0Ee7OKJ4H0QKOhQM6vpjRrz+P2u9thn6wUxoPsef QQIhAP/jCkfejFcy4v15beqKzwz08/tslVjF+Yq41eJGejmxAiEA05pMoqfkyjcx fyvGhpoOyoCp71vSGUfR2I9CR65oKh0CIC1Msjs66LlfJtQctRq6bCEtFCxEcsP+ eEjYo/Sk6WphAiEAxpgWPMJeU/shFT28gS+tmhjPZLpEoT1qkVlC14u0b3ECIQDX tZZZxCtPAm7shftEib0VU77Lk8MsXJcx2C4voRsjEw==`, }, { kind: PEMCipherAES128, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,D4492E793FC835CC038A728ED174F78A EyfQSzXSjv6BaNH+NHdXRlkHdimpF9izWlugVJAPApgXrq5YldPe2aGIOFXyJ+QE ZIG20DYqaPzJRjTEbPNZ6Es0S2JJ5yCpKxwJuDkgJZKtF39Q2i36JeGbSZQIuWJE GZbBpf1jDH/pr0iGonuAdl2PCCZUiy+8eLsD2tyviHUkFLOB+ykYoJ5t8ngZ/B6D 33U43LLb7+9zD4y3Q9OVHqBFGyHcxCY9+9Qh4ZnFp7DTf6RY5TNEvE3s4g6aDpBs 3NbvRVvYTgs8K9EPk4K+5R+P2kD8J8KvEIGxVa1vz8QoCJ/jr7Ka2rvNgPCex5/E 080LzLHPCrXKdlr/f50yhNWq08ZxMWQFkui+FDHPDUaEELKAXV8/5PDxw80Rtybo AVYoCVIbZXZCuCO81op8UcOgEpTtyU5Lgh3Mw5scQL0= -----END RSA TESTING KEY-----`)), plainDER: ` MIIBOgIBAAJBAMBlj5FxYtqbcy8wY89d/S7n0+r5MzD9F63BA/Lpl78vQKtdJ5dT cDGh/rBt1ufRrNp0WihcmZi7Mpl/3jHjiWECAwEAAQJABNOHYnKhtDIqFYj1OAJ3 k3GlU0OlERmIOoeY/cL2V4lgwllPBEs7r134AY4wMmZSBUj8UR/O4SNO668ElKPE cQIhAOuqY7/115x5KCdGDMWi+jNaMxIvI4ETGwV40ykGzqlzAiEA0P9oEC3m9tHB kbpjSTxaNkrXxDgdEOZz8X0uOUUwHNsCIAwzcSCiGLyYJTULUmP1ESERfW1mlV78 XzzESaJpIM/zAiBQkSTcl9VhcJreQqvjn5BnPZLP4ZHS4gPwJAGdsj5J4QIhAOVR B3WlRNTXR2WsJ5JdByezg9xzdXzULqmga0OE339a`, }, { kind: PEMCipherAES192, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-192-CBC,E2C9FB02BCA23ADE1829F8D8BC5F5369 cqVslvHqDDM6qwU6YjezCRifXmKsrgEev7ng6Qs7UmDJOpHDgJQZI9fwMFUhIyn5 FbCu1SHkLMW52Ld3CuEqMnzWMlhPrW8tFvUOrMWPYSisv7nNq88HobZEJcUNL2MM Y15XmHW6IJwPqhKyLHpWXyOCVEh4ODND2nV15PCoi18oTa475baxSk7+1qH7GuIs Rb7tshNTMqHbCpyo9Rn3UxeFIf9efdl8YLiMoIqc7J8E5e9VlbeQSdLMQOgDAQJG ReUtTw8exmKsY4gsSjhkg5uiw7/ZB1Ihto0qnfQJgjGc680qGkT1d6JfvOfeYAk6 xn5RqS/h8rYAYm64KnepfC9vIujo4NqpaREDmaLdX5MJPQ+SlytITQvgUsUq3q/t Ss85xjQEZH3hzwjQqdJvmA4hYP6SUjxYpBM+02xZ1Xw= -----END RSA TESTING KEY-----`)), plainDER: ` MIIBOwIBAAJBAMGcRrZiNNmtF20zyS6MQ7pdGx17aFDl+lTl+qnLuJRUCMUG05xs OmxmL/O1Qlf+bnqR8Bgg65SfKg21SYuLhiMCAwEAAQJBAL94uuHyO4wux2VC+qpj IzPykjdU7XRcDHbbvksf4xokSeUFjjD3PB0Qa83M94y89ZfdILIqS9x5EgSB4/lX qNkCIQD6cCIqLfzq/lYbZbQgAAjpBXeQVYsbvVtJrPrXJAlVVQIhAMXpDKMeFPMn J0g2rbx1gngx0qOa5r5iMU5w/noN4W2XAiBjf+WzCG5yFvazD+dOx3TC0A8+4x3P uZ3pWbaXf5PNuQIgAcdXarvhelH2w2piY1g3BPeFqhzBSCK/yLGxR82KIh8CIQDD +qGKsd09NhQ/G27y/DARzOYtml1NvdmCQAgsDIIOLA==`, }, { kind: PEMCipherAES256, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,8E7ED5CD731902CE938957A886A5FFBD 4Mxr+KIzRVwoOP0wwq6caSkvW0iS+GE2h2Ov/u+n9ZTMwL83PRnmjfjzBgfRZLVf JFPXxUK26kMNpIdssNnqGOds+DhB+oSrsNKoxgxSl5OBoYv9eJTVYm7qOyAFIsjr DRKAcjYCmzfesr7PVTowwy0RtHmYwyXMGDlAzzZrEvaiySFFmMyKKvtoavwaFoc7 Pz3RZScwIuubzTGJ1x8EzdffYOsdCa9Mtgpp3L136+23dOd6L/qK2EG2fzrJSHs/ 2XugkleBFSMKzEp9mxXKRfa++uidQvMZTFLDK9w5YjrRvMBo/l2BoZIsq0jAIE1N sv5Z/KwlX+3MDEpPQpUwGPlGGdLnjI3UZ+cjgqBcoMiNc6HfgbBgYJSU6aDSHuCk clCwByxWkBNgJ2GrkwNrF26v+bGJJJNR4SKouY1jQf0= -----END RSA TESTING KEY-----`)), plainDER: ` MIIBOgIBAAJBAKy3GFkstoCHIEeUU/qO8207m8WSrjksR+p9B4tf1w5k+2O1V/GY AQ5WFCApItcOkQe/I0yZZJk/PmCqMzSxrc8CAwEAAQJAOCAz0F7AW9oNelVQSP8F Sfzx7O1yom+qWyAQQJF/gFR11gpf9xpVnnyu1WxIRnDUh1LZwUsjwlDYb7MB74id oQIhANPcOiLwOPT4sIUpRM5HG6BF1BI7L77VpyGVk8xNP7X/AiEA0LMHZtk4I+lJ nClgYp4Yh2JZ1Znbu7IoQMCEJCjwKDECIGd8Dzm5tViTkUW6Hs3Tlf73nNs65duF aRnSglss8I3pAiEAonEnKruawgD8RavDFR+fUgmQiPz4FnGGeVgfwpGG1JECIBYq PXHYtPqxQIbD2pScR5qum7iGUh11lEUPkmt+2uqS`, }, { // generated with: // openssl genrsa -aes128 -passout pass:asdf -out server.orig.key 128 kind: PEMCipherAES128, password: []byte("asdf"), pemData: []byte(testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7 6ei/MlytjE0FFgZOGQ+jrwomKfpl8kdefeE0NSt/DMRrw8OacHAzBNi3pPEa0eX3 eND9l7C9meCirWovjj9QWVHrXyugFuDIqgdhQ8iHTgCfF3lrmcttVrbIfMDw+smD hTP8O1mS/MHl92NE0nhv0w== -----END RSA TESTING KEY-----`)), plainDER: ` MGMCAQACEQC6ssxmYuauuHGOCDAI54RdAgMBAAECEQCWIn6Yv2O+kBcDF7STctKB AgkA8SEfu/2i3g0CCQDGNlXbBHX7kQIIK3Ww5o0cYbECCQDCimPb0dYGsQIIeQ7A jryIst8=`, }, } var incompleteBlockPEM = testingKey(` -----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7 6L8yXK2MTQUWBk4ZD6OvCiYp+mXyR1594TQ1K38MxGvDw5pwcDME2Lek8RrR5fd40P2XsL2Z4KKt ai+OP1BZUetfK6AW4MiqB2FDyIdOAJ8XeWuZy21Wtsh8wPD6yYOFM/w7WZL8weX3Y0TSeG/T -----END RSA TESTING KEY-----`) func TestIncompleteBlock(t *testing.T) { // incompleteBlockPEM contains ciphertext that is not a multiple of the // block size. This previously panicked. See #11215. block, _ := pem.Decode([]byte(incompleteBlockPEM)) _, err := DecryptPEMBlock(block, []byte("foo")) if err == nil { t.Fatal("Bad PEM data decrypted successfully") } const expectedSubstr = "block size" if e := err.Error(); !strings.Contains(e, expectedSubstr) { t.Fatalf("Expected error containing %q but got: %q", expectedSubstr, e) } } func testingKey(s string) string { // TODO: restore following line once Go 1.12 is required for building // return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") return strings.Replace(s, "TESTING KEY", "PRIVATE KEY", -1) } google-certificate-transparency-go-2308f62/x509/pkcs1.go000066400000000000000000000112721462611535200227210ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto/rsa" "errors" "math/big" "github.com/google/certificate-transparency-go/asn1" ) // pkcs1PrivateKey is a structure which mirrors the PKCS#1 ASN.1 for an RSA private key. type pkcs1PrivateKey struct { Version int N *big.Int E int D *big.Int P *big.Int Q *big.Int // We ignore these values, if present, because rsa will calculate them. Dp *big.Int `asn1:"optional"` Dq *big.Int `asn1:"optional"` Qinv *big.Int `asn1:"optional"` AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"` } type pkcs1AdditionalRSAPrime struct { Prime *big.Int // We ignore these values because rsa will calculate them. Exp *big.Int Coeff *big.Int } // pkcs1PublicKey reflects the ASN.1 structure of a PKCS#1 public key. type pkcs1PublicKey struct { N *big.Int E int } // ParsePKCS1PrivateKey parses an RSA private key in PKCS#1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "RSA PRIVATE KEY". func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { var priv pkcs1PrivateKey rest, err := asn1.Unmarshal(der, &priv) if len(rest) > 0 { return nil, asn1.SyntaxError{Msg: "trailing data"} } if err != nil { if _, err := asn1.Unmarshal(der, &ecPrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParseECPrivateKey instead for this key format)") } if _, err := asn1.Unmarshal(der, &pkcs8{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)") } return nil, err } if priv.Version > 1 { return nil, errors.New("x509: unsupported private key version") } if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 { return nil, errors.New("x509: private key contains zero or negative value") } key := new(rsa.PrivateKey) key.PublicKey = rsa.PublicKey{ E: priv.E, N: priv.N, } key.D = priv.D key.Primes = make([]*big.Int, 2+len(priv.AdditionalPrimes)) key.Primes[0] = priv.P key.Primes[1] = priv.Q for i, a := range priv.AdditionalPrimes { if a.Prime.Sign() <= 0 { return nil, errors.New("x509: private key contains zero or negative prime") } key.Primes[i+2] = a.Prime // We ignore the other two values because rsa will calculate // them as needed. } err = key.Validate() if err != nil { return nil, err } key.Precompute() return key, nil } // MarshalPKCS1PrivateKey converts an RSA private key to PKCS#1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "RSA PRIVATE KEY". // For a more flexible key format which is not RSA specific, use // MarshalPKCS8PrivateKey. func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte { key.Precompute() version := 0 if len(key.Primes) > 2 { version = 1 } priv := pkcs1PrivateKey{ Version: version, N: key.N, E: key.PublicKey.E, D: key.D, P: key.Primes[0], Q: key.Primes[1], Dp: key.Precomputed.Dp, Dq: key.Precomputed.Dq, Qinv: key.Precomputed.Qinv, } priv.AdditionalPrimes = make([]pkcs1AdditionalRSAPrime, len(key.Precomputed.CRTValues)) for i, values := range key.Precomputed.CRTValues { priv.AdditionalPrimes[i].Prime = key.Primes[2+i] priv.AdditionalPrimes[i].Exp = values.Exp priv.AdditionalPrimes[i].Coeff = values.Coeff } b, _ := asn1.Marshal(priv) return b } // ParsePKCS1PublicKey parses an RSA public key in PKCS#1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "RSA PUBLIC KEY". func ParsePKCS1PublicKey(der []byte) (*rsa.PublicKey, error) { var pub pkcs1PublicKey rest, err := asn1.Unmarshal(der, &pub) if err != nil { if _, err := asn1.Unmarshal(der, &publicKeyInfo{}); err == nil { return nil, errors.New("x509: failed to parse public key (use ParsePKIXPublicKey instead for this key format)") } return nil, err } if len(rest) > 0 { return nil, asn1.SyntaxError{Msg: "trailing data"} } if pub.N.Sign() <= 0 || pub.E <= 0 { return nil, errors.New("x509: public key contains zero or negative value") } if pub.E > 1<<31-1 { return nil, errors.New("x509: public key contains large public exponent") } return &rsa.PublicKey{ E: pub.E, N: pub.N, }, nil } // MarshalPKCS1PublicKey converts an RSA public key to PKCS#1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "RSA PUBLIC KEY". func MarshalPKCS1PublicKey(key *rsa.PublicKey) []byte { derBytes, _ := asn1.Marshal(pkcs1PublicKey{ N: key.N, E: key.E, }) return derBytes } google-certificate-transparency-go-2308f62/x509/pkcs8.go000066400000000000000000000110331462611535200227230ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto/ecdsa" "crypto/rsa" "errors" "fmt" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" // TODO(robpercival): change this to crypto/ed25519 when Go 1.13 is min version "golang.org/x/crypto/ed25519" ) // pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn // and RFC 5208. type pkcs8 struct { Version int Algo pkix.AlgorithmIdentifier PrivateKey []byte // optional attributes omitted. } // ParsePKCS8PrivateKey parses an unencrypted private key in PKCS#8, ASN.1 DER form. // // It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, or a ed25519.PrivateKey. // More types might be supported in the future. // // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) { var privKey pkcs8 if _, err := asn1.Unmarshal(der, &privKey); err != nil { if _, err := asn1.Unmarshal(der, &ecPrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParseECPrivateKey instead for this key format)") } if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)") } return nil, err } switch { case privKey.Algo.Algorithm.Equal(OIDPublicKeyRSA): key, err = ParsePKCS1PrivateKey(privKey.PrivateKey) if err != nil { return nil, errors.New("x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error()) } return key, nil case privKey.Algo.Algorithm.Equal(OIDPublicKeyECDSA): bytes := privKey.Algo.Parameters.FullBytes namedCurveOID := new(asn1.ObjectIdentifier) if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil { namedCurveOID = nil } key, err = parseECPrivateKey(namedCurveOID, privKey.PrivateKey) if err != nil { return nil, errors.New("x509: failed to parse EC private key embedded in PKCS#8: " + err.Error()) } return key, nil case privKey.Algo.Algorithm.Equal(OIDPublicKeyEd25519): if l := len(privKey.Algo.Parameters.FullBytes); l != 0 { return nil, errors.New("x509: invalid Ed25519 private key parameters") } var curvePrivateKey []byte if _, err := asn1.Unmarshal(privKey.PrivateKey, &curvePrivateKey); err != nil { return nil, fmt.Errorf("x509: invalid Ed25519 private key: %v", err) } if l := len(curvePrivateKey); l != ed25519.SeedSize { return nil, fmt.Errorf("x509: invalid Ed25519 private key length: %d", l) } return ed25519.NewKeyFromSeed(curvePrivateKey), nil default: return nil, fmt.Errorf("x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm) } } // MarshalPKCS8PrivateKey converts a private key to PKCS#8, ASN.1 DER form. // // The following key types are currently supported: *rsa.PrivateKey, *ecdsa.PrivateKey // and ed25519.PrivateKey. Unsupported key types result in an error. // // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) { var privKey pkcs8 switch k := key.(type) { case *rsa.PrivateKey: privKey.Algo = pkix.AlgorithmIdentifier{ Algorithm: OIDPublicKeyRSA, Parameters: asn1.NullRawValue, } privKey.PrivateKey = MarshalPKCS1PrivateKey(k) case *ecdsa.PrivateKey: oid, ok := OIDFromNamedCurve(k.Curve) if !ok { return nil, errors.New("x509: unknown curve while marshaling to PKCS#8") } oidBytes, err := asn1.Marshal(oid) if err != nil { return nil, errors.New("x509: failed to marshal curve OID: " + err.Error()) } privKey.Algo = pkix.AlgorithmIdentifier{ Algorithm: OIDPublicKeyECDSA, Parameters: asn1.RawValue{ FullBytes: oidBytes, }, } if privKey.PrivateKey, err = marshalECPrivateKeyWithOID(k, nil); err != nil { return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error()) } case ed25519.PrivateKey: privKey.Algo = pkix.AlgorithmIdentifier{ Algorithm: OIDPublicKeyEd25519, } curvePrivateKey, err := asn1.Marshal(k.Seed()) if err != nil { return nil, fmt.Errorf("x509: failed to marshal private key: %v", err) } privKey.PrivateKey = curvePrivateKey default: return nil, fmt.Errorf("x509: unknown key type while marshaling PKCS#8: %T", key) } return asn1.Marshal(privKey) } google-certificate-transparency-go-2308f62/x509/pkcs8_test.go000066400000000000000000000202331462611535200237640ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "encoding/hex" "reflect" "strings" "testing" // TODO(robpercival): change this to crypto/ed25519 when Go 1.13 is min version "golang.org/x/crypto/ed25519" ) // Generated using: // // openssl genrsa 1024 | openssl pkcs8 -topk8 -nocrypt var pkcs8RSAPrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031` // Generated using: // // openssl ecparam -genkey -name secp224r1 | openssl pkcs8 -topk8 -nocrypt var pkcs8P224PrivateKeyHex = `3078020100301006072a8648ce3d020106052b810400210461305f020101041cca3d72b3e88fed2684576dad9b80a9180363a5424986900e3abcab3fa13c033a0004f8f2a6372872a4e61263ed893afb919576a4cacfecd6c081a2cbc76873cf4ba8530703c6042b3a00e2205087e87d2435d2e339e25702fae1` // Generated using: // // openssl ecparam -genkey -name secp256r1 | openssl pkcs8 -topk8 -nocrypt var pkcs8P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420dad6b2f49ca774c36d8ae9517e935226f667c929498f0343d2424d0b9b591b43a14403420004b9c9b90095476afe7b860d8bd43568cab7bcb2eed7b8bf2fa0ce1762dd20b04193f859d2d782b1e4cbfd48492f1f533113a6804903f292258513837f07fda735` // Generated using: // // openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt var pkcs8P384PrivateKeyHex = `3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104309bf832f6aaaeacb78ce47ffb15e6fd0fd48683ae79df6eca39bfb8e33829ac94aa29d08911568684c2264a08a4ceb679a164036200049070ad4ed993c7770d700e9f6dc2baa83f63dd165b5507f98e8ff29b5d2e78ccbe05c8ddc955dbf0f7497e8222cfa49314fe4e269459f8e880147f70d785e530f2939e4bf9f838325bb1a80ad4cf59272ae0e5efe9a9dc33d874492596304bd3` // Generated using: // // openssl ecparam -genkey -name secp521r1 | openssl pkcs8 -topk8 -nocrypt // // Note that OpenSSL will truncate the private key if it can (i.e. it emits it // like an integer, even though it's an OCTET STRING field). Thus if you // regenerate this you may, randomly, find that it's a byte shorter than // expected and the Go test will fail to recreate it exactly. var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020101044200cfe0b87113a205cf291bb9a8cd1a74ac6c7b2ebb8199aaa9a5010d8b8012276fa3c22ac913369fa61beec2a3b8b4516bc049bde4fb3b745ac11b56ab23ac52e361a1818903818600040138f75acdd03fbafa4f047a8e4b272ba9d555c667962b76f6f232911a5786a0964e5edea6bd21a6f8725720958de049c6e3e6661c1c91b227cebee916c0319ed6ca003db0a3206d372229baf9dd25d868bf81140a518114803ce40c1855074d68c4e9dab9e65efba7064c703b400f1767f217dac82715ac1f6d88c74baf47a7971de4ea` // From RFC 8410, Section 7. var pkcs8Ed25519PrivateKeyHex = `302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842` func TestPKCS8(t *testing.T) { tests := []struct { name string keyHex string keyType reflect.Type curve elliptic.Curve }{ { name: "RSA private key", keyHex: pkcs8RSAPrivateKeyHex, keyType: reflect.TypeOf(&rsa.PrivateKey{}), }, { name: "P-224 private key", keyHex: pkcs8P224PrivateKeyHex, keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), curve: elliptic.P224(), }, { name: "P-256 private key", keyHex: pkcs8P256PrivateKeyHex, keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), curve: elliptic.P256(), }, { name: "P-384 private key", keyHex: pkcs8P384PrivateKeyHex, keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), curve: elliptic.P384(), }, { name: "P-521 private key", keyHex: pkcs8P521PrivateKeyHex, keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), curve: elliptic.P521(), }, { name: "Ed25519 private key", keyHex: pkcs8Ed25519PrivateKeyHex, keyType: reflect.TypeOf(ed25519.PrivateKey{}), }, } for _, test := range tests { derBytes, err := hex.DecodeString(test.keyHex) if err != nil { t.Errorf("%s: failed to decode hex: %s", test.name, err) continue } privKey, err := ParsePKCS8PrivateKey(derBytes) if err != nil { t.Errorf("%s: failed to decode PKCS#8: %s", test.name, err) continue } if reflect.TypeOf(privKey) != test.keyType { t.Errorf("%s: decoded PKCS#8 returned unexpected key type: %T", test.name, privKey) continue } if ecKey, isEC := privKey.(*ecdsa.PrivateKey); isEC && ecKey.Curve != test.curve { t.Errorf("%s: decoded PKCS#8 returned unexpected curve %#v", test.name, ecKey.Curve) continue } reserialised, err := MarshalPKCS8PrivateKey(privKey) if err != nil { t.Errorf("%s: failed to marshal into PKCS#8: %s", test.name, err) continue } if !bytes.Equal(derBytes, reserialised) { t.Errorf("%s: marshaled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes) continue } } } const hexPKCS8TestPKCS1Key = "3082025c02010002818100b1a1e0945b9289c4d3f1329f8a982c4a2dcd59bfd372fb8085a9c517554607ebd2f7990eef216ac9f4605f71a03b04f42a5255b158cf8e0844191f5119348baa44c35056e20609bcf9510f30ead4b481c81d7865fb27b8e0090e112b717f3ee08cdfc4012da1f1f7cf2a1bc34c73a54a12b06372d09714742dd7895eadde4aa5020301000102818062b7fa1db93e993e40237de4d89b7591cc1ea1d04fed4904c643f17ae4334557b4295270d0491c161cb02a9af557978b32b20b59c267a721c4e6c956c2d147046e9ae5f2da36db0106d70021fa9343455f8f973a4b355a26fd19e6b39dee0405ea2b32deddf0f4817759ef705d02b34faab9ca93c6766e9f722290f119f34449024100d9c29a4a013a90e35fd1be14a3f747c589fac613a695282d61812a711906b8a0876c6181f0333ca1066596f57bff47e7cfcabf19c0fc69d9cd76df743038b3cb024100d0d3546fecf879b5551f2bd2c05e6385f2718a08a6face3d2aecc9d7e03645a480a46c81662c12ad6bd6901e3bd4f38029462de7290859567cdf371c79088d4f024100c254150657e460ea58573fcf01a82a4791e3d6223135c8bdfed69afe84fbe7857274f8eb5165180507455f9b4105c6b08b51fe8a481bb986a202245576b713530240045700003b7a867d0041df9547ae2e7f50248febd21c9040b12dae9c2feab0d3d4609668b208e4727a3541557f84d372ac68eaf74ce1018a4c9a0ef92682c8fd02405769731480bb3a4570abf422527c5f34bf732fa6c1e08cc322753c511ce055fac20fc770025663ad3165324314df907f1f1942f0448a7e9cdbf87ecd98b92156" const hexPKCS8TestECKey = "3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50" var pkcs8MismatchKeyTests = []struct { hexKey string errorContains string }{ {hexKey: hexPKCS8TestECKey, errorContains: "use ParseECPrivateKey instead"}, {hexKey: hexPKCS8TestPKCS1Key, errorContains: "use ParsePKCS1PrivateKey instead"}, } func TestPKCS8MismatchKeyFormat(t *testing.T) { for i, test := range pkcs8MismatchKeyTests { derBytes, _ := hex.DecodeString(test.hexKey) _, err := ParsePKCS8PrivateKey(derBytes) if !strings.Contains(err.Error(), test.errorContains) { t.Errorf("#%d: expected error containing %q, got %s", i, test.errorContains, err) } } } google-certificate-transparency-go-2308f62/x509/pkix/000077500000000000000000000000001462611535200223215ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/pkix/pkix.go000066400000000000000000000206441462611535200236310ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package pkix contains shared, low level structures used for ASN.1 parsing // and serialization of X.509 certificates, CRL and OCSP. package pkix import ( "encoding/hex" "fmt" "math/big" "time" "github.com/google/certificate-transparency-go/asn1" ) // AlgorithmIdentifier represents the ASN.1 structure of the same name. See RFC // 5280, section 4.1.1.2. type AlgorithmIdentifier struct { Algorithm asn1.ObjectIdentifier Parameters asn1.RawValue `asn1:"optional"` } type RDNSequence []RelativeDistinguishedNameSET var attributeTypeNames = map[string]string{ "2.5.4.6": "C", "2.5.4.10": "O", "2.5.4.11": "OU", "2.5.4.3": "CN", "2.5.4.5": "SERIALNUMBER", "2.5.4.7": "L", "2.5.4.8": "ST", "2.5.4.9": "STREET", "2.5.4.17": "POSTALCODE", } // String returns a string representation of the sequence r, // roughly following the RFC 2253 Distinguished Names syntax. func (r RDNSequence) String() string { s := "" for i := 0; i < len(r); i++ { rdn := r[len(r)-1-i] if i > 0 { s += "," } for j, tv := range rdn { if j > 0 { s += "+" } oidString := tv.Type.String() typeName, ok := attributeTypeNames[oidString] if !ok { derBytes, err := asn1.Marshal(tv.Value) if err == nil { s += oidString + "=#" + hex.EncodeToString(derBytes) continue // No value escaping necessary. } typeName = oidString } valueString := fmt.Sprint(tv.Value) escaped := make([]rune, 0, len(valueString)) for k, c := range valueString { escape := false switch c { case ',', '+', '"', '\\', '<', '>', ';': escape = true case ' ': escape = k == 0 || k == len(valueString)-1 case '#': escape = k == 0 } if escape { escaped = append(escaped, '\\', c) } else { escaped = append(escaped, c) } } s += typeName + "=" + string(escaped) } } return s } type RelativeDistinguishedNameSET []AttributeTypeAndValue // AttributeTypeAndValue mirrors the ASN.1 structure of the same name in // RFC 5280, Section 4.1.2.4. type AttributeTypeAndValue struct { Type asn1.ObjectIdentifier Value interface{} } // AttributeTypeAndValueSET represents a set of ASN.1 sequences of // AttributeTypeAndValue sequences from RFC 2986 (PKCS #10). type AttributeTypeAndValueSET struct { Type asn1.ObjectIdentifier Value [][]AttributeTypeAndValue `asn1:"set"` } // Extension represents the ASN.1 structure of the same name. See RFC // 5280, section 4.2. type Extension struct { Id asn1.ObjectIdentifier Critical bool `asn1:"optional"` Value []byte } // Name represents an X.509 distinguished name. This only includes the common // elements of a DN. When parsing, all elements are stored in Names and // non-standard elements can be extracted from there. When marshaling, elements // in ExtraNames are appended and override other values with the same OID. type Name struct { Country, Organization, OrganizationalUnit []string Locality, Province []string StreetAddress, PostalCode []string SerialNumber, CommonName string Names []AttributeTypeAndValue ExtraNames []AttributeTypeAndValue } func (n *Name) FillFromRDNSequence(rdns *RDNSequence) { for _, rdn := range *rdns { if len(rdn) == 0 { continue } for _, atv := range rdn { n.Names = append(n.Names, atv) value, ok := atv.Value.(string) if !ok { continue } t := atv.Type if len(t) == 4 && t[0] == OIDAttribute[0] && t[1] == OIDAttribute[1] && t[2] == OIDAttribute[2] { switch t[3] { case OIDCommonName[3]: n.CommonName = value case OIDSerialNumber[3]: n.SerialNumber = value case OIDCountry[3]: n.Country = append(n.Country, value) case OIDLocality[3]: n.Locality = append(n.Locality, value) case OIDProvince[3]: n.Province = append(n.Province, value) case OIDStreetAddress[3]: n.StreetAddress = append(n.StreetAddress, value) case OIDOrganization[3]: n.Organization = append(n.Organization, value) case OIDOrganizationalUnit[3]: n.OrganizationalUnit = append(n.OrganizationalUnit, value) case OIDPostalCode[3]: n.PostalCode = append(n.PostalCode, value) } } } } } var ( OIDAttribute = asn1.ObjectIdentifier{2, 5, 4} OIDCountry = asn1.ObjectIdentifier{2, 5, 4, 6} OIDOrganization = asn1.ObjectIdentifier{2, 5, 4, 10} OIDOrganizationalUnit = asn1.ObjectIdentifier{2, 5, 4, 11} OIDCommonName = asn1.ObjectIdentifier{2, 5, 4, 3} OIDSerialNumber = asn1.ObjectIdentifier{2, 5, 4, 5} OIDLocality = asn1.ObjectIdentifier{2, 5, 4, 7} OIDProvince = asn1.ObjectIdentifier{2, 5, 4, 8} OIDStreetAddress = asn1.ObjectIdentifier{2, 5, 4, 9} OIDPostalCode = asn1.ObjectIdentifier{2, 5, 4, 17} OIDPseudonym = asn1.ObjectIdentifier{2, 5, 4, 65} OIDTitle = asn1.ObjectIdentifier{2, 5, 4, 12} OIDDnQualifier = asn1.ObjectIdentifier{2, 5, 4, 46} OIDName = asn1.ObjectIdentifier{2, 5, 4, 41} OIDSurname = asn1.ObjectIdentifier{2, 5, 4, 4} OIDGivenName = asn1.ObjectIdentifier{2, 5, 4, 42} OIDInitials = asn1.ObjectIdentifier{2, 5, 4, 43} OIDGenerationQualifier = asn1.ObjectIdentifier{2, 5, 4, 44} ) // appendRDNs appends a relativeDistinguishedNameSET to the given RDNSequence // and returns the new value. The relativeDistinguishedNameSET contains an // attributeTypeAndValue for each of the given values. See RFC 5280, A.1, and // search for AttributeTypeAndValue. func (n Name) appendRDNs(in RDNSequence, values []string, oid asn1.ObjectIdentifier) RDNSequence { if len(values) == 0 || oidInAttributeTypeAndValue(oid, n.ExtraNames) { return in } s := make([]AttributeTypeAndValue, len(values)) for i, value := range values { s[i].Type = oid s[i].Value = value } return append(in, s) } func (n Name) ToRDNSequence() (ret RDNSequence) { ret = n.appendRDNs(ret, n.Country, OIDCountry) ret = n.appendRDNs(ret, n.Province, OIDProvince) ret = n.appendRDNs(ret, n.Locality, OIDLocality) ret = n.appendRDNs(ret, n.StreetAddress, OIDStreetAddress) ret = n.appendRDNs(ret, n.PostalCode, OIDPostalCode) ret = n.appendRDNs(ret, n.Organization, OIDOrganization) ret = n.appendRDNs(ret, n.OrganizationalUnit, OIDOrganizationalUnit) if len(n.CommonName) > 0 { ret = n.appendRDNs(ret, []string{n.CommonName}, OIDCommonName) } if len(n.SerialNumber) > 0 { ret = n.appendRDNs(ret, []string{n.SerialNumber}, OIDSerialNumber) } for _, atv := range n.ExtraNames { ret = append(ret, []AttributeTypeAndValue{atv}) } return ret } // String returns the string form of n, roughly following // the RFC 2253 Distinguished Names syntax. func (n Name) String() string { return n.ToRDNSequence().String() } // oidInAttributeTypeAndValue reports whether a type with the given OID exists // in atv. func oidInAttributeTypeAndValue(oid asn1.ObjectIdentifier, atv []AttributeTypeAndValue) bool { for _, a := range atv { if a.Type.Equal(oid) { return true } } return false } // CertificateList represents the ASN.1 structure of the same name. See RFC // 5280, section 5.1. Use Certificate.CheckCRLSignature to verify the // signature. type CertificateList struct { TBSCertList TBSCertificateList SignatureAlgorithm AlgorithmIdentifier SignatureValue asn1.BitString } // HasExpired reports whether certList should have been updated by now. func (certList *CertificateList) HasExpired(now time.Time) bool { return !now.Before(certList.TBSCertList.NextUpdate) } // TBSCertificateList represents the ASN.1 structure TBSCertList. See RFC // 5280, section 5.1. type TBSCertificateList struct { Raw asn1.RawContent Version int `asn1:"optional,default:0"` Signature AlgorithmIdentifier Issuer RDNSequence ThisUpdate time.Time NextUpdate time.Time `asn1:"optional"` RevokedCertificates []RevokedCertificate `asn1:"optional"` Extensions []Extension `asn1:"tag:0,optional,explicit"` } // RevokedCertificate represents the unnamed ASN.1 structure that makes up the // revokedCertificates member of the TBSCertList structure. See RFC // 5280, section 5.1. type RevokedCertificate struct { SerialNumber *big.Int RevocationTime time.Time Extensions []Extension `asn1:"optional"` } google-certificate-transparency-go-2308f62/x509/ptr_sysptr_windows.go000066400000000000000000000010051462611535200256740ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.11 // +build go1.11 package x509 import ( "syscall" "unsafe" ) // For Go versions >= 1.11, the ExtraPolicyPara field in // syscall.CertChainPolicyPara is of type syscall.Pointer. See: // https://github.com/golang/go/commit/4869ec00e87ef func convertToPolicyParaType(p unsafe.Pointer) syscall.Pointer { return (syscall.Pointer)(p) } google-certificate-transparency-go-2308f62/x509/ptr_uint_windows.go000066400000000000000000000007421462611535200253160ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !go1.11 // +build !go1.11 package x509 import "unsafe" // For Go versions before 1.11, the ExtraPolicyPara field in // syscall.CertChainPolicyPara was of type uintptr. See: // https://github.com/golang/go/commit/4869ec00e87ef func convertToPolicyParaType(p unsafe.Pointer) uintptr { return uintptr(p) } google-certificate-transparency-go-2308f62/x509/revoked.go000066400000000000000000000321411462611535200233350ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "encoding/pem" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" ) // OID values for CRL extensions (TBSCertList.Extensions), RFC 5280 s5.2. var ( OIDExtensionCRLNumber = asn1.ObjectIdentifier{2, 5, 29, 20} OIDExtensionDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27} OIDExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28} ) // OID values for CRL entry extensions (RevokedCertificate.Extensions), RFC 5280 s5.3 var ( OIDExtensionCRLReasons = asn1.ObjectIdentifier{2, 5, 29, 21} OIDExtensionInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24} OIDExtensionCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29} ) // RevocationReasonCode represents the reason for a certificate revocation; see RFC 5280 s5.3.1. type RevocationReasonCode asn1.Enumerated // RevocationReasonCode values. var ( Unspecified = RevocationReasonCode(0) KeyCompromise = RevocationReasonCode(1) CACompromise = RevocationReasonCode(2) AffiliationChanged = RevocationReasonCode(3) Superseded = RevocationReasonCode(4) CessationOfOperation = RevocationReasonCode(5) CertificateHold = RevocationReasonCode(6) RemoveFromCRL = RevocationReasonCode(8) PrivilegeWithdrawn = RevocationReasonCode(9) AACompromise = RevocationReasonCode(10) ) // ReasonFlag holds a bitmask of applicable revocation reasons, from RFC 5280 s4.2.1.13 type ReasonFlag int // ReasonFlag values. const ( UnusedFlag ReasonFlag = 1 << iota KeyCompromiseFlag CACompromiseFlag AffiliationChangedFlag SupersededFlag CessationOfOperationFlag CertificateHoldFlag PrivilegeWithdrawnFlag AACompromiseFlag ) // CertificateList represents the ASN.1 structure of the same name from RFC 5280, s5.1. // It has the same content as pkix.CertificateList, but the contents include parsed versions // of any extensions. type CertificateList struct { Raw asn1.RawContent TBSCertList TBSCertList SignatureAlgorithm pkix.AlgorithmIdentifier SignatureValue asn1.BitString } // ExpiredAt reports whether now is past the expiry time of certList. func (certList *CertificateList) ExpiredAt(now time.Time) bool { return now.After(certList.TBSCertList.NextUpdate) } // Indication of whether extensions need to be critical or non-critical. Extensions that // can be either are omitted from the map. var listExtCritical = map[string]bool{ // From RFC 5280... OIDExtensionAuthorityKeyId.String(): false, // s5.2.1 OIDExtensionIssuerAltName.String(): false, // s5.2.2 OIDExtensionCRLNumber.String(): false, // s5.2.3 OIDExtensionDeltaCRLIndicator.String(): true, // s5.2.4 OIDExtensionIssuingDistributionPoint.String(): true, // s5.2.5 OIDExtensionFreshestCRL.String(): false, // s5.2.6 OIDExtensionAuthorityInfoAccess.String(): false, // s5.2.7 } var certExtCritical = map[string]bool{ // From RFC 5280... OIDExtensionCRLReasons.String(): false, // s5.3.1 OIDExtensionInvalidityDate.String(): false, // s5.3.2 OIDExtensionCertificateIssuer.String(): true, // s5.3.3 } // IssuingDistributionPoint represents the ASN.1 structure of the same // name type IssuingDistributionPoint struct { DistributionPoint distributionPointName `asn1:"optional,tag:0"` OnlyContainsUserCerts bool `asn1:"optional,tag:1"` OnlyContainsCACerts bool `asn1:"optional,tag:2"` OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"` IndirectCRL bool `asn1:"optional,tag:4"` OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"` } // TBSCertList represents the ASN.1 structure of the same name from RFC // 5280, section 5.1. It has the same content as pkix.TBSCertificateList // but the extensions are included in a parsed format. type TBSCertList struct { Raw asn1.RawContent Version int Signature pkix.AlgorithmIdentifier Issuer pkix.RDNSequence ThisUpdate time.Time NextUpdate time.Time RevokedCertificates []*RevokedCertificate Extensions []pkix.Extension // Cracked out extensions: AuthorityKeyID []byte IssuerAltNames GeneralNames CRLNumber int BaseCRLNumber int // -1 if no delta CRL present IssuingDistributionPoint IssuingDistributionPoint IssuingDPFullNames GeneralNames FreshestCRLDistributionPoint []string OCSPServer []string IssuingCertificateURL []string } // ParseCertificateList parses a CertificateList (e.g. a CRL) from the given // bytes. It's often the case that PEM encoded CRLs will appear where they // should be DER encoded, so this function will transparently handle PEM // encoding as long as there isn't any leading garbage. func ParseCertificateList(clBytes []byte) (*CertificateList, error) { if bytes.HasPrefix(clBytes, pemCRLPrefix) { block, _ := pem.Decode(clBytes) if block != nil && block.Type == pemType { clBytes = block.Bytes } } return ParseCertificateListDER(clBytes) } // ParseCertificateListDER parses a DER encoded CertificateList from the given bytes. // For non-fatal errors, this function returns both an error and a CertificateList // object. func ParseCertificateListDER(derBytes []byte) (*CertificateList, error) { var errs Errors // First parse the DER into the pkix structures. pkixList := new(pkix.CertificateList) if rest, err := asn1.Unmarshal(derBytes, pkixList); err != nil { errs.AddID(ErrInvalidCertList, err) return nil, &errs } else if len(rest) != 0 { errs.AddID(ErrTrailingCertList) return nil, &errs } // Transcribe the revoked certs but crack out extensions. revokedCerts := make([]*RevokedCertificate, len(pkixList.TBSCertList.RevokedCertificates)) for i, pkixRevoked := range pkixList.TBSCertList.RevokedCertificates { revokedCerts[i] = parseRevokedCertificate(pkixRevoked, &errs) if revokedCerts[i] == nil { return nil, &errs } } certList := CertificateList{ Raw: derBytes, TBSCertList: TBSCertList{ Raw: pkixList.TBSCertList.Raw, Version: pkixList.TBSCertList.Version, Signature: pkixList.TBSCertList.Signature, Issuer: pkixList.TBSCertList.Issuer, ThisUpdate: pkixList.TBSCertList.ThisUpdate, NextUpdate: pkixList.TBSCertList.NextUpdate, RevokedCertificates: revokedCerts, Extensions: pkixList.TBSCertList.Extensions, CRLNumber: -1, BaseCRLNumber: -1, }, SignatureAlgorithm: pkixList.SignatureAlgorithm, SignatureValue: pkixList.SignatureValue, } // Now crack out extensions. for _, e := range certList.TBSCertList.Extensions { if expectCritical, present := listExtCritical[e.Id.String()]; present { if e.Critical && !expectCritical { errs.AddID(ErrUnexpectedlyCriticalCertListExtension, e.Id) } else if !e.Critical && expectCritical { errs.AddID(ErrUnexpectedlyNonCriticalCertListExtension, e.Id) } } switch { case e.Id.Equal(OIDExtensionAuthorityKeyId): // RFC 5280 s5.2.1 var a authKeyId if rest, err := asn1.Unmarshal(e.Value, &a); err != nil { errs.AddID(ErrInvalidCertListAuthKeyID, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingCertListAuthKeyID) } certList.TBSCertList.AuthorityKeyID = a.Id case e.Id.Equal(OIDExtensionIssuerAltName): // RFC 5280 s5.2.2 if err := parseGeneralNames(e.Value, &certList.TBSCertList.IssuerAltNames); err != nil { errs.AddID(ErrInvalidCertListIssuerAltName, err) } case e.Id.Equal(OIDExtensionCRLNumber): // RFC 5280 s5.2.3 if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.CRLNumber); err != nil { errs.AddID(ErrInvalidCertListCRLNumber, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingCertListCRLNumber) } if certList.TBSCertList.CRLNumber < 0 { errs.AddID(ErrNegativeCertListCRLNumber, certList.TBSCertList.CRLNumber) } case e.Id.Equal(OIDExtensionDeltaCRLIndicator): // RFC 5280 s5.2.4 if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.BaseCRLNumber); err != nil { errs.AddID(ErrInvalidCertListDeltaCRL, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingCertListDeltaCRL) } if certList.TBSCertList.BaseCRLNumber < 0 { errs.AddID(ErrNegativeCertListDeltaCRL, certList.TBSCertList.BaseCRLNumber) } case e.Id.Equal(OIDExtensionIssuingDistributionPoint): parseIssuingDistributionPoint(e.Value, &certList.TBSCertList.IssuingDistributionPoint, &certList.TBSCertList.IssuingDPFullNames, &errs) case e.Id.Equal(OIDExtensionFreshestCRL): // RFC 5280 s5.2.6 if err := parseDistributionPoints(e.Value, &certList.TBSCertList.FreshestCRLDistributionPoint); err != nil { errs.AddID(ErrInvalidCertListFreshestCRL, err) return nil, err } case e.Id.Equal(OIDExtensionAuthorityInfoAccess): // RFC 5280 s5.2.7 var aia []accessDescription if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil { errs.AddID(ErrInvalidCertListAuthInfoAccess, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingCertListAuthInfoAccess) } for _, v := range aia { // GeneralName: uniformResourceIdentifier [6] IA5String if v.Location.Tag != tagURI { continue } switch { case v.Method.Equal(OIDAuthorityInfoAccessOCSP): certList.TBSCertList.OCSPServer = append(certList.TBSCertList.OCSPServer, string(v.Location.Bytes)) case v.Method.Equal(OIDAuthorityInfoAccessIssuers): certList.TBSCertList.IssuingCertificateURL = append(certList.TBSCertList.IssuingCertificateURL, string(v.Location.Bytes)) } // TODO(drysdale): cope with more possibilities } default: if e.Critical { errs.AddID(ErrUnhandledCriticalCertListExtension, e.Id) } } } if errs.Fatal() { return nil, &errs } if errs.Empty() { return &certList, nil } return &certList, &errs } func parseIssuingDistributionPoint(data []byte, idp *IssuingDistributionPoint, name *GeneralNames, errs *Errors) { // RFC 5280 s5.2.5 if rest, err := asn1.Unmarshal(data, idp); err != nil { errs.AddID(ErrInvalidCertListIssuingDP, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingCertListIssuingDP) } typeCount := 0 if idp.OnlyContainsUserCerts { typeCount++ } if idp.OnlyContainsCACerts { typeCount++ } if idp.OnlyContainsAttributeCerts { typeCount++ } if typeCount > 1 { errs.AddID(ErrCertListIssuingDPMultipleTypes, idp.OnlyContainsUserCerts, idp.OnlyContainsCACerts, idp.OnlyContainsAttributeCerts) } for _, fn := range idp.DistributionPoint.FullName { if _, err := parseGeneralName(fn.FullBytes, name, false); err != nil { errs.AddID(ErrCertListIssuingDPInvalidFullName, err) } } } // RevokedCertificate represents the unnamed ASN.1 structure that makes up the // revokedCertificates member of the TBSCertList structure from RFC 5280, s5.1. // It has the same content as pkix.RevokedCertificate but the extensions are // included in a parsed format. type RevokedCertificate struct { pkix.RevokedCertificate // Cracked out extensions: RevocationReason RevocationReasonCode InvalidityDate time.Time Issuer GeneralNames } func parseRevokedCertificate(pkixRevoked pkix.RevokedCertificate, errs *Errors) *RevokedCertificate { result := RevokedCertificate{RevokedCertificate: pkixRevoked} for _, e := range pkixRevoked.Extensions { if expectCritical, present := certExtCritical[e.Id.String()]; present { if e.Critical && !expectCritical { errs.AddID(ErrUnexpectedlyCriticalRevokedCertExtension, e.Id) } else if !e.Critical && expectCritical { errs.AddID(ErrUnexpectedlyNonCriticalRevokedCertExtension, e.Id) } } switch { case e.Id.Equal(OIDExtensionCRLReasons): // RFC 5280, s5.3.1 var reason asn1.Enumerated if rest, err := asn1.Unmarshal(e.Value, &reason); err != nil { errs.AddID(ErrInvalidRevocationReason, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingRevocationReason) } result.RevocationReason = RevocationReasonCode(reason) case e.Id.Equal(OIDExtensionInvalidityDate): // RFC 5280, s5.3.2 if rest, err := asn1.Unmarshal(e.Value, &result.InvalidityDate); err != nil { errs.AddID(ErrInvalidRevocationInvalidityDate, err) } else if len(rest) != 0 { errs.AddID(ErrTrailingRevocationInvalidityDate) } case e.Id.Equal(OIDExtensionCertificateIssuer): // RFC 5280, s5.3.3 if err := parseGeneralNames(e.Value, &result.Issuer); err != nil { errs.AddID(ErrInvalidRevocationIssuer, err) } default: if e.Critical { errs.AddID(ErrUnhandledCriticalRevokedCertExtension, e.Id) } } } return &result } // CheckCertificateListSignature checks that the signature in crl is from c. func (c *Certificate) CheckCertificateListSignature(crl *CertificateList) error { algo := SignatureAlgorithmFromAI(crl.SignatureAlgorithm) return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) } google-certificate-transparency-go-2308f62/x509/revoked_test.go000066400000000000000000002575041462611535200244100ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "encoding/pem" "math/big" "reflect" "strings" "testing" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" ) func TestParseCertificateList(t *testing.T) { var tests = []struct { desc string data string // as hex want TBSCertList wantErr string }{ { desc: "valid-certlist", data: ("3082026c" + // SEQUENCE CertificateList ("30820154" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3081a4" + // SEQUENCE OF ("3027" + ("0208" + "764bedd38afd51f7") + // serial number ("170d" + "3137303131333134313835385a") + // revocation time ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0103")))) + ("3027" + ("0208" + "3b772e5f1202118e") + ("170d" + "3137303531303130353530375a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0101")))) + ("3027" + ("0208" + "0b54e3090079ad4b") + ("170d" + "3137303431323038353331375a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0101")))) + ("3027" + ("0208" + "31da3380182af9b2") + ("170d" + "3136303931353230323231335a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0103"))))) + ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{ { RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(0x764bedd38afd51f7), RevocationTime: time.Date(2017, 1, 13, 14, 18, 58, 0, time.UTC), }, RevocationReason: AffiliationChanged, }, { RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(0x3b772e5f1202118e), RevocationTime: time.Date(2017, 5, 10, 10, 55, 7, 0, time.UTC), }, RevocationReason: KeyCompromise, }, { RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(0x0b54e3090079ad4b), RevocationTime: time.Date(2017, 4, 12, 8, 53, 17, 0, time.UTC), }, RevocationReason: KeyCompromise, }, { RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(0x31da3380182af9b2), RevocationTime: time.Date(2016, 9, 15, 20, 22, 13, 0, time.UTC), }, RevocationReason: AffiliationChanged, }, }, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, }, }, { desc: "invalid-cert-critical-ext-revocation-time", data: ("3082026f" + // SEQUENCE CertificateList ("30820157" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3081a7" + // SEQUENCE OF ("302a" + ("0208" + "764bedd38afd51f7") + // serial number ("170d" + "3137303131333134313835385a") + // revocation time ("300f" + ("300d" + ("0603" + "551d15") + ("0101ff") + // INVALID critical: true ("0403" + "0a0103")))) + ("3027" + ("0208" + "3b772e5f1202118e") + ("170d" + "3137303531303130353530375a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0101")))) + ("3027" + ("0208" + "0b54e3090079ad4b") + ("170d" + "3137303431323038353331375a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0101")))) + ("3027" + ("0208" + "31da3380182af9b2") + ("170d" + "3136303931353230323231335a") + ("300c" + ("300a" + ("0603" + "551d15") + ("0403" + "0a0103"))))) + ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "marked critical", }, { desc: "invalid-unknown-critical-ext", data: ("308201c9" + // SEQUENCE CertificateList ("3081b2" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a033" + ("3031" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1f") + // OID: unknown ("0101ff") + // critical: true ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "unhandled critical extension", }, { desc: "invalid-unknown-ext-trailing-data", data: ("308201c9" + // SEQUENCE CertificateList ("3081b2" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a033" + ("3031" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1f") + // OID: unknown ("010100") + // critical: false ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369") + "00"), wantErr: "trailing data", }, { desc: "invalid-wrong-asn1", data: "0a0101", wantErr: "structure error", }, // The following example is used as the template for other variations { desc: "valid-empty-certlist", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, }, }, { desc: "valid-delta-crl-indicator-ext", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1b") + // OID: delta-crl-indicator ("0101ff") + // critical: true ("0404" + "02020120")) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: 288, }, }, { desc: "invalid-delta-crl-indicator-ext-non-critical", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1b") + // OID: delta-crl-indicator ("010100") + // INVALID: critical: false ("0404" + "02020120")) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "marked non-critical", }, { desc: "invalid-delta-crl-indicator-ext-wrong-asn1", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1b") + // OID: delta-crl-indicator ("0101ff") + // critical: true ("0404" + "0a020123")) + // INVALID: tag ENUM not int ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "failed to unmarshal", }, { desc: "invalid-delta-crl-indicator-ext-trailing-data", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1b") + // OID: delta-crl-indicator ("0101ff") + // critical: true ("0404" + "020101DD")) + // INVALID: trailing data ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "trailing data", }, { desc: "invalid-delta-crl-indicator-ext-negative", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d1b") + // OID: delta-crl-indicator ("0101ff") + // critical: true ("0404" + "02028120")) + // INVALID: negative base CRL ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "negative", }, { desc: "invalid-crl-number-ext-critical", data: ("308201c9" + // SEQUENCE CertificateList ("3081b2" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a033" + ("3031" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d14") + // OID: CRL-number ("0101ff") + // critical: true ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "marked critical", }, { desc: "invalid-crl-number-ext-trailing-data", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "0201" + "0623"))))) + // INVALID: trailing data ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "trailing data", }, { desc: "invalid-crl-number-ext-negative", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "0202" + "8623"))))) + // INVALID: negative value ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "negative", }, { desc: "invalid-crl-number-ext-wrong-asn1", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "0a02" + "0623"))))) + // INVALID: enum tag ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "structure error", }, { desc: "invalid-auth-key-id-ext-trailing-data", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3015" + "8013" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + // INVALID: trailing data ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "0202" + "0623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "trailing data", }, { desc: "invalid-auth-key-id-ext-wrong-asn1", data: ("308201c6" + // SEQUENCE CertificateList ("3081af" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // no revoked certs ("a030" + ("302e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3116" + // INVALID: set not sequence "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "0202" + "0623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "failed to unmarshal", }, { desc: "valid-auth-info-access-ext-ca-issuer", data: ("308201ee" + // SEQUENCE CertificateList ("3081d7" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a058" + ("3056" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("3026" + ("0608" + "2b06010505070101") + // OID: authority-info-access ("041a" + ("3018" + ("3016" + ("0608" + "2b06010505073002") + // OID: CA issuers ("860a" + "687474703a2f2f777777"))))) + // 'http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, IssuingCertificateURL: []string{"http://www"}, }, }, { desc: "valid-auth-info-access-ext-ocsp-server", data: ("308201ee" + // SEQUENCE CertificateList ("3081d7" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a058" + ("3056" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("3026" + ("0608" + "2b06010505070101") + // OID: authority-info-access ("041a" + ("3018" + ("3016" + ("0608" + "2b06010505073001") + // OID: OCSP ("860a" + "687474703a2f2f777777"))))) + // 'http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, OCSPServer: []string{"http://www"}, }, }, { desc: "valid-auth-info-access-ext-non-uri-ignored", data: ("308201ee" + // SEQUENCE CertificateList ("3081d7" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a058" + ("3056" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("3026" + ("0608" + "2b06010505070101") + // OID: authority-info-access ("041a" + ("3018" + ("3016" + ("0608" + "2b06010505073001") + // OID: OCSP ("820a" + "687474703a2f2f777777"))))) + // dNSName: 'http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, }, }, { desc: "invalid-auth-info-access-ext-wrong-asn1", data: ("308201ee" + // SEQUENCE CertificateList ("3081d7" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a058" + ("3056" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("3026" + ("0608" + "2b06010505070101") + // OID: authority-info-access ("041a" + ("3018" + ("3116" + // INVALID: set not sequence ("0608" + "2b06010505073002") + // OID: CA issuers ("860a" + "687474703a2f2f777777"))))) + // 'http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "failed to unmarshal", }, { desc: "invalid-auth-info-access-ext-trailing-data", data: ("308201ee" + // SEQUENCE CertificateList ("3081d7" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a058" + ("3056" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("3026" + ("0608" + "2b06010505070101") + // OID: authority-info-access ("041a" + ("3017" + ("3015" + ("0608" + "2b06010505073002") + // OID: CA issuers ("8609" + "687474703a2f2f7777"))) + "77")) + // INVALID: trailing data ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "trailing data", }, { desc: "valid-issuer-alt-name-ext", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d12") + // OID: issuer-alt-name ("0407" + ("3005" + "8203" + "777777"))) + // [2] dNSName = 'www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, IssuerAltNames: GeneralNames{DNSNames: []string{"www"}}, }, }, { desc: "invalid-issuer-alt-name-ext", data: ("308201d6" + // SEQUENCE CertificateList ("3081bf" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a040" + ("303e" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300e" + ("0603" + "551d12") + // OID: issuer-alt-name ("0407" + ("3005" + "8903" + "777777"))) + // INVALID: tag 9 not used ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "failed to parse", }, { desc: "valid-freshest-crl-ext", data: ("308201e3" + // SEQUENCE CertificateList ("3081cc" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a04d" + ("304b" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("301b" + ("0603" + "551d2e") + // OID: freshest-crl ("0414" + ("3012" + ("3010" + ("a00e" + ("a00c" + ("860a" + "687474703a2f2f777777"))))))) + // uRI='http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, FreshestCRLDistributionPoint: []string{"http://www"}, }, }, { desc: "invalid-freshest-crl-ext", data: ("308201e3" + // SEQUENCE CertificateList ("3081cc" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a04d" + ("304b" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("301b" + ("0603" + "551d2e") + // OID: freshest-crl ("0414" + ("3112" + // INVALID: set-of not sequence-of ("3010" + ("a00e" + ("a00c" + ("860a" + "687474703a2f2f777777"))))))) + // uRI='http://www' ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "structure error", }, { desc: "valid-issuing-dp-ext", data: ("308201d7" + // SEQUENCE CertificateList ("3081c0" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a041" + ("303f" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300f" + ("0603" + "551d1c") + // OID: issuing-distribution-point ("0101ff") + // critical: true ("0405" + ("3003" + // SEQUENCE "8101" + "ff"))) + // [1]: onlyContainsUserCerts: true ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), want: TBSCertList{ Version: 1, Signature: pkix.AlgorithmIdentifier{ Algorithm: oidSignatureSHA256WithRSA, Parameters: asn1.RawValue{Class: 0, Tag: 5, Bytes: []byte{}, FullBytes: []byte{5, 0}}, }, Issuer: pkix.RDNSequence{ []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCountry, Value: "US"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDOrganization, Value: "Google Inc"}, }, []pkix.AttributeTypeAndValue{ {Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"}, }, }, ThisUpdate: time.Date(2017, 6, 29, 01, 0, 2, 0, time.UTC), NextUpdate: time.Date(2017, 7, 9, 01, 0, 2, 0, time.UTC), RevokedCertificates: []*RevokedCertificate{}, AuthorityKeyID: fromHex("4add06161bbcf668b576f581b6bb621aba5a812f"), CRLNumber: 1571, BaseCRLNumber: -1, IssuingDistributionPoint: IssuingDistributionPoint{OnlyContainsUserCerts: true}, }, }, { desc: "invalid-issuing-dp-ext", data: ("308201d7" + // SEQUENCE CertificateList ("3081c0" + // SEQUENCE TBSCertList ("0201" + "01") + // version 2(0x01) ("300d" + // SEQUENCE AlgorithmIdentifier ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("3049" + // SEQUENCE Name ("310b" + ("3009" + ("0603" + "550406") + // OID: country ("1302" + "5553"))) + // "US" ("3113" + ("3011" + ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63"))) + // "Google Inc" ("3125" + ("3023" + ("0603" + "550403") + // OID: commonName ("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732")))) + ("170d" + "3137303632393031303030325a") + // UTCTime ("170d" + "3137303730393031303030325a") + // UTCTime ("3000") + // SEQUENCE OF no revoked certs ("a041" + ("303f" + ("301f" + ("0603" + "551d23") + // OID: authority-key-id ("0418" + ("3016" + "8014" + "4add06161bbcf668b576f581b6bb621aba5a812f"))) + ("300f" + ("0603" + "551d1c") + // OID: issuing-distribution-point ("0101ff") + // critical: true ("0405" + ("3103" + // INVALID: SET not SEQUENCE "8101" + "ff"))) + // [1]: onlyContainsUserCerts: true ("300b" + ("0603" + "551d14") + // OID: CRL-number ("0404" + "02020623"))))) + ("300d" + ("0609" + "2a864886f70d01010b") + // OID: sha256WithRSA "0500") + // NULL ("03820101" + // BIT STRING length 0x101 "004dcde29667973239cca344c58b72128fb5c5db03efdc75cfb7d9a0410ec03c8cd21160b449cd80224f41ca9d91529295ef7d0179ca4b08bb688cecce13cc07b20ecd87ffde1bc356554083c40bea7a387dacc54b3848b3710acf2fa613d007b12afc37f0a77082655b8dbb6683ba2fc52555e9f74bb5ba9429377ff38e193e799fc05c4c9bbcee29492945a732db67ba3575a79a83427a1f6d18d9ede01c544f3ccd68e5680a9b5418e03e1d80b3e77e69860982a4d21c6b111b07c87fe32c561e871554896b37651d5aaf42b2d092ce8d4dd4ae1d7a97091c0a06c03d71580e0557a51408513fde3012f02dac76536822a564faa2553048729633b68f1fc369")), wantErr: "failed to unmarshal", }, } for _, test := range tests { inData := fromHex(test.data) got, err := ParseCertificateList(inData) if err != nil { if test.wantErr == "" { t.Errorf("ParseCertificateList(%q)=%+v,%v; want _,nil", test.desc, got, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("ParseCertificateList(%q)=%+v,%v; want _,%q", test.desc, got, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("ParseCertificateList(%q)=%+v,nil; want _,%q", test.desc, got, test.wantErr) continue } // Zero out unparsed extensions before comparison to make test data simpler. got.TBSCertList.Raw = nil got.TBSCertList.Extensions = nil for _, rc := range got.TBSCertList.RevokedCertificates { rc.Extensions = nil } if !reflect.DeepEqual(got.TBSCertList, test.want) { t.Errorf("ParseCertificateList(%q)=%+v; want %+v", test.desc, got.TBSCertList, test.want) } } } func TestParseRevokedCertificate(t *testing.T) { var tests = []struct { desc string data string // as hex want RevokedCertificate wantErr string }{ // CRL Reason { desc: "valid-reason-ext", data: ("3027" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300c" + // extensions ("300a" + // extension ("0603" + "551d15") + // OID: reason ("0403" + // octet string "0a01" + "01")))), // enum:1 want: RevokedCertificate{ RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(4284944556325212558), RevocationTime: time.Date(2017, 05, 10, 10, 55, 07, 0, time.UTC), Extensions: []pkix.Extension{ { Id: OIDExtensionCRLReasons, Critical: false, Value: fromHex("0a01" + "01"), }, }, }, RevocationReason: KeyCompromise, }, }, { desc: "invalid-reason-ext-wrong-type", data: ("3027" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300c" + // extensions ("300a" + // extension ("0603" + "551d15") + // OID: reason ("0403" + // octet string "0201" + "01")))), // int:1 wantErr: "tags don't match", }, { desc: "invalid-reason-ext-trailing-data", data: ("3028" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300d" + // extensions ("300b" + // extension ("0603" + "551d15") + // OID: reason ("0404" + // octet string "0a01" + "01" + "aa")))), // enum:1 wantErr: "trailing data", }, { desc: "invalid-reason-ext-critical", data: ("302b" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("3010" + // extensions ("300e" + // extension ("0603" + "551d15") + // OID: reason ("0101ff") + // critical: true ("0404" + // octet string "0a01" + "01" + "aa")))), // enum:1 wantErr: "marked critical", }, // Invalidity Date { desc: "valid-invalidity-date-ext", data: ("3033" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("3018" + // extensions ("3016" + // extension ("0603" + "551d18") + // OID: invalidity date ("040f" + // octet string "170d" + "3137303531303130353530375a")))), want: RevokedCertificate{ RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(4284944556325212558), RevocationTime: time.Date(2017, 05, 10, 10, 55, 07, 0, time.UTC), Extensions: []pkix.Extension{ { Id: OIDExtensionInvalidityDate, Critical: false, Value: fromHex("170d" + "3137303531303130353530375a"), }, }, }, InvalidityDate: time.Date(2017, 05, 10, 10, 55, 07, 0, time.UTC), }, }, { desc: "invalid-invalidity-date-ext-wrong-type", data: ("3027" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300c" + // extensions ("300a" + // extension ("0603" + "551d18") + // OID: invalidity date ("0403" + // octet string "0a01" + "01")))), // enum:1 wantErr: "failed to parse", }, { desc: "invalid-invalidity-date-ext-trailing-data", data: ("3036" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("301b" + // extensions ("3019" + // extension ("0603" + "551d18") + // OID: invalidity date ("0412" + // octet string "170d" + "3137303531303130353530375a" + "0a0101")))), wantErr: "trailing data", }, { desc: "invalid-invalidity-date-ext-critical", data: ("3036" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("301b" + // extensions ("3019" + // extension ("0603" + "551d18") + // OID: invalidity date ("0101ff") + // critical: true ("040f" + // octet string "170d" + "3137303531303130353530375a")))), wantErr: "marked critical", }, // Issuer { desc: "valid-issuer-ext", data: ("303b" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("3020" + // extensions ("301e" + // extension ("0603" + "551d1d") + // OID: issuer ("0101ff") + // critical: true ("0414" + // octet string ("3012" + ("8210" + "7777772e676f6f676c652e636f2e756b")))))), // "www.google.co.uk" want: RevokedCertificate{ RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(4284944556325212558), RevocationTime: time.Date(2017, 05, 10, 10, 55, 07, 0, time.UTC), Extensions: []pkix.Extension{ { Id: OIDExtensionCertificateIssuer, Critical: true, Value: fromHex("3012" + ("8210" + "7777772e676f6f676c652e636f2e756b")), }, }, }, Issuer: GeneralNames{ DNSNames: []string{"www.google.co.uk"}, }, }, }, { desc: "invalid-issuer-ext-wrong-type", data: ("302a" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300f" + // extensions ("300d" + // extension ("0603" + "551d1d") + // OID: issuer ("0101ff") + // critical: true ("0403" + // octet string "0a01" + "01")))), // enum:1 wantErr: "failed to parse", }, { desc: "invalid-issuer-ext-non-critical", data: ("303b" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("3020" + // extensions ("301e" + // extension ("0603" + "551d1d") + // OID: issuer ("010100") + // critical: false ("0414" + // octet string ("3012" + ("8210" + "7777772e676f6f676c652e636f2e756b")))))), // "www.google.co.uk" wantErr: "marked non-critical", }, // Unknown extension { desc: "valid-unknown-ext", data: ("3027" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300c" + // extensions ("300a" + // extension ("0603" + "551d14") + // OID: CRL number ("0403" + // octet string "0a01" + "01")))), // enum:1 want: RevokedCertificate{ RevokedCertificate: pkix.RevokedCertificate{ SerialNumber: big.NewInt(4284944556325212558), RevocationTime: time.Date(2017, 05, 10, 10, 55, 07, 0, time.UTC), Extensions: []pkix.Extension{ { Id: OIDExtensionCRLNumber, Critical: false, Value: fromHex("0a01" + "01"), }, }, }, }, }, { desc: "invalid-unknown-ext-critical", data: ("302a" + // sequence ("0208" + "3b772e5f1202118e") + // serial number ("170d" + "3137303531303130353530375a") + // revocation time ("300f" + // extensions ("300d" + // extension ("0603" + "551d14") + // OID: CRL number ("0101ff") + // critical: true ("0403" + // octet string "0a01" + "01")))), // enum:1 wantErr: "unhandled critical extension", }, } for _, test := range tests { inData := fromHex(test.data) var pkixCert pkix.RevokedCertificate if _, err := asn1.Unmarshal(inData, &pkixCert); err != nil { t.Errorf("asn1.Unmarshal(%s)=_,%v; want _,nil", test.data, err) continue } var errs Errors got := parseRevokedCertificate(pkixCert, &errs) if len(errs.Errs) > 0 { err := errs.Errs[0] if test.wantErr == "" { t.Errorf("parseRevokedCertificate(%q)=%+v,%v; want _,nil", test.desc, got, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("parseRevokedCertificate(%q)=%+v,%v; want _,%q", test.desc, got, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("parseRevokedCertificate(%q)=%+v,nil; want _,%q", test.desc, got, test.wantErr) continue } if !reflect.DeepEqual(got, &test.want) { t.Errorf("parseRevokedCertificate(%q)=%+v; want %+v", test.desc, got, test.want) } } } func TestParseIssuingDistributionPoint(t *testing.T) { var tests = []struct { data string // as hex want IssuingDistributionPoint wantErr string }{ { data: ("3003" + "8101ff"), want: IssuingDistributionPoint{OnlyContainsUserCerts: true}, }, { data: ("3003" + "8201ff"), want: IssuingDistributionPoint{OnlyContainsCACerts: true}, }, { data: ("3003" + "8501ff"), want: IssuingDistributionPoint{OnlyContainsAttributeCerts: true}, }, { data: ("3006" + "810100" + "8501ff"), want: IssuingDistributionPoint{OnlyContainsAttributeCerts: true}, }, { data: ("3009" + // SEQUENCE ("a007" + // tag [0] = distributionPoint / DistributionPointName ("a005" + // CHOICE [0] = fullName / GeneralNames "8203" + "777777"))), // CHOICE [2] = dNSName want: IssuingDistributionPoint{ DistributionPoint: distributionPointName{ FullName: []asn1.RawValue{ { Class: asn1.ClassContextSpecific, Tag: 2, IsCompound: false, Bytes: fromHex("777777"), FullBytes: fromHex("8203777777"), }, }, }, }, }, { data: ("3019" + // SEQUENCE ("a017" + // tag [0] = distributionPoint / DistributionPointName ("a115" + // CHOICE [1] = nameRelativeToCRLIssuer / RelativeDistinguishedName ("3113" + // SET OF ("3011" + // SEQUENCE ("0603" + "55040a") + // OID: organization ("130a" + "476f6f676c6520496e63")))))), // "Google Inc" want: IssuingDistributionPoint{ DistributionPoint: distributionPointName{ RelativeName: pkix.RDNSequence{ pkix.RelativeDistinguishedNameSET{ pkix.AttributeTypeAndValue{ Type: pkix.OIDOrganization, Value: "Google Inc", }, }, }, }, }, }, { data: ("3006" + "8101ff" + "8501ff"), wantErr: "multiple cert", }, { data: ("3003" + "8501ff" + "00"), wantErr: "trailing data", }, { data: ("3103" + "8101ff"), // INVALID: SET not SEQUENCE wantErr: "failed to unmarshal", }, { data: ("3009" + // SEQUENCE ("a007" + // tag [0] = distributionPoint / DistributionPointName ("a005" + // CHOICE [0] = fullName / GeneralNames "8903" + "777777"))), // INVALID: choice 9 not allowed wantErr: "failed to unmarshal GeneralName", }, } for _, test := range tests { inData := fromHex(test.data) var got IssuingDistributionPoint var gn GeneralNames var errs Errors parseIssuingDistributionPoint(inData, &got, &gn, &errs) if !errs.Empty() { err := errs.Errs[0] if test.wantErr == "" { t.Errorf("asn1.Unmarshal(%s)=_,%v; want _,nil", test.data, err) } else if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("asn1.Unmarshal(%s)=_,%v; want _,%q", test.data, err, test.wantErr) } continue } if test.wantErr != "" { t.Errorf("asn1.Unmarshal(%s)=%+v,nil; want _,%q", test.data, got, test.wantErr) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("asn1.Unmarshal(%s)=%+v; want %+v", test.data, got, test.want) } } } // CRL for Google Internet Authority G2: // // Certificate Revocation List (CRL): // Version 2 (0x1) // Signature Algorithm: sha256WithRSAEncryption // Issuer: /C=US/O=Google Inc/CN=Google Internet Authority G2 // Last Update: Jun 29 01:00:02 2017 GMT // Next Update: Jul 9 01:00:02 2017 GMT // CRL extensions: // X509v3 Authority Key Identifier: // keyid:4A:DD:06:16:1B:BC:F6:68:B5:76:F5:81:B6:BB:62:1A:BA:5A:81:2F // X509v3 CRL Number: // 1571 // Revoked Certificates: // Serial Number: 764BEDD38AFD51F7 // Revocation Date: Jan 13 14:18:58 2017 GMT // CRL entry extensions: // X509v3 CRL Reason Code: // Affiliation Changed // Serial Number: 3B772E5F1202118E // Revocation Date: May 10 10:55:07 2017 GMT // CRL entry extensions: // X509v3 CRL Reason Code: // Key Compromise // Serial Number: 0B54E3090079AD4B // Revocation Date: Apr 12 08:53:17 2017 GMT // CRL entry extensions: // X509v3 CRL Reason Code: // Key Compromise // Serial Number: 31DA3380182AF9B2 // Revocation Date: Sep 15 20:22:13 2016 GMT // CRL entry extensions: // X509v3 CRL Reason Code: // Affiliation Changed // Signature Algorithm: sha256WithRSAEncryption // 4d:cd:e2:96:67:97:32:39:cc:a3:44:c5:8b:72:12:8f:b5:c5: // db:03:ef:dc:75:cf:b7:d9:a0:41:0e:c0:3c:8c:d2:11:60:b4: // 49:cd:80:22:4f:41:ca:9d:91:52:92:95:ef:7d:01:79:ca:4b: // 08:bb:68:8c:ec:ce:13:cc:07:b2:0e:cd:87:ff:de:1b:c3:56: // 55:40:83:c4:0b:ea:7a:38:7d:ac:c5:4b:38:48:b3:71:0a:cf: // 2f:a6:13:d0:07:b1:2a:fc:37:f0:a7:70:82:65:5b:8d:bb:66: // 83:ba:2f:c5:25:55:e9:f7:4b:b5:ba:94:29:37:7f:f3:8e:19: // 3e:79:9f:c0:5c:4c:9b:bc:ee:29:49:29:45:a7:32:db:67:ba: // 35:75:a7:9a:83:42:7a:1f:6d:18:d9:ed:e0:1c:54:4f:3c:cd: // 68:e5:68:0a:9b:54:18:e0:3e:1d:80:b3:e7:7e:69:86:09:82: // a4:d2:1c:6b:11:1b:07:c8:7f:e3:2c:56:1e:87:15:54:89:6b: // 37:65:1d:5a:af:42:b2:d0:92:ce:8d:4d:d4:ae:1d:7a:97:09: // 1c:0a:06:c0:3d:71:58:0e:05:57:a5:14:08:51:3f:de:30:12: // f0:2d:ac:76:53:68:22:a5:64:fa:a2:55:30:48:72:96:33:b6: // 8f:1f:c3:69 const giag2CRL = `-----BEGIN X509 CRL----- MIICbDCCAVQCAQEwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UEBhMCVVMxEzARBgNV BAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3Jp dHkgRzIXDTE3MDYyOTAxMDAwMloXDTE3MDcwOTAxMDAwMlowgaQwJwIIdkvt04r9 UfcXDTE3MDExMzE0MTg1OFowDDAKBgNVHRUEAwoBAzAnAgg7dy5fEgIRjhcNMTcw NTEwMTA1NTA3WjAMMAoGA1UdFQQDCgEBMCcCCAtU4wkAea1LFw0xNzA0MTIwODUz MTdaMAwwCgYDVR0VBAMKAQEwJwIIMdozgBgq+bIXDTE2MDkxNTIwMjIxM1owDDAK BgNVHRUEAwoBA6AwMC4wHwYDVR0jBBgwFoAUSt0GFhu89mi1dvWBtrtiGrpagS8w CwYDVR0UBAQCAgYjMA0GCSqGSIb3DQEBCwUAA4IBAQBNzeKWZ5cyOcyjRMWLchKP tcXbA+/cdc+32aBBDsA8jNIRYLRJzYAiT0HKnZFSkpXvfQF5yksIu2iM7M4TzAey Ds2H/94bw1ZVQIPEC+p6OH2sxUs4SLNxCs8vphPQB7Eq/Dfwp3CCZVuNu2aDui/F JVXp90u1upQpN3/zjhk+eZ/AXEybvO4pSSlFpzLbZ7o1daeag0J6H20Y2e3gHFRP PM1o5WgKm1QY4D4dgLPnfmmGCYKk0hxrERsHyH/jLFYehxVUiWs3ZR1ar0Ky0JLO jU3Urh16lwkcCgbAPXFYDgVXpRQIUT/eMBLwLax2U2gipWT6olUwSHKWM7aPH8Np -----END X509 CRL-----` // Certificate for GIAG2: // // Data: // Version: 3 (0x2) // Serial Number: // 01:00:21:25:88:b0:fa:59:a7:77:ef:05:7b:66:27:df // Signature Algorithm: sha256WithRSAEncryption // Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA // Validity // Not Before: May 22 11:32:37 2017 GMT // Not After : Dec 31 23:59:59 2018 GMT // Subject: C=US, O=Google Inc, CN=Google Internet Authority G2 // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (2048 bit) // Modulus: // 00:9c:2a:04:77:5c:d8:50:91:3a:06:a3:82:e0:d8: // 50:48:bc:89:3f:f1:19:70:1a:88:46:7e:e0:8f:c5: // f1:89:ce:21:ee:5a:fe:61:0d:b7:32:44:89:a0:74: // 0b:53:4f:55:a4:ce:82:62:95:ee:eb:59:5f:c6:e1: // 05:80:12:c4:5e:94:3f:bc:5b:48:38:f4:53:f7:24: // e6:fb:91:e9:15:c4:cf:f4:53:0d:f4:4a:fc:9f:54: // de:7d:be:a0:6b:6f:87:c0:d0:50:1f:28:30:03:40: // da:08:73:51:6c:7f:ff:3a:3c:a7:37:06:8e:bd:4b: // 11:04:eb:7d:24:de:e6:f9:fc:31:71:fb:94:d5:60: // f3:2e:4a:af:42:d2:cb:ea:c4:6a:1a:b2:cc:53:dd: // 15:4b:8b:1f:c8:19:61:1f:cd:9d:a8:3e:63:2b:84: // 35:69:65:84:c8:19:c5:46:22:f8:53:95:be:e3:80: // 4a:10:c6:2a:ec:ba:97:20:11:c7:39:99:10:04:a0: // f0:61:7a:95:25:8c:4e:52:75:e2:b6:ed:08:ca:14: // fc:ce:22:6a:b3:4e:cf:46:03:97:97:03:7e:c0:b1: // de:7b:af:45:33:cf:ba:3e:71:b7:de:f4:25:25:c2: // 0d:35:89:9d:9d:fb:0e:11:79:89:1e:37:c5:af:8e: // 72:69 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Authority Key Identifier: // keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E // // X509v3 Subject Key Identifier: // 4A:DD:06:16:1B:BC:F6:68:B5:76:F5:81:B6:BB:62:1A:BA:5A:81:2F // X509v3 Key Usage: critical // Certificate Sign, CRL Sign // Authority Information Access: // OCSP - URI:http://g.symcd.com // // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:0 // X509v3 CRL Distribution Points: // // Full Name: // URI:http://g.symcb.com/crls/gtglobal.crl // // X509v3 Certificate Policies: // Policy: 1.3.6.1.4.1.11129.2.5.1 // Policy: 2.23.140.1.2.2 // // X509v3 Extended Key Usage: // TLS Web Server Authentication, TLS Web Client Authentication // Signature Algorithm: sha256WithRSAEncryption // ca:49:e5:ac:d7:64:64:77:5b:be:71:fa:cf:f4:1e:23:c7:9a: // 69:63:54:5f:eb:4c:d6:19:28:23:64:66:8e:1c:c7:87:80:64: // 5f:04:8b:26:af:98:df:0a:70:bc:bc:19:3d:ee:7b:33:a9:7f: // bd:f4:05:d4:70:bb:05:26:79:ea:9a:c7:98:b9:07:19:65:34: // cc:3c:e9:3f:c5:01:fa:6f:0c:7e:db:7a:70:5c:4c:fe:2d:00: // f0:ca:be:2d:8e:b4:a8:80:fb:01:13:88:cb:9c:3f:e5:bb:77: // ca:3a:67:36:f3:ce:d5:27:02:72:43:a0:bd:6e:02:f1:47:05: // 71:3e:01:59:e9:11:9e:1a:f3:84:0f:80:a6:a2:78:35:2f:b6: // c7:a2:7f:17:7c:e1:8b:56:ae:ee:67:88:51:27:30:60:a5:62: // 52:c3:37:d5:3b:ea:85:2a:01:38:87:a2:cf:70:ad:a4:7a:c9: // c4:e7:ca:c5:da:bc:23:32:f2:fe:18:c2:7b:e0:df:3b:2f:d4: // d0:10:e6:96:4c:fb:44:b7:21:64:0d:b9:00:94:30:12:26:87: // 58:98:39:05:38:0f:cc:82:48:0c:0a:47:66:ee:bf:b4:5f:c4: // ff:70:a8:e1:7f:8b:79:2b:b8:65:32:a3:b9:b7:31:e9:0a:f5: // f6:1f:32:dc const giag2Cert = `-----BEGIN CERTIFICATE----- MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS R2VvVHJ1c3QgR2xvYmFsIENBMB4XDTE3MDUyMjExMzIzN1oXDTE4MTIzMTIzNTk1 OVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMT HEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQCcKgR3XNhQkToGo4Lg2FBIvIk/8RlwGohGfuCPxfGJziHu Wv5hDbcyRImgdAtTT1WkzoJile7rWV/G4QWAEsRelD+8W0g49FP3JOb7kekVxM/0 Uw30SvyfVN59vqBrb4fA0FAfKDADQNoIc1Fsf/86PKc3Bo69SxEE630k3ub5/DFx +5TVYPMuSq9C0svqxGoassxT3RVLix/IGWEfzZ2oPmMrhDVpZYTIGcVGIvhTlb7j gEoQxirsupcgEcc5mRAEoPBhepUljE5SdeK27QjKFPzOImqzTs9GA5eXA37Asd57 r0Uzz7o+cbfe9CUlwg01iZ2d+w4ReYkeN8WvjnJpAgMBAAGjggERMIIBDTAfBgNV HSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1 dvWBtrtiGrpagS8wDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggr BgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAw NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9i YWwuY3JsMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwHQYDVR0l BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQDKSeWs 12Rkd1u+cfrP9B4jx5ppY1Rf60zWGSgjZGaOHMeHgGRfBIsmr5jfCnC8vBk97nsz qX+99AXUcLsFJnnqmseYuQcZZTTMPOk/xQH6bwx+23pwXEz+LQDwyr4tjrSogPsB E4jLnD/lu3fKOmc2887VJwJyQ6C9bgLxRwVxPgFZ6RGeGvOED4Cmong1L7bHon8X fOGLVq7uZ4hRJzBgpWJSwzfVO+qFKgE4h6LPcK2kesnE58rF2rwjMvL+GMJ74N87 L9TQEOaWTPtEtyFkDbkAlDASJodYmDkFOA/MgkgMCkdm7r+0X8T/cKjhf4t5K7hl MqO5tzHpCvX2HzLc -----END CERTIFICATE-----` func TestParseGIAG2CertificateList(t *testing.T) { certList, err := ParseCertificateList([]byte(giag2CRL)) if err != nil { t.Fatalf("error parsing: %s", err) } if got, want := len(certList.TBSCertList.RevokedCertificates), 4; got != want { t.Errorf("len(ParseCertificateList(crl).TBSCertList.RevokedCertificates) = %d; want %d", got, want) } when := time.Date(2017, 7, 7, 12, 0, 0, 0, time.UTC) if certList.ExpiredAt(when) { t.Errorf("certList.ExpiredAt(%v)=true; want false", when) } if got, want := certList.TBSCertList.CRLNumber, 1571; got != want { t.Errorf("ParseCertificateList(crl).TBSCertList.CRLNumber = %d; want %d", got, want) } pemBlock, _ := pem.Decode([]byte(giag2Cert)) giag2, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("error parsing GIAG2 cert: %v", err) } if err := giag2.CheckCertificateListSignature(certList); err != nil { t.Errorf("CheckCertificateListSignature(giag2CRL)=%v; want nil", err) } } google-certificate-transparency-go-2308f62/x509/root.go000066400000000000000000000007431462611535200226640ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import "sync" var ( once sync.Once systemRoots *CertPool systemRootsErr error ) func systemRootsPool() *CertPool { once.Do(initSystemRoots) return systemRoots } func initSystemRoots() { systemRoots, systemRootsErr = loadSystemRoots() if systemRootsErr != nil { systemRoots = nil } } google-certificate-transparency-go-2308f62/x509/root_aix.go000066400000000000000000000004421462611535200235210ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 // Possible certificate files; stop after finding one. var certFiles = []string{ "/var/ssl/certs/ca-bundle.crt", } google-certificate-transparency-go-2308f62/x509/root_bsd.go000066400000000000000000000010731462611535200235110ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build dragonfly || freebsd || netbsd || openbsd // +build dragonfly freebsd netbsd openbsd package x509 // Possible certificate files; stop after finding one. var certFiles = []string{ "/usr/local/etc/ssl/cert.pem", // FreeBSD "/etc/ssl/cert.pem", // OpenBSD "/usr/local/share/certs/ca-root-nss.crt", // DragonFly "/etc/openssl/certs/ca-certificates.crt", // NetBSD } google-certificate-transparency-go-2308f62/x509/root_cgo_darwin.go000066400000000000000000000270351462611535200250630ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build cgo && !arm && !arm64 && !ios // +build cgo,!arm,!arm64,!ios package x509 /* #cgo CFLAGS: -mmacosx-version-min=10.10 -D__MAC_OS_X_VERSION_MAX_ALLOWED=101300 #cgo LDFLAGS: -framework CoreFoundation -framework Security #include #include #include #include static Boolean isSSLPolicy(SecPolicyRef policyRef) { if (!policyRef) { return false; } CFDictionaryRef properties = SecPolicyCopyProperties(policyRef); if (properties == NULL) { return false; } Boolean isSSL = false; CFTypeRef value = NULL; if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) { isSSL = CFEqual(value, kSecPolicyAppleSSL); } CFRelease(properties); return isSSL; } // sslTrustSettingsResult obtains the final kSecTrustSettingsResult value // for a certificate in the user or admin domain, combining usage constraints // for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage and // kSecTrustSettingsAllowedError. // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting static SInt32 sslTrustSettingsResult(SecCertificateRef cert) { CFArrayRef trustSettings = NULL; OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); // According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings", // but the rules of the override are unclear. Let's assume admin trust settings are applicable // if and only if user trust settings fail to load or are NULL. if (err != errSecSuccess || trustSettings == NULL) { if (trustSettings != NULL) CFRelease(trustSettings); err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings); } // > no trust settings [...] means "this certificate must be verified to a known trusted certificate” // (Should this cause a fallback from user to admin domain? It's unclear.) if (err != errSecSuccess || trustSettings == NULL) { if (trustSettings != NULL) CFRelease(trustSettings); return kSecTrustSettingsResultUnspecified; } // > An empty trust settings array means "always trust this certificate” with an // > overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot. if (CFArrayGetCount(trustSettings) == 0) { CFRelease(trustSettings); return kSecTrustSettingsResultTrustRoot; } // kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"), // but the Go linker's internal linking mode can't handle CFSTR relocations. // Create our own dynamic string instead and release it below. CFStringRef _kSecTrustSettingsResult = CFStringCreateWithCString( NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8); CFStringRef _kSecTrustSettingsPolicy = CFStringCreateWithCString( NULL, "kSecTrustSettingsPolicy", kCFStringEncodingUTF8); CFStringRef _kSecTrustSettingsPolicyString = CFStringCreateWithCString( NULL, "kSecTrustSettingsPolicyString", kCFStringEncodingUTF8); CFIndex m; SInt32 result = 0; for (m = 0; m < CFArrayGetCount(trustSettings); m++) { CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m); // First, check if this trust setting is constrained to a non-SSL policy. SecPolicyRef policyRef; if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsPolicy, (const void**)&policyRef)) { if (!isSSLPolicy(policyRef)) { continue; } } if (CFDictionaryContainsKey(tSetting, _kSecTrustSettingsPolicyString)) { // Restricted to a hostname, not a root. continue; } CFNumberRef cfNum; if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) { CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result); } else { // > If this key is not present, a default value of // > kSecTrustSettingsResultTrustRoot is assumed. result = kSecTrustSettingsResultTrustRoot; } // If multiple dictionaries match, we are supposed to "OR" them, // the semantics of which are not clear. Since TrustRoot and TrustAsRoot // are mutually exclusive, Deny should probably override, and Invalid and // Unspecified be overridden, approximate this by stopping at the first // TrustRoot, TrustAsRoot or Deny. if (result == kSecTrustSettingsResultTrustRoot) { break; } else if (result == kSecTrustSettingsResultTrustAsRoot) { break; } else if (result == kSecTrustSettingsResultDeny) { break; } } // If trust settings are present, but none of them match the policy... // the docs don't tell us what to do. // // "Trust settings for a given use apply if any of the dictionaries in the // certificate’s trust settings array satisfies the specified use." suggests // that it's as if there were no trust settings at all, so we should probably // fallback to the admin trust settings. TODO. if (result == 0) { result = kSecTrustSettingsResultUnspecified; } CFRelease(_kSecTrustSettingsPolicy); CFRelease(_kSecTrustSettingsPolicyString); CFRelease(_kSecTrustSettingsResult); CFRelease(trustSettings); return result; } // isRootCertificate reports whether Subject and Issuer match. static Boolean isRootCertificate(SecCertificateRef cert, CFErrorRef *errRef) { CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, errRef); if (*errRef != NULL) { return false; } CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, errRef); if (*errRef != NULL) { CFRelease(subjectName); return false; } Boolean equal = CFEqual(subjectName, issuerName); CFRelease(subjectName); CFRelease(issuerName); return equal; } // CopyPEMRootsCTX509 fetches the system's list of trusted X.509 root certificates // for the kSecTrustSettingsPolicy SSL. // // On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root // certificates of the system. On failure, the function returns -1. // Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots. // // Note: The CFDataRef returned in pemRoots and untrustedPemRoots must // be released (using CFRelease) after we've consumed its content. static int CopyPEMRootsCTX509(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) { int i; if (debugDarwinRoots) { fprintf(stderr, "crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified); } // Get certificates from all domains, not just System, this lets // the user add CAs to their "login" keychain, and Admins to add // to the "System" keychain SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainUser }; int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain); if (pemRoots == NULL || untrustedPemRoots == NULL) { return -1; } CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0); for (i = 0; i < numDomains; i++) { int j; CFArrayRef certs = NULL; OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs); if (err != noErr) { continue; } CFIndex numCerts = CFArrayGetCount(certs); for (j = 0; j < numCerts; j++) { SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j); if (cert == NULL) { continue; } SInt32 result; if (domains[i] == kSecTrustSettingsDomainSystem) { // Certs found in the system domain are always trusted. If the user // configures "Never Trust" on such a cert, it will also be found in the // admin or user domain, causing it to be added to untrustedPemRoots. The // Go code will then clean this up. result = kSecTrustSettingsResultTrustRoot; } else { result = sslTrustSettingsResult(cert); if (debugDarwinRoots) { CFErrorRef errRef = NULL; CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef); if (errRef != NULL) { fprintf(stderr, "crypto/x509: SecCertificateCopyShortDescription failed\n"); CFRelease(errRef); continue; } CFIndex length = CFStringGetLength(summary); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; char *buffer = malloc(maxSize); if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) { fprintf(stderr, "crypto/x509: %s returned %d\n", buffer, (int)result); } free(buffer); CFRelease(summary); } } CFMutableDataRef appendTo; // > Note the distinction between the results kSecTrustSettingsResultTrustRoot // > and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to // > root (self-signed) certificates; the latter can only be applied to // > non-root certificates. if (result == kSecTrustSettingsResultTrustRoot) { CFErrorRef errRef = NULL; if (!isRootCertificate(cert, &errRef) || errRef != NULL) { if (errRef != NULL) CFRelease(errRef); continue; } appendTo = combinedData; } else if (result == kSecTrustSettingsResultTrustAsRoot) { CFErrorRef errRef = NULL; if (isRootCertificate(cert, &errRef) || errRef != NULL) { if (errRef != NULL) CFRelease(errRef); continue; } appendTo = combinedData; } else if (result == kSecTrustSettingsResultDeny) { appendTo = combinedUntrustedData; } else if (result == kSecTrustSettingsResultUnspecified) { // Certificates with unspecified trust should probably be added to a pool of // intermediates for chain building, or checked for transitive trust and // added to the root pool (which is an imprecise approximation because it // cuts chains short) but we don't support either at the moment. TODO. continue; } else { continue; } CFDataRef data = NULL; err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); if (err != noErr) { continue; } if (data != NULL) { CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data)); CFRelease(data); } } CFRelease(certs); } *pemRoots = combinedData; *untrustedPemRoots = combinedUntrustedData; return 0; } */ import "C" import ( "errors" "unsafe" ) func loadSystemRoots() (*CertPool, error) { var data, untrustedData C.CFDataRef err := C.CopyPEMRootsCTX509(&data, &untrustedData, C.bool(debugDarwinRoots)) if err == -1 { return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo") } defer C.CFRelease(C.CFTypeRef(data)) defer C.CFRelease(C.CFTypeRef(untrustedData)) buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) roots := NewCertPool() roots.AppendCertsFromPEM(buf) if C.CFDataGetLength(untrustedData) == 0 { return roots, nil } buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData))) untrustedRoots := NewCertPool() untrustedRoots.AppendCertsFromPEM(buf) trustedRoots := NewCertPool() for _, c := range roots.certs { if !untrustedRoots.contains(c) { trustedRoots.AddCert(c) } } return trustedRoots, nil } google-certificate-transparency-go-2308f62/x509/root_darwin.go000066400000000000000000000203771462611535200242350ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go package x509 import ( "bufio" "bytes" "crypto/sha1" "encoding/pem" "fmt" "io" "os" "os/exec" "os/user" "path/filepath" "strings" "sync" ) var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1") func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { return nil, nil } // This code is only used when compiling without cgo. // It is here, instead of root_nocgo_darwin.go, so that tests can check it // even if the tests are run with cgo enabled. // The linker will not include these unused functions in binaries built with cgo enabled. // execSecurityRoots finds the macOS list of trusted root certificates // using only command-line tools. This is our fallback path when cgo isn't available. // // The strategy is as follows: // // 1. Run "security trust-settings-export" and "security // trust-settings-export -d" to discover the set of certs with some // user-tweaked trust policy. We're too lazy to parse the XML // (Issue 26830) to understand what the trust // policy actually is. We just learn that there is _some_ policy. // // 2. Run "security find-certificate" to dump the list of system root // CAs in PEM format. // // 3. For each dumped cert, conditionally verify it with "security // verify-cert" if that cert was in the set discovered in Step 1. // Without the Step 1 optimization, running "security verify-cert" // 150-200 times takes 3.5 seconds. With the optimization, the // whole process takes about 180 milliseconds with 1 untrusted root // CA. (Compared to 110ms in the cgo path) func execSecurityRoots() (*CertPool, error) { hasPolicy, err := getCertsWithTrustPolicy() if err != nil { return nil, err } if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: %d certs have a trust policy\n", len(hasPolicy)) } keychains := []string{"/Library/Keychains/System.keychain"} // Note that this results in trusting roots from $HOME/... (the environment // variable), which might not be expected. u, err := user.Current() if err != nil { if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: can't get user home directory: %v\n", err) } } else { keychains = append(keychains, filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"), // Fresh installs of Sierra use a slightly different path for the login keychain filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"), ) } type rootCandidate struct { c *Certificate system bool } var ( mu sync.Mutex roots = NewCertPool() numVerified int // number of execs of 'security verify-cert', for debug stats wg sync.WaitGroup verifyCh = make(chan rootCandidate) ) // Using 4 goroutines to pipe into verify-cert seems to be // about the best we can do. The verify-cert binary seems to // just RPC to another server with coarse locking anyway, so // running 16 at a time for instance doesn't help at all. Due // to the "if hasPolicy" check below, though, we will rarely // (or never) call verify-cert on stock macOS systems, though. // The hope is that we only call verify-cert when the user has // tweaked their trust policy. These 4 goroutines are only // defensive in the pathological case of many trust edits. for i := 0; i < 4; i++ { wg.Add(1) go func() { defer wg.Done() for cert := range verifyCh { sha1CapHex := fmt.Sprintf("%X", sha1.Sum(cert.c.Raw)) var valid bool verifyChecks := 0 if hasPolicy[sha1CapHex] { verifyChecks++ valid = verifyCertWithSystem(cert.c) } else { // Certificates not in SystemRootCertificates without user // or admin trust settings are not trusted. valid = cert.system } mu.Lock() numVerified += verifyChecks if valid { roots.AddCert(cert.c) } mu.Unlock() } }() } err = forEachCertInKeychains(keychains, func(cert *Certificate) { verifyCh <- rootCandidate{c: cert, system: false} }) if err != nil { close(verifyCh) return nil, err } err = forEachCertInKeychains([]string{ "/System/Library/Keychains/SystemRootCertificates.keychain", }, func(cert *Certificate) { verifyCh <- rootCandidate{c: cert, system: true} }) if err != nil { close(verifyCh) return nil, err } close(verifyCh) wg.Wait() if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: ran security verify-cert %d times\n", numVerified) } return roots, nil } func forEachCertInKeychains(paths []string, f func(*Certificate)) error { args := append([]string{"find-certificate", "-a", "-p"}, paths...) cmd := exec.Command("/usr/bin/security", args...) data, err := cmd.Output() if err != nil { return err } for len(data) > 0 { var block *pem.Block block, data = pem.Decode(data) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := ParseCertificate(block.Bytes) if err != nil { continue } f(cert) } return nil } func verifyCertWithSystem(cert *Certificate) bool { data := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }) f, err := os.CreateTemp("", "cert") if err != nil { fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err) return false } defer os.Remove(f.Name()) if _, err := f.Write(data); err != nil { fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err) return false } if err := f.Close(); err != nil { fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err) return false } cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L") var stderr bytes.Buffer if debugDarwinRoots { cmd.Stderr = &stderr } if err := cmd.Run(); err != nil { if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes())) } return false } if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert approved %s\n", cert.Subject) } return true } // getCertsWithTrustPolicy returns the set of certs that have a // possibly-altered trust policy. The keys of the map are capitalized // sha1 hex of the raw cert. // They are the certs that should be checked against `security // verify-cert` to see whether the user altered the default trust // settings. This code is only used for cgo-disabled builds. func getCertsWithTrustPolicy() (map[string]bool, error) { set := map[string]bool{} td, err := os.MkdirTemp("", "x509trustpolicy") if err != nil { return nil, err } defer os.RemoveAll(td) run := func(file string, args ...string) error { file = filepath.Join(td, file) args = append(args, file) cmd := exec.Command("/usr/bin/security", args...) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { // If there are no trust settings, the // `security trust-settings-export` command // fails with: // exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found. // Rather than match on English substrings that are probably // localized on macOS, just interpret any failure to mean that // there are no trust settings. if debugDarwinRoots { fmt.Fprintf(os.Stderr, "crypto/x509: exec %q: %v, %s\n", cmd.Args, err, stderr.Bytes()) } return nil } f, err := os.Open(file) if err != nil { return err } defer f.Close() // Gather all the runs of 40 capitalized hex characters. br := bufio.NewReader(f) var hexBuf bytes.Buffer for { b, err := br.ReadByte() isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9') if isHex { hexBuf.WriteByte(b) } else { if hexBuf.Len() == 40 { set[hexBuf.String()] = true } hexBuf.Reset() } if err == io.EOF { break } if err != nil { return err } } return nil } if err := run("user", "trust-settings-export"); err != nil { return nil, fmt.Errorf("dump-trust-settings (user): %v", err) } if err := run("admin", "trust-settings-export", "-d"); err != nil { return nil, fmt.Errorf("dump-trust-settings (admin): %v", err) } return set, nil } google-certificate-transparency-go-2308f62/x509/root_darwin_arm_gen.go000066400000000000000000000111341462611535200257140ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore // +build ignore // Generates root_darwin_armx.go. // // As of iOS 8, there is no API for querying the system trusted X.509 root // certificates. We could use SecTrustEvaluate to verify that a trust chain // exists for a certificate, but the x509 API requires returning the entire // chain. // // Apple publishes the list of trusted root certificates for iOS on // support.apple.com. So we parse the list and extract the certificates from // an OS X machine and embed them into the x509 package. package main import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/pem" "flag" "fmt" "go/format" "io" "log" "net/http" "os" "os/exec" "regexp" "strings" "github.com/google/certificate-transparency-go/x509" ) var output = flag.String("output", "root_darwin_armx.go", "file name to write") func main() { certs, err := selectCerts() if err != nil { log.Fatal(err) } buf := new(bytes.Buffer) fmt.Fprintf(buf, "// Code generated by root_darwin_arm_gen --output %s; DO NOT EDIT.\n", *output) fmt.Fprintf(buf, "%s", header) fmt.Fprintf(buf, "const systemRootsPEM = `\n") for _, cert := range certs { b := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, } if err := pem.Encode(buf, b); err != nil { log.Fatal(err) } } fmt.Fprintf(buf, "`") source, err := format.Source(buf.Bytes()) if err != nil { log.Fatal("source format error:", err) } if err := os.WriteFile(*output, source, 0644); err != nil { log.Fatal(err) } } func selectCerts() ([]*x509.Certificate, error) { ids, err := fetchCertIDs() if err != nil { return nil, err } scerts, err := sysCerts() if err != nil { return nil, err } var certs []*x509.Certificate for _, id := range ids { if c, ok := scerts[id.fingerprint]; ok { certs = append(certs, c) } else { fmt.Printf("WARNING: cannot find certificate: %s (fingerprint: %s)\n", id.name, id.fingerprint) } } return certs, nil } func sysCerts() (certs map[string]*x509.Certificate, err error) { cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain") data, err := cmd.Output() if err != nil { return nil, err } certs = make(map[string]*x509.Certificate) for len(data) > 0 { var block *pem.Block block, data = pem.Decode(data) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { continue } fingerprint := sha256.Sum256(cert.Raw) certs[hex.EncodeToString(fingerprint[:])] = cert } return certs, nil } type certID struct { name string fingerprint string } // fetchCertIDs fetches IDs of iOS X509 certificates from apple.com. func fetchCertIDs() ([]certID, error) { // Download the iOS 11 support page. The index for all iOS versions is here: // https://support.apple.com/en-us/HT204132 resp, err := http.Get("https://support.apple.com/en-us/HT208125") if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } text := string(body) text = text[strings.Index(text, "
    ")] var ids []certID cols := make(map[string]int) for i, rowmatch := range regexp.MustCompile("(?s)(.*?)").FindAllStringSubmatch(text, -1) { row := rowmatch[1] if i == 0 { // Parse table header row to extract column names for i, match := range regexp.MustCompile("(?s)(.*?)").FindAllStringSubmatch(row, -1) { cols[match[1]] = i } continue } values := regexp.MustCompile("(?s)(.*?)").FindAllStringSubmatch(row, -1) name := values[cols["Certificate name"]][1] fingerprint := values[cols["Fingerprint (SHA-256)"]][1] fingerprint = strings.ReplaceAll(fingerprint, "
    ", "") fingerprint = strings.ReplaceAll(fingerprint, "\n", "") fingerprint = strings.ReplaceAll(fingerprint, " ", "") fingerprint = strings.ToLower(fingerprint) ids = append(ids, certID{ name: name, fingerprint: fingerprint, }) } return ids, nil } const header = ` // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build cgo // +build darwin // +build arm arm64 ios package x509 func loadSystemRoots() (*CertPool, error) { p := NewCertPool() p.AppendCertsFromPEM([]byte(systemRootsPEM)) return p, nil } ` google-certificate-transparency-go-2308f62/x509/root_darwin_armx.go000066400000000000000000010002351462611535200252540ustar00rootroot00000000000000// Code generated by root_darwin_arm_gen --output root_darwin_armx.go; DO NOT EDIT. // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build cgo && darwin && (arm || arm64 || ios) // +build cgo // +build darwin // +build arm arm64 ios package x509 func loadSystemRoots() (*CertPool, error) { p := NewCertPool() p.AppendCertsFromPEM([]byte(systemRootsPEM)) return p, nil } const systemRootsPEM = ` -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe 3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX 4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ 51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC +Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X 7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz 43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFVTCCBD2gAwIBAgIEO/OB0DANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJj aDEOMAwGA1UEChMFYWRtaW4xETAPBgNVBAsTCFNlcnZpY2VzMSIwIAYDVQQLExlD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0aWVzMRYwFAYDVQQDEw1BZG1pbi1Sb290LUNB MB4XDTAxMTExNTA4NTEwN1oXDTIxMTExMDA3NTEwN1owbDELMAkGA1UEBhMCY2gx DjAMBgNVBAoTBWFkbWluMREwDwYDVQQLEwhTZXJ2aWNlczEiMCAGA1UECxMZQ2Vy dGlmaWNhdGlvbiBBdXRob3JpdGllczEWMBQGA1UEAxMNQWRtaW4tUm9vdC1DQTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMvgr0QUIv5qF0nyXZ3PXAJi C4C5Wr+oVTN7oxIkXkxvO0GJToM9n7OVJjSmzBL0zJ2HXj0MDRcvhSY+KiZZc6Go vDvr5Ua481l7ILFeQAFtumeza+vvxeL5Nd0Maga2miiacLNAKXbAcUYRa0Ov5VZB ++YcOYNNt/aisWbJqA2y8He+NsEgJzK5zNdayvYXQTZN+7tVgWOck16Da3+4FXdy fH1NCWtZlebtMKtERtkVAaVbiWW24CjZKAiVfggjsiLo3yVMPGj3budLx5D9hEEm vlyDOtcjebca+AcZglppWMX/iHIrx7740y0zd6cWEqiLIcZCrnpkr/KzwO135GkC AwEAAaOCAf0wggH5MA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIASBkTCBjjCBiwYI YIV0AREDAQAwfzArBggrBgEFBQcCAjAfGh1UaGlzIGlzIHRoZSBBZG1pbi1Sb290 LUNBIENQUzBQBggrBgEFBQcCARZEaHR0cDovL3d3dy5pbmZvcm1hdGlrLmFkbWlu LmNoL1BLSS9saW5rcy9DUFNfMl8xNl83NTZfMV8xN18zXzFfMC5wZGYwfwYDVR0f BHgwdjB0oHKgcKRuMGwxFjAUBgNVBAMTDUFkbWluLVJvb3QtQ0ExIjAgBgNVBAsT GUNlcnRpZmljYXRpb24gQXV0aG9yaXRpZXMxETAPBgNVBAsTCFNlcnZpY2VzMQ4w DAYDVQQKEwVhZG1pbjELMAkGA1UEBhMCY2gwHQYDVR0OBBYEFIKf+iNzIPGXi7JM Tb5CxX9mzWToMIGZBgNVHSMEgZEwgY6AFIKf+iNzIPGXi7JMTb5CxX9mzWTooXCk bjBsMQswCQYDVQQGEwJjaDEOMAwGA1UEChMFYWRtaW4xETAPBgNVBAsTCFNlcnZp Y2VzMSIwIAYDVQQLExlDZXJ0aWZpY2F0aW9uIEF1dGhvcml0aWVzMRYwFAYDVQQD Ew1BZG1pbi1Sb290LUNBggQ784HQMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0B AQUFAAOCAQEAeE96XCYRpy6umkPKXDWCRn7INo96ZrWpMggcDORuofHIwdTkgOeM vWOxDN/yuT7CC3FAaUajbPRbDw0hRMcqKz0aC8CgwcyIyhw/rFK29mfNTG3EviP9 QSsEbnelFnjpm1wjz4EaBiFjatwpUbI6+Zv3XbEt9QQXBn+c6DeFLe4xvC4B+MTr a440xTk59pSYux8OHhEvqIwHCkiijGqZhTS3KmGFeBopaR+dJVBRBMoXwzk4B3Hn 0Zib1dEYFZa84vPJZyvxCbLOnPRDJgH6V2uQqbG+6DXVaf/wORVOvF/wzzv0viM/ RWbEtJZdvo8N3sdtCULzifnxP/V0T9+4ZQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp 6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D 0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ +jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S 5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B 8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc 0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e KeC2uAloGRwYQw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIIGDCCBgCgAwIBAgIGAT8vMXfmMA0GCSqGSIb3DQEBCwUAMIIBCjELMAkGA1UE BhMCRVMxEjAQBgNVBAgMCUJhcmNlbG9uYTFYMFYGA1UEBwxPQmFyY2Vsb25hIChz ZWUgY3VycmVudCBhZGRyZXNzIGF0IGh0dHA6Ly93d3cuYW5mLmVzL2VzL2FkZHJl c3MtZGlyZWNjaW9uLmh0bWwgKTEnMCUGA1UECgweQU5GIEF1dG9yaWRhZCBkZSBD ZXJ0aWZpY2FjaW9uMRcwFQYDVQQLDA5BTkYgQ2xhc2UgMSBDQTEaMBgGCSqGSIb3 DQEJARYLaW5mb0BhbmYuZXMxEjAQBgNVBAUTCUc2MzI4NzUxMDEbMBkGA1UEAwwS QU5GIEdsb2JhbCBSb290IENBMB4XDTEzMDYxMDE3NDUzOFoXDTMzMDYwNTE3NDUz OFowggEKMQswCQYDVQQGEwJFUzESMBAGA1UECAwJQmFyY2Vsb25hMVgwVgYDVQQH DE9CYXJjZWxvbmEgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgaHR0cDovL3d3dy5h bmYuZXMvZXMvYWRkcmVzcy1kaXJlY2Npb24uaHRtbCApMScwJQYDVQQKDB5BTkYg QXV0b3JpZGFkIGRlIENlcnRpZmljYWNpb24xFzAVBgNVBAsMDkFORiBDbGFzZSAx IENBMRowGAYJKoZIhvcNAQkBFgtpbmZvQGFuZi5lczESMBAGA1UEBRMJRzYzMjg3 NTEwMRswGQYDVQQDDBJBTkYgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDHPi9xy4wynbcUbWjorVUgQKeUAVh937J7P37XmsfH ZLOBZKIIlhhCtRwnDlg7x+BUvtJOTkIbEGMujDygUQ2s3HDYr5I41hTyM2Pl0cq2 EuSGEbPIHb3dEX8NAguFexM0jqNjrreN3hM2/+TOkAxSdDJP2aMurlySC5zwl47K ZLHtcVrkZnkDa0o5iN24hJT4vBDT4t2q9khQ+qb1D8KgCOb02r1PxWXu3vfd6Ha2 mkdB97iGuEh5gO2n4yOmFS5goFlVA2UdPbbhJsb8oKVKDd+YdCKGQDCkQyG4AjmC YiNm3UPG/qtftTH5cWri67DlLtm6fyUFOMmO6NSh0RtR745pL8GyWJUanyq/Q4bF HQB21E+WtTsCaqjGaoFcrBunMypmCd+jUZXl27TYENRFbrwNdAh7m2UztcIyb+Sg VJFyfvVsBQNvnp7GPimVxXZNc4VpxEXObRuPWQN1oZN/90PcZVqTia/SHzEyTryL ckhiLG3jZiaFZ7pTZ5I9wti9Pn+4kOHvE3Y/4nEnUo4mTxPX9pOlinF+VCiybtV2 u1KSlc+YaIM7VmuyndDZCJRXm3v0/qTE7t5A5fArZl9lvibigMbWB8fpD+c1GpGH Eo8NRY0lkaM+DkIqQoaziIsz3IKJrfdKaq9bQMSlIfameKBZ8fNYTBZrH9KZAIhz YwIDAQABo4IBfjCCAXowHQYDVR0OBBYEFIf6nt9SdnXsSUogb1twlo+d77sXMB8G A1UdIwQYMBaAFIf6nt9SdnXsSUogb1twlo+d77sXMA8GA1UdEwEB/wQFMAMBAf8w DgYDVR0PAQH/BAQDAgEGMIIBFQYDVR0RBIIBDDCCAQiCEWh0dHA6Ly93d3cuYW5m LmVzgQtpbmZvQGFuZi5lc6SB5TCB4jE0MDIGA1UECQwrR3JhbiBWaWEgZGUgbGVz IENvcnRzIENhdGFsYW5lcy4gOTk2LiAwODAxODESMBAGA1UEBwwJQmFyY2Vsb25h MScwJQYDVQQKDB5BTkYgQXV0b3JpZGFkIGRlIENlcnRpZmljYWNpb24xEjAQBgNV BAUTCUc2MzI4NzUxMDFZMFcGA1UECwxQSW5zY3JpdGEgZW4gZWwgTWluaXN0ZXJp byBkZWwgSW50ZXJpb3IgZGUgRXNwYcOxYSBjb24gZWwgbnVtZXJvIG5hY2lvbmFs IDE3MS40NDMwDQYJKoZIhvcNAQELBQADggIBAIgR9tFTZ9BCYg+HViMxOfF0MHN2 Pe/eC128ARdS+GH8A4thtbqiH/SOYbWofO/0zssHhNKa5iQEj45lCAb8BANpWJMD nWkPr6jq2+50a6d0MMgSS2l1rvjSF+3nIrEuicshHXSTi3q/vBLKr7uGKMVFaM68 XAropIwk6ndlA0JseARSPsbetv7ALESMIZAxlHV1TcctYHd0bB3c/Jz+PLszJQqs Cg/kBPo2D111OXZkIY8W/fJuG9veR783khAK2gUnC0zLLCNsYzEbdGt8zUmBsAsM cGxqGm6B6vDXd65OxWqw13xdq/24+5R8Ng1PF9tvfjZkUFBF30CxjWur7P90WiKI G7IGfr6BE1NgXlhEQQu4F+HizB1ypEPzGWltecXQ4yOzO+H0WfFTjLTYX6VSveyW DQV18ixF8M4tHP/SwNE+yyv2b2JJ3/3RpxjtFlLk+opJ574x0gD/dMJuWTH0JqVY 3PbRfE1jIxFpk164Qz/Xp7H7w7f6xh+tQCkBs3PUYmnGIZcPwq44Q6JHlCNsKx4K hxfggTvRCk4w79cUID45c2qDsRCqTPoOo/cbOpcfVhbH9LdMORpmuLwNogRZEUSE fWpqR9q+0kcQf4zGSWIURIyDrogdpDgoHDxktqgMgc+qA4ZE2WQl1D8hmev53A46 lUSrWUiWfDXtK3ux -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFkjCCA3qgAwIBAgIIAeDltYNno+AwDQYJKoZIhvcNAQEMBQAwZzEbMBkGA1UE AwwSQXBwbGUgUm9vdCBDQSAtIEcyMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0 aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMw HhcNMTQwNDMwMTgxMDA5WhcNMzkwNDMwMTgxMDA5WjBnMRswGQYDVQQDDBJBcHBs ZSBSb290IENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgREkhI2imKScUcx+xuM23+TfvgHN6s XuI2pyT5f1BrTM65MFQn5bPW7SXmMLYFN14UIhHF6Kob0vuy0gmVOKTvKkmMXT5x ZgM4+xb1hYjkWpIMBDLyyED7Ul+f9sDx47pFoFDVEovy3d6RhiPw9bZyLgHaC/Yu OQhfGaFjQQscp5TBhsRTL3b2CtcM0YM/GlMZ81fVJ3/8E7j4ko380yhDPLVoACVd J2LT3VXdRCCQgzWTxb+4Gftr49wIQuavbfqeQMpOhYV4SbHXw8EwOTKrfl+q04tv ny0aIWhwZ7Oj8ZhBbZF8+NfbqOdfIRqMM78xdLe40fTgIvS/cjTf94FNcX1RoeKz 8NMoFnNvzcytN31O661A4T+B/fc9Cj6i8b0xlilZ3MIZgIxbdMYs0xBTJh0UT8TU gWY8h2czJxQI6bR3hDRSj4n4aJgXv8O7qhOTH11UL6jHfPsNFL4VPSQ08prcdUFm IrQB1guvkJ4M6mL4m1k8COKWNORj3rw31OsMiANDC1CvoDTdUE0V+1ok2Az6DGOe HwOx4e7hqkP0ZmUoNwIx7wHHHtHMn23KVDpA287PT0aLSmWaasZobNfMmRtHsHLD d4/E92GcdB/O/WuhwpyUgquUoue9G7q5cDmVF8Up8zlYNPXEpMZ7YLlmQ1A/bmH8 DvmGqmAMQ0uVAgMBAAGjQjBAMB0GA1UdDgQWBBTEmRNsGAPCe8CjoA1/coB6HHcm jTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwF AAOCAgEAUabz4vS4PZO/Lc4Pu1vhVRROTtHlznldgX/+tvCHM/jvlOV+3Gp5pxy+ 8JS3ptEwnMgNCnWefZKVfhidfsJxaXwU6s+DDuQUQp50DhDNqxq6EWGBeNjxtUVA eKuowM77fWM3aPbn+6/Gw0vsHzYmE1SGlHKy6gLti23kDKaQwFd1z4xCfVzmMX3z ybKSaUYOiPjjLUKyOKimGY3xn83uamW8GrAlvacp/fQ+onVJv57byfenHmOZ4VxG /5IFjPoeIPmGlFYl5bRXOJ3riGQUIUkhOb9iZqmxospvPyFgxYnURTbImHy99v6Z SYA7LNKmp4gDBDEZt7Y6YUX6yfIjyGNzv1aJMbDZfGKnexWoiIqrOEDCzBL/FePw N983csvMmOa/orz6JopxVtfnJBtIRD6e/J/JzBrsQzwBvDR4yGn1xuZW7AYJNpDr FEobXsmII9oDMJELuDY++ee1KG++P+w8j2Ud5cAeh6Squpj9kuNsJnfdBrRkBof0 Tta6SqoWqPQFZ2aWuuJVecMsXUmPgEkrihLHdoBR37q9ZV0+N0djMenl9MU/S60E inpxLK8JQzcPqOMyT/RFtm2XNuyE9QoB6he7hY1Ck3DDUOUUi78/w0EP3SIEIwiK um1xRKtzCTrJ+VKACd+66eYWyi4uTLLT3OUEVLLUNIAytbwPF+E= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49 AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517 IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4 at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM 6BgD56KyKA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0 MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+ +FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1 XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3 R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93 d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0 YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7 R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX UKqK1drk/NAJBzewdXUh -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFujCCBKKgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhjELMAkGA1UEBhMCVVMx HTAbBgNVBAoTFEFwcGxlIENvbXB1dGVyLCBJbmMuMS0wKwYDVQQLEyRBcHBsZSBD b21wdXRlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxKTAnBgNVBAMTIEFwcGxlIFJv b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTA1MDIxMDAwMTgxNFoXDTI1MDIx MDAwMTgxNFowgYYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBcHBsZSBDb21wdXRl ciwgSW5jLjEtMCsGA1UECxMkQXBwbGUgQ29tcHV0ZXIgQ2VydGlmaWNhdGUgQXV0 aG9yaXR5MSkwJwYDVQQDEyBBcHBsZSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOSRqQkfkdseR1DrBe1e eYQt6zaiV0xV7IsZid75S2z1B6siMALoGD74UAnTf0GomPnRymacJGsR0KO75Bsq wx+VnnoMpEeLW9QWNzPLxA9NzhRp0ckZcvVdDtV/X5vyJQO6VY9NXQ3xZDUjFUsV WR2zlPf2nJ7PULrBWFBnjwi0IPfLrCwgb3C2PwEwjLdDzw+dPfMrSSgayP7OtbkO 2V4c1ss9tTqt9A8OAJILsSEWLnTVPA3bYharo3GSR1NVwa8vQbP4++NwzeajTEV+ H0xrUJZBicR0YgsQg0GHM4qBsTBY7FoEMoxos48d3mVz/2deZbxJ2HafMxRloXeU yS0CAwEAAaOCAi8wggIrMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ MB0GA1UdDgQWBBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAfBgNVHSMEGDAWgBQr0GlH lHYJ/vRrjS5ApvdHTX8IXjCCASkGA1UdIASCASAwggEcMIIBGAYJKoZIhvdjZAUB MIIBCTBBBggrBgEFBQcCARY1aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmlj YXRlYXV0aG9yaXR5L3Rlcm1zLmh0bWwwgcMGCCsGAQUFBwICMIG2GoGzUmVsaWFu Y2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2Nl cHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5k IGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRp ZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wRAYDVR0fBD0wOzA5oDegNYYz aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L3Jvb3Qu Y3JsMFUGCCsGAQUFBwEBBEkwRzBFBggrBgEFBQcwAoY5aHR0cHM6Ly93d3cuYXBw bGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L2Nhc2lnbmVycy5odG1sMA0GCSqG SIb3DQEBBQUAA4IBAQCd2i0oWC99dgS5BNM+zrdmY06PL9T+S61yvaM5xlJNBZhS 9YlRASR5vhoy9+VEi0tEBzmC1lrKtCBe2a4VXR2MHTK/ODFiSF3H4ZCx+CRA+F9Y m1FdV53B5f88zHIhbsTp6aF31ywXJsM/65roCwO66bNKcuszCVut5mIxauivL9Wv Hld2j383LS4CXN1jyfJxuCZA3xWNdUQ/eb3mHZnhQyw+rW++uaT+DjUZUWOxw961 kj5ReAFziqQjyqSI8R5cH0EWLX6VCqrpiUGYGxrdyyC/R14MJsVVNU3GMIuZZxTH CR+6R8faAQmHJEKVvRNgGQrv6n8Obs3BREM6StXj -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID9zCCAt+gAwIBAgILMTI1MzcyODI4MjgwDQYJKoZIhvcNAQELBQAwWDELMAkG A1UEBhMCSlAxHDAaBgNVBAoTE0phcGFuZXNlIEdvdmVybm1lbnQxDTALBgNVBAsT BEdQS0kxHDAaBgNVBAMTE0FwcGxpY2F0aW9uQ0EyIFJvb3QwHhcNMTMwMzEyMTUw MDAwWhcNMzMwMzEyMTUwMDAwWjBYMQswCQYDVQQGEwJKUDEcMBoGA1UEChMTSmFw YW5lc2UgR292ZXJubWVudDENMAsGA1UECxMER1BLSTEcMBoGA1UEAxMTQXBwbGlj YXRpb25DQTIgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKaq rSVl1gAR1uh6dqr05rRL88zDUrSNrKZPtZJxb0a11a2LEiIXJc5F6BR6hZrkIxCo +rFnUOVtR+BqiRPjrq418fRCxQX3TZd+PCj8sCaRHoweOBqW3FhEl2LjMsjRFUFN dZh4vqtoqV7tR76kuo6hApfek3SZbWe0BSXulMjtqqS6MmxCEeu+yxcGkOGThchk KM4fR8fAXWDudjbcMztR63vPctgPeKgZggiQPhqYjY60zxU2pm7dt+JNQCBT2XYq 0HisifBPizJtROouurCp64ndt295D6uBbrjmiykLWa+2SQ1RLKn9nShjZrhwlXOa 2Po7M7xCQhsyrLEy+z0CAwEAAaOBwTCBvjAdBgNVHQ4EFgQUVqesqgIdsqw9kA6g by5Bxnbne9owDgYDVR0PAQH/BAQDAgEGMHwGA1UdEQR1MHOkcTBvMQswCQYDVQQG EwJKUDEYMBYGA1UECgwP5pel5pys5Zu95pS/5bqcMRswGQYDVQQLDBLmlL/lupzo qo3oqLzln7rnm6QxKTAnBgNVBAMMIOOCouODl+ODquOCseODvOOCt+ODp+ODs0NB MiBSb290MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH+aCXWs B9FydC53VzDCBJzUgKaD56WgG5/+q/OAvdVKo6GPtkxgEefK4WCB10jBIFmlYTKL nZ6X02aD2mUuWD7b5S+lzYxzplG+WCigeVxpL0PfY7KJR8q73rk0EWOgDiUX5Yf0 HbCwpc9BqHTG6FPVQvSCLVMJEWgmcZR1E02qdog8dLHW40xPYsNJTE5t8XB+w3+m Bcx4m+mB26jIx1ye/JKSLaaX8ji1bnOVDMA/zqaUMLX6BbfeniCq/BNkyYq6ZO/i Y+TYmK5rtT6mVbgzPixy+ywRAPtbFi+E0hOe+gXFwctyTiLdhMpLvNIthhoEdlkf SUJiOxMfFui61/0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ 4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF 6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF 661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS 3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF 3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIJmzCCB4OgAwIBAgIBATANBgkqhkiG9w0BAQwFADCCAR4xPjA8BgNVBAMTNUF1 dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0 aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyMjE4MDgy MVoXDTMwMTIxNzIzNTk1OVowggEeMT4wPAYDVQQDEzVBdXRvcmlkYWQgZGUgQ2Vy dGlmaWNhY2lvbiBSYWl6IGRlbCBFc3RhZG8gVmVuZXpvbGFubzELMAkGA1UEBhMC VkUxEDAOBgNVBAcTB0NhcmFjYXMxGTAXBgNVBAgTEERpc3RyaXRvIENhcGl0YWwx NjA0BgNVBAoTLVNpc3RlbWEgTmFjaW9uYWwgZGUgQ2VydGlmaWNhY2lvbiBFbGVj dHJvbmljYTFDMEEGA1UECxM6U3VwZXJpbnRlbmRlbmNpYSBkZSBTZXJ2aWNpb3Mg ZGUgQ2VydGlmaWNhY2lvbiBFbGVjdHJvbmljYTElMCMGCSqGSIb3DQEJARYWYWNy YWl6QHN1c2NlcnRlLmdvYi52ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBAME77xNS8ZlW47RsBeEaaRZhJoZ4rw785UAFCuPZOAVMqNS1wMYqzy95q6Gk UO81ER/ugiQX/KMcq/4HBn83fwdYWxPZfwBfK7BP2p/JsFgzYeFP0BXOLmvoJIzl Jb6FW+1MPwGBjuaZGFImWZsSmGUclb51mRYMZETh9/J5CLThR1exStxHQptwSzra zNFpkQY/zmj7+YZNA9yDoroVFv6sybYOZ7OxNDo7zkSLo45I7gMwtxqWZ8VkJZkC 8+p0dX6mkhUT0QAV64Zc9HsZiH/oLhEkXjhrgZ28cF73MXIqLx1fyM4kPH1yOJi/ R72nMwL7D+Sd6mZgI035TxuHXc2/uOwXfKrrTjaJDz8Jp6DdessOkxIgkKXRjP+F K3ze3n4NUIRGhGRtyvEjK95/2g02t6PeYiYVGur6ruS49n0RAaSS0/LJb6XzaAAe 0mmO2evnEqxIKwy2mZRNPfAVW1l3wCnWiUwryBU6OsbFcFFrQm+00wOicXvOTHBM aiCVAVZTb9RSLyi+LJ1llzJZO3pq3IRiiBj38Nooo+2ZNbMEciSgmig7YXaUcmud SVQvLSL+Yw+SqawyezwZuASbp7d/0rutQ59d81zlbMt3J7yB567rT2IqIydQ8qBW k+fmXzghX+/FidYsh/aK+zZ7Wy68kKHuzEw1Vqkat5DGs+VzAgMBAAGjggLeMIIC 2jASBgNVHRMBAf8ECDAGAQH/AgECMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52 ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0wMB0GA1UdDgQWBBStuyIdxuDS Aaj9dlBSk+2YwU2u0zCCAVAGA1UdIwSCAUcwggFDgBStuyIdxuDSAaj9dlBSk+2Y wU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRpZmlj YWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAw DgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYD VQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25p Y2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEgZGUgU2VydmljaW9zIGRlIENl cnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG9w0BCQEWFmFjcmFpekBz dXNjZXJ0ZS5nb2IudmWCAQEwDgYDVR0PAQH/BAQDAgEGMDcGA1UdEQQwMC6CD3N1 c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0wMFQGA1Ud HwRNMEswJKAioCCGHmhodHA6Ly93d3cuc3VzY2VydGUuZ29iLnZlL2xjcjAjoCGg H4YdbGRhcDovL2FjcmFpei5zdXNjZXJ0ZS5nb2IudmUwNwYIKwYBBQUHAQEEKzAp MCcGCCsGAQUFBzABhhtoaHRwOi8vb2NzcC5zdXNjZXJ0ZS5nb2IudmUwQAYDVR0g BDkwNzA1BgVghl4BAjAsMCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRl LmdvYi52ZS9kcGMwDQYJKoZIhvcNAQEMBQADggIBAK4qy/zmZ9zBwfW3yOYtLcBT Oy4szJyPz7/RhNH3bPVH7HbDTGpi6JZ4YXdXMBeJE5qBF4a590Kgj8Rlnltt+Rbo OFQOU1UDqKuTdBsA//Zry5899fmn8jBUkg4nh09jhHHbLlaUScdz704Zz2+UVg7i s/r3Legxap60KzmdrmTAE9VKte1TQRgavQwVX5/2mO/J+SCas//UngI+h8SyOucq mjudYEgBrZaodUsagUfn/+AzFNrGLy+al+5nZeHb8JnCfLHWS0M9ZyhgoeO/czyn 99+5G93VWNv4zfc4KiavHZKrkn8F9pg0ycIZh+OwPT/RE2zq4gTazBMlP3ACIe/p olkNaOEa8KvgzW96sjBZpMW49zFmyINYkcj+uaNCJrVGsXgdBmkuRGJNWFZ9r0cG woIaxViFBypsz045r1ESfYPlfDOavBhZ/giR/Xocm9CHkPRY2BApMMR0DUCyGETg Ql+L3kfdTKzuDjUp2DM9FqysQmaM81YDZufWkMhlZPfHwC7KbNougoLroa5Umeos bqAXWmk46SwIdWRPLLqbUpDTKooynZKpSYIkkotdgJoVZUUCY+RCO8jsVPEU6ece SxztNUm5UOta1OJPMwSAKRHOo3ilVb9c6lAixDdvV8MeNbqe6asM1mpCHWbJ/0rg 5Ls9Cxx8hracyp0ev7b0 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIIKv++n6Lw6YcwDQYJKoZIhvcNAQEFBQAwKDELMAkGA1UE BhMCQkUxGTAXBgNVBAMTEEJlbGdpdW0gUm9vdCBDQTIwHhcNMDcxMDA0MTAwMDAw WhcNMjExMjE1MDgwMDAwWjAoMQswCQYDVQQGEwJCRTEZMBcGA1UEAxMQQmVsZ2l1 bSBSb290IENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZzQh6S /3UPi790hqc/7bIYLS2X+an7mEoj39WN4IzGMhwWLQdC1i22bi+n9fzGhYJdld61 IgDMqFNAn68KNaJ6x+HK92AQZw6nUHMXU5WfIp8MXW+2QbyM69odRr2nlL/zGsvU +40OHjPIltfsjFPekx40HopQcSZYtF3CiInaYNKJIT/e1wEYNm7hLHADBGXvmAYr XR5i3FVr/mZkIV/4L+HXmymvb82fqgxG0YjFnaKVn6w/Fa7yYd/vw2uaItgscf1Y HewApDgglVrH1Tdjuk+bqv5WRi5j2Qsj1Yr6tSPwiRuhFA0m2kHwOI8w7QUmecFL TqG4flVSOmlGhHUCAwEAAaOBuzCBuDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zBCBgNVHSAEOzA5MDcGBWA4CQEBMC4wLAYIKwYBBQUHAgEWIGh0dHA6 Ly9yZXBvc2l0b3J5LmVpZC5iZWxnaXVtLmJlMB0GA1UdDgQWBBSFiuv0xbu+DlkD lN7WgAEV4xCcOTARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUhYrr9MW7 vg5ZA5Te1oABFeMQnDkwDQYJKoZIhvcNAQEFBQADggEBAFHYhd27V2/MoGy1oyCc UwnzSgEMdL8rs5qauhjyC4isHLMzr87lEwEnkoRYmhC598wUkmt0FoqW6FHvv/pK JaeJtmMrXZRY0c8RcrYeuTlBFk0pvDVTC9rejg7NqZV3JcqUWumyaa7YwBO+mPyW nIR/VRPmPIfjvCCkpDZoa01gZhz5v6yAlGYuuUGK02XThIAC71AdXkbc98m6tTR8 KvPG2F9fVJ3bTc0R5/0UAoNmXsimABKgX77OFP67H6dh96tK8QYUn8pJQsKpvO2F sauBQeYNxUJpU4c5nUwfAA4+Bw11V0SoU7Q2dmSZ3G7rPUZuFF1eR1ONeE3gJ7uO hXY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr 6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN 9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h 9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo +fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h 3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX 0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c /3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D 34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv 033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq 4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK /yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD 3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE 7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb 7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka +elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q 130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG 9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4 Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0 aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N 8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K /OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu 7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC 28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6 lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB 0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09 5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q 619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn 2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG 5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0 BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb 5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ 0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ 8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do 0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ 44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs 6GAqm4VKQPNriiTsBhYscw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn 0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n 3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P 5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi DrW5viSP -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 /ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp 7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN 5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe /v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR 5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s +12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 +HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF 5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ d0jQ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq 7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p 26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi 1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu tGWaIZDgqtCYvDi1czyL+Nw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDQzCCAiugAwIBAgIQX/h7KCtU3I1CoxW1aMmt/zANBgkqhkiG9w0BAQUFADA1 MRYwFAYDVQQKEw1DaXNjbyBTeXN0ZW1zMRswGQYDVQQDExJDaXNjbyBSb290IENB IDIwNDgwHhcNMDQwNTE0MjAxNzEyWhcNMjkwNTE0MjAyNTQyWjA1MRYwFAYDVQQK Ew1DaXNjbyBTeXN0ZW1zMRswGQYDVQQDExJDaXNjbyBSb290IENBIDIwNDgwggEg MA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQCwmrmrp68Kd6ficba0ZmKUeIhH xmJVhEAyv8CrLqUccda8bnuoqrpu0hWISEWdovyD0My5jOAmaHBKeN8hF570YQXJ FcjPFto1YYmUQ6iEqDGYeJu5Tm8sUxJszR2tKyS7McQr/4NEb7Y9JHcJ6r8qqB9q VvYgDxFUl4F1pyXOWWqCZe+36ufijXWLbvLdT6ZeYpzPEApk0E5tzivMW/VgpSdH jWn0f84bcN5wGyDWbs2mAag8EtKpP6BrXruOIIt6keO1aO6g58QBdKhTCytKmg9l Eg6CTY5j/e/rmxrbU6YTYK/CfdfHbBcl1HP7R2RQgYCUTOG/rksc35LtLgXfAgED o1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJ/PI FR5umgIJFq0roIlgX9p7L6owEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEF BQADggEBAJ2dhISjQal8dwy3U8pORFBi71R803UXHOjgxkhLtv5MOhmBVrBW7hmW Yqpao2TB9k5UM8Z3/sUcuuVdJcr18JOagxEu5sv4dEX+5wW4q+ffy0vhN4TauYuX cB7w4ovXsNgOnbFp1iqRe6lJT37mjpXYgyc81WhJDtSd9i7rp77rMKSsH0T8lasz Bvt9YAretIpjsJyp8qS5UwGH0GikJ3+r/+n6yUA4iGe0OcaEb1fJU9u6ju7AQ7L4 CYNu/2bPPu8Xs1gYJQk0XuPL1hS27PKSb3TkL4Eq1ZKR4OCXPDJoBYVL0fdX4lId kxpUnwVwwEpxYB5DC2Ae/qPOgRnhCzU= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 l7+ijrRU -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDoTCCAomgAwIBAgIQKTZHquOKrIZKI1byyrdhrzANBgkqhkiG9w0BAQUFADBO MQswCQYDVQQGEwJ1czEYMBYGA1UEChMPVS5TLiBHb3Zlcm5tZW50MQ0wCwYDVQQL EwRGQkNBMRYwFAYDVQQDEw1Db21tb24gUG9saWN5MB4XDTA3MTAxNTE1NTgwMFoX DTI3MTAxNTE2MDgwMFowTjELMAkGA1UEBhMCdXMxGDAWBgNVBAoTD1UuUy4gR292 ZXJubWVudDENMAsGA1UECxMERkJDQTEWMBQGA1UEAxMNQ29tbW9uIFBvbGljeTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeNvTMn5K1b+3i9L0dHbsd4 6ZOcpN7JHP0vGzk4rEcXwH53KQA7Ax9oD81Npe53uCxiazH2+nIJfTApBnznfKM9 hBiKHa4skqgf6F5PjY7rPxr4nApnnbBnTfAu0DDew5SwoM8uCjR/VAnTNr2kSVdS c+md/uRIeUYbW40y5KVIZPMiDZKdCBW/YDyD90ciJSKtKXG3d+8XyaK2lF7IMJCk FEhcVlcLQUwF1CpMP64Sm1kRdXAHImktLNMxzJJ+zM2kfpRHqpwJCPZLr1LoakCR xVW9QLHIbVeGlRfmH3O+Ry4+i0wXubklHKVSFzYIWcBCvgortFZRPBtVyYyQd+sC AwEAAaN7MHkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFC9Yl9ipBZilVh/72at17wI8NjTHMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJ KwYBBAGCNxUCBBYEFHa3YJbdFFYprHWF03BjwbxHhhyLMA0GCSqGSIb3DQEBBQUA A4IBAQBgrvNIFkBypgiIybxHLCRLXaCRc+1leJDwZ5B6pb8KrbYq+Zln34PFdx80 CTj5fp5B4Ehg/uKqXYeI6oj9XEWyyWrafaStsU+/HA2fHprA1RRzOCuKeEBuMPdi 4c2Z/FFpZ2wR3bgQo2jeJqVW/TZsN5hs++58PGxrcD/3SDcJjwtCga1GRrgLgwb0 Gzigf0/NC++DiYeXHIowZ9z9VKEDfgHLhUyxCynDvux84T8PCVI8L6eaSP436REG WOE2QYrEtr+O3c5Ks7wawM36GpnScZv6z7zyxFSjiDV2zBssRm8MtNHDYXaSdBHq S4CNHIkRi+xb/xfJSPzn4AYR4oRe -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI 2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp +2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW /zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR 6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC 9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV /erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z +pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB /wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM 4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV 2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl 0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB NVOFBkpdn627G190 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0 MRMwEQYDVQQDEwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQG EwJJTDAeFw0wNDAzMjQxMTMyMThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMT CkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNpZ24xCzAJBgNVBAYTAklMMIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49qROR+WCf4C9DklBKK 8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTyP2Q2 98CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb 2CEJKHxNGGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxC ejVb7Us6eva1jsz/D3zkYDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7Kpi Xd3DTKaCQeQzC6zJMw9kglcq/QytNuEMrkvF7zuZ2SOzW120V+x0cAwqTwIDAQAB o4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2Zl ZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0PAQH/BAQD AgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRL AZs+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWd foPPbrxHbvUanlR2QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0M cXS6hMTXcpuEfDhOZAYnKuGntewImbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq 8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb/627HOkthIDYIb6FUtnUdLlp hbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VGzT2ouvDzuFYk Res3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U AGegcQCCSA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGATCCA+mgAwIBAgIRAI9hcRW6eVgXjH0ROqzW264wDQYJKoZIhvcNAQELBQAw RTEfMB0GA1UEAxMWQ29tU2lnbiBHbG9iYWwgUm9vdCBDQTEVMBMGA1UEChMMQ29t U2lnbiBMdGQuMQswCQYDVQQGEwJJTDAeFw0xMTA3MTgxMDI0NTRaFw0zNjA3MTYx MDI0NTVaMEUxHzAdBgNVBAMTFkNvbVNpZ24gR2xvYmFsIFJvb3QgQ0ExFTATBgNV BAoTDENvbVNpZ24gTHRkLjELMAkGA1UEBhMCSUwwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQCyKClzKh3rm6n1nvigmV/VU1D4hSwYW2ro3VqpzpPo0Ph3 3LguqjXd5juDwN4mpxTpD99d7Xu5X6KGTlMVtfN+bTbA4t3x7DU0Zqn0BE5XuOgs 3GLH41Vmr5wox1bShVpM+IsjcN4E/hMnDtt/Bkb5s33xCG+ohz5dlq0gA9qfr/g4 O9lkHZXTCeYrmVzd/il4x79CqNvGkdL3um+OKYl8rg1dPtD8UsytMaDgBAopKR+W igc16QJzCbvcinlETlrzP/Ny76BWPnAQgaYBULax/Q5thVU+N3sEOKp6uviTdD+X O6i96gARU4H0xxPFI75PK/YdHrHjfjQevXl4J37FJfPMSHAbgPBhHC+qn/014DOx 46fEGXcdw2BFeIIIwbj2GH70VyJWmuk/xLMCHHpJ/nIF8w25BQtkPpkwESL6esaU b1CyB4Vgjyf16/0nRiCAKAyC/DY/Yh+rDWtXK8c6QkXD2XamrVJo43DVNFqGZzbf 5bsUXqiVDOz71AxqqK+p4ek9374xPNMJ2rB5MLPAPycwI0bUuLHhLy6nAIFHLhut TNI+6Y/soYpi5JSaEjcY7pxI8WIkUAzr2r+6UoT0vAdyOt7nt1y8844a7szo/aKf woziHl2O1w6ZXUC30K+ptXVaOiW79pBDcbLZ9ZdbONhS7Ea3iH4HJNwktrBJLQID AQABo4HrMIHoMA8GA1UdEwEB/wQFMAMBAf8wgYQGA1UdHwR9MHswPKA6oDiGNmh0 dHA6Ly9mZWRpci5jb21zaWduLmNvLmlsL2NybC9jb21zaWduZ2xvYmFscm9vdGNh LmNybDA7oDmgN4Y1aHR0cDovL2NybDEuY29tc2lnbi5jby5pbC9jcmwvY29tc2ln bmdsb2JhbHJvb3RjYS5jcmwwDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQCRZPY DUhirGm6rgZbPvuqJpFQsTAfBgNVHSMEGDAWgBQCRZPYDUhirGm6rgZbPvuqJpFQ sTANBgkqhkiG9w0BAQsFAAOCAgEAk1V5V9701xsfy4mfX+tP9Ln5e9h3N+QMwUfj kr+k3e8iXOqADjTpUHeBkEee5tJq09ZLp/43F5tZ2eHdYq2ZEX7iWHCnOQet6Yw9 SU1TahsrGDA6JJD9sdPFnNZooGsU1520e0zNB0dNWwxrWAmu4RsBxvEpWCJbvzQL dOfyX85RWwli81OiVMBc5XvJ1mxsIIqli45oRynKtsWP7E+b0ISJ1n+XFLdQo/Nm WA/5sDfT0F5YPzWdZymudMbXitimxC+n4oQE4mbQ4Zm718Iwg3pP9gMMcSc7Qc1J kJHPH9O7gVubkKHuSYj9T3Ym6c6egL1pb4pz/uT7cT26Fiopc/jdqbe2EAfoJZkv hlp/zdzOoXTWjiKNA5zmgWnZn943FuE9KMRyKtyi/ezJXCh8ypnqLIKxeFfZl69C BwJsPXUTuqj8Fic0s3aZmmr7C4jXycP+Q8V+akMEIoHAxcd960b4wVWKqOcI/kZS Q0cYqWOY1LNjznRt9lweWEfwDBL3FhrHOmD4++1N3FkkM4W+Q1b2WOL24clDMj+i 2n9Iw0lc1llHMSMvA5D0vpsXZpOgcCVahfXczQKi9wQ3oZyonJeWx4/rXdMtagAB VBYGFuMEUEQtybI+eIbnp5peO2WAAblQI4eTy/jMVowe5tfMEXovV3sz9ULgmGb3 DscLP1I= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr 9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt 6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7 MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58 ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp /hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y Johw1+qRzT65ysCQblrGXnRl11z+o+I= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp 3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl 6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I 0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 6pZjamVFkpUBtA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 sycX -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t 9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd +SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N 0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie 4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7 gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/ h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDOzCCAiOgAwIBAgIRANAeRlAAACmMAAAAAgAAAAIwDQYJKoZIhvcNAQEFBQAw PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD Ew5EU1QgUm9vdCBDQSBYNDAeFw0wMDA5MTMwNjIyNTBaFw0yMDA5MTMwNjIyNTBa MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UE AxMORFNUIFJvb3QgQ0EgWDQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQCthX3OFEYY8gSeIYur0O4ypOT68HnDrjLfIutL5PZHRwQGjzCPb9PFo/ihboJ8 RvfGhBAqpQCo47zwYEhpWm1jB+L/OE/dBBiyn98krfU2NiBKSom2J58RBeAwHGEy cO+lewyjVvbDDLUy4CheY059vfMjPAftCRXjqSZIolQb9FdPcAoa90mFwB7rKniE J7vppdrUScSS0+eBrHSUPLdvwyn4RGp+lSwbWYcbg5EpSpE0GRJdchic0YDjvIoC YHpe7Rkj93PYRTQyU4bhC88ck8tMqbvRYqMRqR+vobbkrj5LLCOQCHV5WEoxWh+0 E2SpIFe7RkV++MmpIAc0h1tZAgMBAAGjMjAwMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFPCD6nPIP1ubWzdf9UyPWvf0hki9MA0GCSqGSIb3DQEBBQUAA4IBAQCE G85wl5eEWd7adH6XW/ikGN5salvpq/Fix6yVTzE6CrhlP5LBdkf6kx1bSPL18M45 g0rw2zA/MWOhJ3+S6U+BE0zPGCuu8YQaZibR7snm3HiHUaZNMu5c8D0x0bcMxDjY AVVcHCoNiL53Q4PLW27nbY6wwG0ffFKmgV3blxrYWfuUDgGpyPwHwkfVFvz9qjaV mf12VJffL6W8omBPtgteb6UaT/k1oJ7YI0ldGf+ngpVbRhD+LC3cUtT6GO/BEPZu 8YTV/hbiDH5v3khVqMIeKT6o8IuXGG7F6a6vKwP1F1FwTXf4UC/ivhme7vdUH7B/ Vv4AEbT8dNfEeFxrkDbh -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c 77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 +GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIE5zCCA8+gAwIBAgIBADANBgkqhkiG9w0BAQUFADCBjTELMAkGA1UEBhMCQ0Ex EDAOBgNVBAgTB09udGFyaW8xEDAOBgNVBAcTB1Rvcm9udG8xHTAbBgNVBAoTFEVj aG93b3J4IENvcnBvcmF0aW9uMR8wHQYDVQQLExZDZXJ0aWZpY2F0aW9uIFNlcnZp Y2VzMRowGAYDVQQDExFFY2hvd29yeCBSb290IENBMjAeFw0wNTEwMDYxMDQ5MTNa Fw0zMDEwMDcxMDQ5MTNaMIGNMQswCQYDVQQGEwJDQTEQMA4GA1UECBMHT250YXJp bzEQMA4GA1UEBxMHVG9yb250bzEdMBsGA1UEChMURWNob3dvcnggQ29ycG9yYXRp b24xHzAdBgNVBAsTFkNlcnRpZmljYXRpb24gU2VydmljZXMxGjAYBgNVBAMTEUVj aG93b3J4IFJvb3QgQ0EyMIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA utU/5BkV15UBf+s+JQruKQxr77s3rjp/RpOtmhHILIiO5gsEWP8MMrfrVEiidjI6 Qh6ans0KAWc2Dw0/j4qKAQzOSyAZgjcdypNTBZ7muv212DA2Pu41rXqwMrlBrVi/ KTghfdLlNRu6JrC5y8HarrnRFSKF1Thbzz921kLDRoCi+FVs5eVuK5LvIfkhNAqA byrTgO3T9zfZgk8upmEkANPDL1+8y7dGPB/d6lk0I5mv8PESKX02TlvwgRSIiTHR k8++iOPLBWlGp7ZfqTEXkPUZhgrQQvxcrwCUo6mk8TqgxCDP5FgPoHFiPLef5szP ZLBJDWp7GLyE1PmkQI6WiwIBA6OCAVAwggFMMA8GA1UdEwEB/wQFMAMBAf8wCwYD VR0PBAQDAgEGMB0GA1UdDgQWBBQ74YEboKs/OyGC1eISrq5QqxSlEzCBugYDVR0j BIGyMIGvgBQ74YEboKs/OyGC1eISrq5QqxSlE6GBk6SBkDCBjTELMAkGA1UEBhMC Q0ExEDAOBgNVBAgTB09udGFyaW8xEDAOBgNVBAcTB1Rvcm9udG8xHTAbBgNVBAoT FEVjaG93b3J4IENvcnBvcmF0aW9uMR8wHQYDVQQLExZDZXJ0aWZpY2F0aW9uIFNl cnZpY2VzMRowGAYDVQQDExFFY2hvd29yeCBSb290IENBMoIBADBQBgNVHSAESTBH MEUGCysGAQQB+REKAQMBMDYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuZWNob3dv cnguY29tL2NhL3Jvb3QyL2Nwcy5wZGYwDQYJKoZIhvcNAQEFBQADggEBAG+nrPi/ 0RpfEzrj02C6JGPUar4nbjIhcY6N7DWNeqBoUulBSIH/PYGNHYx7/lnJefiixPGE 7TQ5xPgElxb9bK8zoAApO7U33OubqZ7M7DlHnFeCoOoIAZnG1kuwKwD5CXKB2a74 HzcqNnFW0IsBFCYqrVh/rQgJOzDA8POGbH0DeD0xjwBBooAolkKT+7ZItJF1Pb56 QpDL9G+16F7GkmnKlAIYT3QTS3yFGYChnJcd+6txUPhKi9sSOOmAIaKHnkH9Scz+ A2cSi4A3wUYXVatuVNHpRb2lygfH3SuCX9MU8Ure3zBlSU1LALtMqI4JmcQmQpIq zIzvO2jHyu9PQqo= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE 1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v 1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi 94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP 9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH 4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy vUxFnmG6v4SBkgPR0ml8xQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH 4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er fF6adulZkMV8gzURZVE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS /jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D hNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEYDCCA0igAwIBAgICATAwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCVVMx GDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsGA1UECxMERlBLSTEhMB8GA1UE AxMYRmVkZXJhbCBDb21tb24gUG9saWN5IENBMB4XDTEwMTIwMTE2NDUyN1oXDTMw MTIwMTE2NDUyN1owWTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1UuUy4gR292ZXJu bWVudDENMAsGA1UECxMERlBLSTEhMB8GA1UEAxMYRmVkZXJhbCBDb21tb24gUG9s aWN5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2HX7NRY0WkG/ Wq9cMAQUHK14RLXqJup1YcfNNnn4fNi9KVFmWSHjeavUeL6wLbCh1bI1FiPQzB6+ Duir3MPJ1hLXp3JoGDG4FyKyPn66CG3G/dFYLGmgA/Aqo/Y/ISU937cyxY4nsyOl 4FKzXZbpsLjFxZ+7xaBugkC7xScFNknWJidpDDSPzyd6KgqjQV+NHQOGgxXgVcHF mCye7Bpy3EjBPvmE0oSCwRvDdDa3ucc2Mnr4MrbQNq4iGDGMUHMhnv6DOzCIJOPp wX7e7ZjHH5IQip9bYi+dpLzVhW86/clTpyBLqtsgqyFOHQ1O5piF5asRR12dP8Qj wOMUBm7+nQIDAQABo4IBMDCCASwwDwYDVR0TAQH/BAUwAwEB/zCB6QYIKwYBBQUH AQsEgdwwgdkwPwYIKwYBBQUHMAWGM2h0dHA6Ly9odHRwLmZwa2kuZ292L2ZjcGNh L2NhQ2VydHNJc3N1ZWRCeWZjcGNhLnA3YzCBlQYIKwYBBQUHMAWGgYhsZGFwOi8v bGRhcC5mcGtpLmdvdi9jbj1GZWRlcmFsJTIwQ29tbW9uJTIwUG9saWN5JTIwQ0Es b3U9RlBLSSxvPVUuUy4lMjBHb3Zlcm5tZW50LGM9VVM/Y0FDZXJ0aWZpY2F0ZTti aW5hcnksY3Jvc3NDZXJ0aWZpY2F0ZVBhaXI7YmluYXJ5MA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUrQx6dVzl85jEeZgOrCj9l/TnAvwwDQYJKoZIhvcNAQELBQAD ggEBAI9z2uF/gLGH9uwsz9GEYx728Yi3mvIRte9UrYpuGDco71wb5O9Qt2wmGCMi TR0mRyDpCZzicGJxqxHPkYnos/UqoEfAFMtOQsHdDA4b8Idb7OV316rgVNdF9IU+ 7LQd3nyKf1tNnJaK0KIyn9psMQz4pO9+c+iR3Ah6cFqgr2KBWfgAdKLI3VTKQVZH venAT+0g3eOlCd+uKML80cgX2BLHb94u6b2akfI8WpQukSKAiaGMWMyDeiYZdQKl Dn0KJnNR6obLB6jI/WNaNZvSr79PMUjBhHDbNXuaGQ/lj/RqDG8z2esccKIN47lQ A2EC/0rskqTcLe4qNJMHtyznGI8= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz rD6ogRLQy7rQkgu2npaqBA+K -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz +uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn 5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G spki4cErx5z481+oghLrGREt -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl 4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r 6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z 09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs ewv4n4Q= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc 8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg 515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO xwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h /t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf ReYNnyicsbkqWletNw+vHX/bvZ8= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH /PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu 9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo 2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFSzCCAzOgAwIBAgIRALZLiAfiI+7IXBKtpg4GofIwDQYJKoZIhvcNAQELBQAw PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xMjA5MjgwODU4NTFaFw0zNzEyMzExNTU5NTla MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlm aWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQC2/5c8gb4BWCQnr44BK9ZykjAyG1+bfNTUf+ihYHMwVxAA+lCWJP5Q5ow6ldFX eYTVZ1MMKoI+GFy4MCYa1l7GLbIEUQ7v3wxjR+vEEghRK5lxXtVpe+FdyXcdIOxW juVhYC386RyA3/pqg7sFtR4jEpyCygrzFB0g5AaPQySZn7YKk1pzGxY5vgW28Yyl ZJKPBeRcdvc5w88tvQ7Yy6gOMZvJRg9nU0MEj8iyyIOAX7ryD6uBNaIgIZfOD4k0 eA/PH07p+4woPN405+2f0mb1xcoxeNLOUNFggmOd4Ez3B66DNJ1JSUPUfr0t4urH cWWACOQ2nnlwCjyHKenkkpTqBpIpJ3jmrdc96QoLXvTg1oadLXLLi2RW5vSueKWg OTNYPNyoj420ai39iHPplVBzBN8RiD5C1gJ0+yzEb7xs1uCAb9GGpTJXA9ZN9E4K mSJ2fkpAgvjJ5E7LUy3Hsbbi08J1J265DnGyNPy/HE7CPfg26QrMWJqhGIZO4uGq s3NZbl6dtMIIr69c/aQCb/+4DbvVq9dunxpPkUDwH0ZVbaCSw4nNt7H/HLPLo5wK 4/7NqrwB7N1UypHdTxOHpPaY7/1J1lcqPKZc9mA3v9g+fk5oKiMyOr5u5CI9ByTP isubXVGzMNJxbc5Gim18SjNE2hIvNkvy6fFRCW3bapcOFwIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBTVZx3gnHosnMvFmOcdByYqhux0zTAOBgNV HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAJA75cJTQijq9TFOjj2Rnk0J 89ixUuZPrAwxIbvx6pnMg/y2KOTshAcOD06Xu29oRo8OURWV+Do7H1+CDgxxDryR T64zLiNB9CZrTxOH+nj2LsIPkQWXqmrBap+8hJ4IKifd2ocXhuGzyl3tOKkpboTe Rmv8JxlQpRJ6jH1i/NrnzLyfSa8GuCcn8on3Fj0Y5r3e9YwSkZ/jBI3+BxQaWqw5 ghvxOBnhY+OvbLamURfr+kvriyL2l/4QOl+UoEtTcT9a4RD4co+WgN2NApgAYT2N vC2xR8zaXeEgp4wxXPHj2rkKhkfIoT0Hozymc26Uke1uJDr5yTDRB6iBfSZ9fYTf hsmL5a4NHr6JSFEVg5iWL0rrczTXdM3Jb9DCuiv2mv6Z3WAUjhv5nDk8f0OJU+jl wqu+Iq0nOJt3KLejY2OngeepaUXrjnhWzAWEx/uttjB8YwWfLYwkf0uLkvw4Hp+g pVezbp3YZLhwmmBScMip0P/GnO0QYV7Ngw5u6E0CQUridgR51lQ/ipgyFKDdLZzn uoJxo4ZVKZnSKdt1OvfbQ/+2W/u3fjWAjg1srnm3Ni2XUqGwB5wH5Ss2zQOXlL0t DjQG/MAWifw3VOTWzz0TBPKR2ck2Lj7FWtClTILD/y58Jnb38/1FoqVuVa4uzM8s iTTa9g3nkagQ6hed8vbs -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD 75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp 5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p 6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI l7WdmplNsDz4SgCbZN2fOUvRJ9e4 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi AmvZWg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFHjCCBAagAwIBAgIEAKA3oDANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMC Q1oxOjA4BgNVBAMMMUkuQ0EgLSBRdWFsaWZpZWQgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHksIDA5LzIwMDkxLTArBgNVBAoMJFBydm7DrSBjZXJ0aWZpa2HEjW7DrSBh dXRvcml0YSwgYS5zLjE9MDsGA1UECww0SS5DQSAtIEFjY3JlZGl0ZWQgUHJvdmlk ZXIgb2YgQ2VydGlmaWNhdGlvbiBTZXJ2aWNlczAeFw0wOTA5MDEwMDAwMDBaFw0x OTA5MDEwMDAwMDBaMIG3MQswCQYDVQQGEwJDWjE6MDgGA1UEAwwxSS5DQSAtIFF1 YWxpZmllZCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSwgMDkvMjAwOTEtMCsGA1UE CgwkUHJ2bsOtIGNlcnRpZmlrYcSNbsOtIGF1dG9yaXRhLCBhLnMuMT0wOwYDVQQL DDRJLkNBIC0gQWNjcmVkaXRlZCBQcm92aWRlciBvZiBDZXJ0aWZpY2F0aW9uIFNl cnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTaEy0KC8M9l 4lSaWHMs4+sVV1LwzyJYiIQNeCrv1HHm/YpGIdY/Z640ceankjQvIX7m23BK4OSC 6KO8kZYA3zopOz6GFCOKV2PvLukbc+c2imF6kLHEv6qNA8WxhPbR3xKwlHDwB2yh Wzo7V3QVgDRG83sugqQntKYC3LnlTGbJpNP+Az72gpO9AHUn/IBhFk4ksc8lYS2L 9GCy9CsmdKSBP78p9w8Lx7vDLqkDgt1/zBrcUWmSSb7AE/BPEeMryQV1IdI6nlGn BhWkXOYf6GSdayJw86btuxC7viDKNrbp44HjQRaSxnp6O3eto1x4DfiYdw/YbJFe 7EjkxSQBywIDAQABo4IBLjCCASowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E BAMCAQYwgecGA1UdIASB3zCB3DCB2QYEVR0gADCB0DCBzQYIKwYBBQUHAgIwgcAa gb1UZW50byBjZXJ0aWZpa2F0IGplIHZ5ZGFuIGpha28ga3ZhbGlmaWtvdmFueSBz eXN0ZW1vdnkgY2VydGlmaWthdCBwb2RsZSB6YWtvbmEgYy4gMjI3LzIwMDAgU2Iu IHYgcGxhdG5lbSB6bmVuaS9UaGlzIGlzIHF1YWxpZmllZCBzeXN0ZW0gY2VydGlm aWNhdGUgYWNjb3JkaW5nIHRvIEN6ZWNoIEFjdCBOby4gMjI3LzIwMDAgQ29sbC4w HQYDVR0OBBYEFHnL0CPpOmdwkXRP01Hi4CD94Sj7MA0GCSqGSIb3DQEBCwUAA4IB AQB9laU214hYaBHPZftbDS/2dIGLWdmdSbj1OZbJ8LIPBMxYjPoEMqzAR74tw96T i6aWRa5WdOWaS6I/qibEKFZhJAVXX5mkx2ewGFLJ+0Go+eTxnjLOnhVF2V2s+57b m8c8j6/bS6Ij6DspcHEYpfjjh64hE2r0aSpZDjGzKFM6YpqsCJN8qYe2X1qmGMLQ wvNdjG+nPzCJOOuUEypIWt555ZDLXqS5F7ZjBjlfyDZjEfS2Es9Idok8alf563Mi 9/o+Ba46wMYOkk3P1IlU0RqCajdbliioACKDztAqubONU1guZVzV8tuMASVzbJeL /GAB7ECTwe1RuKrLYtglMKI9 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT 3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU +ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH 6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 +wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG 4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A 7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF /YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R 3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy 9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ 2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 +bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv 8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEXzCCA0egAwIBAgIBATANBgkqhkiG9w0BAQUFADCB0DELMAkGA1UEBhMCRVMx SDBGBgNVBAoTP0laRU5QRSBTLkEuIC0gQ0lGIEEtMDEzMzcyNjAtUk1lcmMuVml0 b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFCMEAGA1UEBxM5QXZkYSBkZWwgTWVk aXRlcnJhbmVvIEV0b3JiaWRlYSAzIC0gMDEwMTAgVml0b3JpYS1HYXN0ZWl6MRMw EQYDVQQDEwpJemVucGUuY29tMR4wHAYJKoZIhvcNAQkBFg9JbmZvQGl6ZW5wZS5j b20wHhcNMDMwMTMwMjMwMDAwWhcNMTgwMTMwMjMwMDAwWjCB0DELMAkGA1UEBhMC RVMxSDBGBgNVBAoTP0laRU5QRSBTLkEuIC0gQ0lGIEEtMDEzMzcyNjAtUk1lcmMu Vml0b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFCMEAGA1UEBxM5QXZkYSBkZWwg TWVkaXRlcnJhbmVvIEV0b3JiaWRlYSAzIC0gMDEwMTAgVml0b3JpYS1HYXN0ZWl6 MRMwEQYDVQQDEwpJemVucGUuY29tMR4wHAYJKoZIhvcNAQkBFg9JbmZvQGl6ZW5w ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1btoCXXhp3xIW D+Bxl8nUCxkyiazWfpt0e68t+Qt9+lZjKZSdEw2Omj4qvr+ovRmDXO3iWpWVOWDl 3JHJjAzFCe8ZEBNDH+QNYwZHmPBaMYFOYFdbAFVHWvys152C308hcFJ6xWWGmjvl 2eMiEl9P2nR2LWue368DCu+ak7j3gjAXaCOdP1a7Bfr+RW3X2SC5R4Xyp8iHlL5J PHJD/WBkLrezwzQPdACw8m9EG7q9kUwlNpL32mROujS3ZkT6mQTzJieLiE3X04s0 uIUqVkk5MhjcHFf7al0N5CzjtTcnXYJKN2Z9EDVskk4olAdGi46eSoZXbjUOP5gk Ej6wVZAXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG MB0GA1UdDgQWBBTqVk/sPIOhFIh4gbIrBSLAB0FbQjANBgkqhkiG9w0BAQUFAAOC AQEAYp7mEzzhw6o5Hf5+T5kcI+t4BJyiIWy7vHlLs/G8dLYXO81aN/Mzg928eMTR TxxYZL8dd9uwsJ50TVfX6L0R4Dyw6wikh3fHRrat9ufXi63j5K91Ysr7aXqnF38d iAgHYkrwC3kuxHBb9C0KBz6h8Q45/KCyN7d37wWAq38yyhPDlaOvyoE6bdUuK5hT m5EYA5JmPyrhQ1moDOyueWBAjxzMEMj+OAY1H90cLv6wszsqerxRrdTOHBdv7MjB EIpvEEQkXUxVXAzFuuT6m2t91Lfnwfl/IvljHaVC7DlyyhRYHD6D4Rx+4QKp4tWL vpw6LkI+gKNJ/YdMCsRZQzEEFA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF8DCCA9igAwIBAgIPBuhGJy8fCo/RhFzjafbVMA0GCSqGSIb3DQEBBQUAMDgx CzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXpl bnBlLmNvbTAeFw0wNzEyMTMxMzA4MjdaFw0zNzEyMTMwODI3MjVaMDgxCzAJBgNV BAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNv bTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMnTesoPHqynhugWZWqx whtFMnGV2f4QW8yv56V5AY+Jw8ryVXH3d753lPNypCxE2J6SmxQ6oeckkAoKVo7F 2CaU4dlI4S0+2gpy3aOZFdqBoof0e24md4lYrdbrDLJBenNubdt6eEHpCIgSfocu ZhFjbFT7PJ1ywLwu/8K33Q124zrX97RovqL144FuwUZvXY3gTcZUVYkaMzEKsVe5 o4qYw+w7NMWVQWl+dcI8IMVhulFHoCCQk6GQS/NOfIVFVJrRBSZBsLVNHTO+xAPI JXzBcNs79AktVCdIrC/hxKw+yMuSTFM5NyPs0wH54AlETU1kwOENWocivK0bo/4m tRXzp/yEGensoYi0RGmEg/OJ0XQGqcwL1sLeJ4VQJsoXuMl6h1YsGgEebL4TrRCs tST1OJGh1kva8bvS3ke18byB9llrzxlT6Y0Vy0rLqW9E5RtBz+GGp8rQap+8TI0G M1qiheWQNaBiXBZO8OOi+gMatCxxs1gs3nsL2xoP694hHwZ3BgOwye+Z/MC5TwuG KP7Suerj2qXDR2kS4Nvw9hmL7Xtw1wLW7YcYKCwEJEx35EiKGsY7mtQPyvp10gFA Wo15v4vPS8+qFsGV5K1Mij4XkdSxYuWC5YAEpAN+jb/af6IPl08M0w3719Hlcn4c yHf/W5oPt64FRuXxqBbsR6QXAgMBAAGjgfYwgfMwgbAGA1UdEQSBqDCBpYEPaW5m b0BpemVucGUuY29tpIGRMIGOMUcwRQYDVQQKDD5JWkVOUEUgUy5BLiAtIENJRiBB MDEzMzcyNjAtUk1lcmMuVml0b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFDMEEG A1UECQw6QXZkYSBkZWwgTWVkaXRlcnJhbmVvIEV0b3JiaWRlYSAxNCAtIDAxMDEw IFZpdG9yaWEtR2FzdGVpejAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUHRxlDqjyJXu0kc/ksbHmvVV0bAUwDQYJKoZIhvcNAQEFBQAD ggIBAMeBRm8hGE+gBe/n1bqXUKJg7aWSFBpSm/nxiEqg3Hh10dUflU7F57dp5iL0 +CmoKom+z892j+Mxc50m0xwbRxYpB2iEitL7sRskPtKYGCwkjq/2e+pEFhsqxPqg l+nqbFik73WrAGLRne0TNtsiC7bw0fRue0aHwp28vb5CO7dz0JoqPLRbEhYArxk5 ja2DUBzIgU+9Ag89njWW7u/kwgN8KRwCfr00J16vU9adF79XbOnQgxCvv11N75B7 XSus7Op9ACYXzAJcY9cZGKfsK8eKPlgOiofmg59OsjQerFQJTx0CCzl+gQgVuaBp E8gyK+OtbBPWg50jLbJtooiGfqgNASYJQNntKE6MkyQP2/EeTXp6WuKlWPHcj1+Z ggwuz7LdmMySlD/5CbOlliVbN/UShUHiGUzGigjB3Bh6Dx4/glmimj4/+eAJn/3B kUtdyXvWton83x18hqrNA/ILUpLxYm9/h+qrdslsUMIZgq+qHfUgKGgu1fxkN0/P pUTEvnK0jHS0bKf68r10OEMr3q/53NjgnZ/cPcqlY0S/kqJPTIAcuxrDmkoEVU3K 7iYLHL8CxWTTnn7S05EcS6L1HOUXHA0MUqORH5zwIe0ClG+poEnK6EOMxPQ02nwi o8ZmPrgbBYhdurz3vOXcFD2nhqi2WVIhA16L4wTtSyoeo09Q -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJLUjEN MAsGA1UECgwES0lTQTEuMCwGA1UECwwlS29yZWEgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHkgQ2VudHJhbDEWMBQGA1UEAwwNS0lTQSBSb290Q0EgMTAeFw0wNTA4MjQw ODA1NDZaFw0yNTA4MjQwODA1NDZaMGQxCzAJBgNVBAYTAktSMQ0wCwYDVQQKDARL SVNBMS4wLAYDVQQLDCVLb3JlYSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBDZW50 cmFsMRYwFAYDVQQDDA1LSVNBIFJvb3RDQSAxMIIBIDANBgkqhkiG9w0BAQEFAAOC AQ0AMIIBCAKCAQEAvATk+hM58DSWIGtsaLv623f/J/es7C/n/fB/bW+MKs0lCVsk 9KFo/CjsySXirO3eyDOE9bClCTqnsUdIxcxPjHmc+QZXfd3uOPbPFLKc6tPAXXdi 8EcNuRpAU1xkcK8IWsD3z3X5bI1kKB4g/rcbGdNaZoNy4rCbvdMlFQ0yb2Q3lIVG yHK+d9VuHygvx2nt54OJM1jT3qC/QOhDUO7cTWu8peqmyGGO9cNkrwYV3CmLP3WM vHFE2/yttRcdbYmDz8Yzvb9Fov4Kn6MRXw+5H5wawkbMnChmn3AmPC7fqoD+jMUE CSVPzZNHPDfqAmeS/vwiJFys0izgXAEzisEZ2wIBA6MyMDAwHQYDVR0OBBYEFL+2 J9gDWnZlTGEBQVYx5Yt7OtnMMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEF BQADggEBABOvUQveimpb5poKyLGQSk6hAp3MiNKrZr097LuxQpVqslxa/6FjZJap aBV/JV6K+KRzwYCKhQoOUugy50X4TmWAkZl0Q+VFnUkq8JSV3enhMNITbslOsXfl BM+tWh6UCVrXPAgcrnrpFDLBRa3SJkhyrKhB2vAhhzle3/xk/2F0KpzZm4tfwjeT 2KM3LzuTa7IbB6d/CVDv0zq+IWuKkDsnSlFOa56ch534eJAx7REnxqhZvvwYC/uO fi5C4e3nCSG9uRPFVmf0JqZCQ5BEVLRxm3bkGhKsGigA35vB1fjbXKP4krG9tNT5 UNkAAk/bg9ART6RCVmE6fhMy04Qfybo= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 +rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c 2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C +C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH /nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg 4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ /L7fCg0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX 1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P 99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh 4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc 3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz 8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l 7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE +V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp +ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og /zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y 4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza 8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR /xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP 0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf 3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl 8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB 4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd 8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A 4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd +LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B 4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK SnQ2+Q== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa /FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO 0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj 7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS 8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ 3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11 bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5 bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1 VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+ Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy 1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt 5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s 3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu 8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ 3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS /ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH 1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u 2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc 7uzXLg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp 5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy 5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv 6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen 5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL +63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR 9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az 5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh /WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw 0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq 4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR 1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM 94B7IWcnMFk= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf 8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN +lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA 1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg 8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk 6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn 0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN sSi6 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM 0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K 2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl 6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK 9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w +2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B 26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ 9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w +2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B 26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst 0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK 1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ 8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm fyWl8kgAwKQB2j8= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9 m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/ TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo 19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e 3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6 MBr1mmz0DlP5OlvRHA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r 0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f 2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL 6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0 uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv /2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N 8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2 9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5 wSsSnqaeG8XmDtkx2Q== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290 IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD 1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/ 5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f 46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth 7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0 Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70 WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm 7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb I+2ksx0WckNLIOFZfsLorSa/ovc= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c 6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn 8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a 77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFejCCA2KgAwIBAgIJAN7E8kTzHab8MA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxJDAiBgNVBAMTG1N3aXNzU2ln biBHb2xkIFJvb3QgQ0EgLSBHMzAeFw0wOTA4MDQxMzMxNDdaFw0zNzA4MDQxMzMx NDdaMEoxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxJDAiBgNV BAMTG1N3aXNzU2lnbiBHb2xkIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEB BQADggIPADCCAgoCggIBAMPon8hlWp1nG8FFl7S0h0NbYWCAnvJ/XvlnRN1E+qu1 q3f/KhlMzm/Ej0Gf4OLNcuDR1FJhQQkKvwpw++CDaWEpytsimlul5t0XlbBvhI46 PmRaQfsbWPz9Kz6ypOasyYK8zvaV+Jd37Sb2WK6eJ+IPg+zFNljIe8/Vh6GphxoT Z2EBbaZpnOKQ8StoZfPosHz8gj3erdgKAAlEeROc8P5udXvCvLNZAQt8xdUt8L// bVfSSYHrtLNQrFv5CxUVjGn/ozkB7fzc3CeXjnuL1Wqm1uAdX80Bkeb1Ipi6LgkY OG8TqIHS+yE35y20YueBkLDGeVm3Z3X+vo87+jbsr63ST3Q2AeVXqyMEzEpel89+ xu+MzJUjaY3LOMcZ9taKABQeND1v2gwLw7qX/BFLUmE+vzNnUxC/eBsJwke6Hq9Y 9XWBf71W8etW19lpDAfpNzGwEhwy71bZvnorfL3TPbxqM006PFAQhyfHegpnU9t/ gJvoniP6+Qg6i6GONFpIM19k05eGBxl9iJTOKnzFat+vvKmfzTqmurtU+X+P388O WsStmryzOndzg0yTPJBotXxQlRHIgl6UcdBBGPvJxmXszom2ziKzEVs/4J0+Gxho DaoDoWdZv2udvPjyZS+aQTpF2F7QNmxvOx5jtI6YTBPbIQ6fe+3qoKpxw+ujoNIl AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBRclwZGNKvfMMV8xQ1VcWYwtWCPnjAfBgNVHSMEGDAWgBRclwZGNKvfMMV8 xQ1VcWYwtWCPnjANBgkqhkiG9w0BAQsFAAOCAgEAd0tN3uqFSqssJ9ZFx/FfIMFb YO0Hy6Iz3DbPx5TxBsfV2s/NrYQ+/xJIf0HopWZXMMQd5KcaLy1Cwe9Gc7LV9Vr9 Dnpr0sgxow1IlldlY1UYwPzkisyYhlurDIonN/ojaFlcJtehwcK5Tiz/KV7mlAu+ zXJPleiP9ve4Pl7Oz54RyawDKUiKqbamNLmsQP/EtnM3scd/qVHbSypHX0AkB4gG tySz+3/3sIsz+r8jdaNc/qplGsK+8X2BdwOBsY3XlQ16PEKYt4+pfVDh31IGmqBS VHiDB2FSCTdeipynxlHRXGPRhNzC29L6Wxg2fWa81CiXL3WWHIQHrIuOUxG+JCGq Z/LBrYic07B4Z3j101gDIApdIPG152XMDiDj1d/mLxkrhWjBBCbPj+0FU6HdBw7r QSbHtKksW+NpPWbAYhvAqobAN8MxBIZwOb5rXyFAQaB/5dkPOEtwX0n4hbgrLqof k0FD+PuydDwfS1dbt9RRoZJKzr4Qou7YFCJ7uUG9jemIqdGPAxpg/z+HiaCZJyJm sD5onnKIUTidEz5FbQXlRrVz7UOGsRQKHrzaDb8eJFxmjw6+of3G62m8Q3nXA3b5 3IeZuJjEzX9tEPkQvixC/pwpTYNrCr21jsRIiv0hB6aAfR+b6au9gmFECnEnX22b kJ6u/zYks2gD1pWMa3M= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+ 6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl +zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0 ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0 uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+ FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7 jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/ u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1 puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFgTCCA2mgAwIBAgIIIj+pFyDegZQwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UE BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEoMCYGA1UEAxMfU3dpc3NTaWdu IFBsYXRpbnVtIFJvb3QgQ0EgLSBHMzAeFw0wOTA4MDQxMzM0MDRaFw0zNzA4MDQx MzM0MDRaME4xCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKDAm BgNVBAMTH1N3aXNzU2lnbiBQbGF0aW51bSBSb290IENBIC0gRzMwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQCUoO8TG59EIBvNxaoiu9nyUj56Wlh35o2h K8ncpPPksxOUAGKbHPJDUEOBfq8wNkmsGIkMGEW4PsdUbePYmllriholqba1Dbd9 I/BffagHqfc+hi7IAU3c5jbtHeU3B2kSS+OD0QQcJPAfcHHnGe1zSG6VKxW2VuYC 31bpm/rqpu7gwsO64MzGyHvXbzqVmzqPvlss0qmgOD7WiOGxYhOO3KswZ82oaqZj K4Kwy8c9Tu1y9n2rMk5lAusPmXT4HBoojA5FAJMsFJ9txxue9orce3jjtJRHHU0F bYR6kFSynot1woDfhzk/n/tIVAeNoCn1+WBfWnLou5ugQuAIADSjFTwT49YaawKy lCGjnUG8KmtOMzumlDj8PccrM7MuKwZ0rJsQb8VORfddoVYDLA1fer0e3h13kGva pS2KTOnfQfTnS+x9lUKfTKkJD0OIPz2T5yv0ekjaaMTdEoAxGl0kVCamJCGzTK3a Fwg2AlfGnIZwyXXJnnxh2HjmuegUafkcECgSXUt1ULo80GdwVVVWS/s9HNjbeU2X 37ie2xcs1TUHuFCp9473Vv96Z0NPINnKZtY4YEvulDHWDaJIm/80aZTGNfWWiO+q ZsyBputMU/8ydKe2nZhXtLomqfEzM2J+OrADEVf/3G8RI60+xgrQzFS3LcKTHeXC pozH2O9T9wIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUVio/kFj0F1oUstcIG4VbVGpUGigwHwYDVR0jBBgwFoAUVio/ kFj0F1oUstcIG4VbVGpUGigwDQYJKoZIhvcNAQELBQADggIBAGztiudDqHknm7jP hz5kOBiMEUKShjfgWMMb7gQu94TsgxBoDH94LZzCl442ThbYDuprSK1Pnl0NzA2p PhiFfsxomTk11tifhsEy+01lsyIUS8iFZtoX/3GRrJxWV95xLFZCv/jNDvCi0//S IhX70HgKfuGwWs6ON9upnueVz2PyLA3S+m/zyNX7ALf3NWcQ03tS7BAy+L/dXsmm gqTxsL8dLt0l5L1N8DWpkQFH+BAClFvrPusNutUdYyylLqvn4x6j7kuqX7FmAbSC WvlGS8fx+N8svv113ZY4mjc6bqXmMhVus5DAOYp0pZWgvg0uiXnNKVaOw15XUcQF bwRVj4HpTL1ZRssqvE3JHfLGTwXkyAQN925P2sM6nNLC9enGJHoUPhxCMKgCRTGp /FCp3NyGOA9bkz9/CE5qDSc6EHlWwxW4PgaG9tlwZ691eoviWMzGdU8yVcVsFAko O/KV5GreLCgHraB9Byjd1Fqj6aZ8E4yZC1J429nR3z5aQ3Z/RmBTws3ndkd8Vc20 OWQQW5VLNV1EgyTV4C4kDMGAbmkAgAZ3CmaCEAxRbzeJV9vzTOW4ue4jZpdgt1Ld 2Zb7uoo7oE3OXvBETJDMIU8bOphrjjGD+YMIUssZwTVr7qEVW4g/bazyNJJTpjAq E9fmhqhd2ULSx52peovL3+6iMcLl -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH 6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ 2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFfjCCA2agAwIBAgIJAKqIsFoLsXabMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxJjAkBgNVBAMTHVN3aXNzU2ln biBTaWx2ZXIgUm9vdCBDQSAtIEczMB4XDTA5MDgwNDEzMTkxNFoXDTM3MDgwNDEz MTkxNFowTDELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEmMCQG A1UEAxMdU3dpc3NTaWduIFNpbHZlciBSb290IENBIC0gRzMwggIiMA0GCSqGSIb3 DQEBAQUAA4ICDwAwggIKAoICAQC+h5sF5nF8Um9t7Dep6bPczF9/01DqIZsE8D2/ vo7JpRQWMhDPmfzscK1INmckDBcy1inlSjmxN+umeAxsbxnKTvdR2hro+iE4bJWc L9aLzDsCm78mmxFFtrg0Wh2mVEhSyJ14cc5ISsyneIPcaKtmHncH0zYYCNfUbWD4 8HnTMzYJkmO3BJr1p5baRa90GvyC46hbDjo/UleYfrycjMHAslrfxH7+DKZUdoN+ ut3nKvRKNk+HZS6lujmNWWEp89OOJHCMU5sRpUcHsnUFXA2E2UTZzckmRFduAn2V AdSrJIbuPXD7V/qwKRTQnfLFl8sJyvHyPefYS5bpiC+eR1GKVGWYSNIS5FR3DAfm vluc8d0Dfo2E/L7JYtX8yTroibVfwgVSYfCcPuwuTYxykY7IQ8GiKF71gCTc4i+H O1MA5cvwsnyNeRmgiM14+MWKWnflBqzdSt7mcG6+r771sasOCLDboD+Uxb4Subx7 J3m1MildrsUgI5IDe1Q5sIkiVG0S48N46jpA/aSTrOktiDzbpkdmTN/YF+0W3hrW 10Fmvx2A8aTgZBEpXgwnBWLr5cQEYtHEnwxqVdZYOJxmD537q1SAmZzsSdaCn9pF 1j9TBgO3/R/shn104KS06DK2qgcj+O8kQZ5jMHj0VN2O8Fo4jhJ/eMdvAlYhM864 uK1pVQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUoYxFkwoSYwunV18ySn3hIee3PmYwHwYDVR0jBBgwFoAUoYxFkwoS YwunV18ySn3hIee3PmYwDQYJKoZIhvcNAQELBQADggIBAIeuYW1IOCrGHNxKLoR4 ScAjKkW4NU3RBfq5BTPEZL3brVQWKrA+DVoo2qYagHMMxEFvr7g0tnfUW44dC4tG kES1s+5JGInBSzSzhzV0op5FZ+1FcWa2uaElc9fCrIj70h2na9rAWubYWWQ0l2Ug MTMDT86tCZ6u6cI+GHW0MyUSuwXsULpxQOK93ohGBSGEi6MrHuswMIm/EfVcRPiR i0tZRQswDcoMT29jvgT+we3gh/7IzVa/5dyOetTWKU6A26ubP45lByL3RM2WHy3H 9Qm2mHD/ONxQFRGEO3+p8NgkVMgXjCsTSdaZf0XRD46/aXI3Uwf05q79Wz55uQbN uIF4tE2g0DW65K7/00m8Ne1jxrP846thWgW2C+T/qSq+31ROwktcaNqjMqLJTVcY UzRZPGaZ1zwCeKdMcdC/2/HEPOcB5gTyRPZIJjAzybEBGesC8cwh+joCMBedyF+A P90lrAKb4xfevcqSFNJSgVPm6vwwZzKpYvaTFxUHMV4PG2n19Km3fC2z7YREMkco BzuGaUWpxzaWkHJ02BKmcyPRTrm2ejrEKaFQBhG52fQmbmIIEiAW8AFXF9QFNmeX 61H5/zMkDAUPVr/vPRxSjoreaQ9aH/DVAzFEs5LG6nWorrvHYAOImP/HBIRSkIbh tJOpUC/o69I2rDBgp9ADE7UK -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICqDCCAi2gAwIBAgIQIW4zpcvTiKRvKQe0JzzE2DAKBggqhkjOPQQDAzCBlDEL MAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBD bGFzcyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0g RzQwHhcNMTExMDA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBlDELMAkGA1UEBhMC VVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1h bnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAxIFB1 YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAATXZrUb266zYO5G6ohjdTsqlG3zXxL24w+etgoUU0hS yNw6s8tIICYSTvqJhNTfkeQpfSgB2dsYQ2mhH7XThhbcx39nI9/fMTGDAzVwsUu3 yBe7UcvclBfb6gk7dhLeqrWjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBRlwI0l9Qy6l3eQP54u4Fr1ztXh5DAKBggqhkjOPQQD AwNpADBmAjEApa7jRlP4mDbjIvouKEkN7jB+M/PsP3FezFWJeJmssv3cHFwzjim5 axfIEWi13IMHAjEAnMhE2mnCNsNUGRCFAtqdR+9B52wmnQk9922Q0QVEL7C8g5No 8gxFSTm/mQQc0xCg -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID9jCCAt6gAwIBAgIQJDJ18h0v0gkz97RqytDzmDANBgkqhkiG9w0BAQsFADCB lDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w HQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRl YyBDbGFzcyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5WjCBlDELMAkGA1UE BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZT eW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAx IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHOddJZKmZgiJM6kXZBxbje/SD 6Jlz+muxNuCad6BAwoGNAcfMjL2Pffd543pMA03Z+/2HOCgs3ZqLVAjbZ/sbjP4o ki++t7JIp4Gh2F6Iw8w5QEFa0dzl2hCfL9oBTf0uRnz5LicKaTfukaMbasxEvxvH w9QRslBglwm9LiL1QYRmn81ApqkAgMEflZKf3vNI79sdd2H8f9/ulqRy0LY+/3gn r8uSFWkI22MQ4uaXrG7crPaizh5HmbmJtxLmodTNWRFnw2+F2EJOKL5ZVVkElauP N4C/DfD8HzpkMViBeNfiNfYgPym4jxZuPkjctUwH4fIa6n4KedaovetdhitNAgMB AAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW BBQzQejIORIVk0jyljIuWvXalF9TYDANBgkqhkiG9w0BAQsFAAOCAQEAFeNzV7EX tl9JaUSm9l56Z6zS3nVJq/4lVcc6yUQVEG6/MWvL2QeTfxyFYwDjMhLgzMv7OWyP 4lPiPEAz2aSMR+atWPuJr+PehilWNCxFuBL6RIluLRQlKCQBZdbqUqwFblYSCT3Q dPTXvQbKqDqNVkL6jXI+dPEDct+HG14OelWWLDi3mIXNTTNEyZSPWjEwN0ujOhKz 5zbRIWhLLTjmU64cJVYIVgNnhJ3Gw84kYsdMNs+wBkS39V8C3dlU6S+QTnrIToNA DJqXPDe/v+z28LSFdyjBC8hnghAXOKK3Buqbvzr46SMHv3TgmDgVVXjucgBcGaP0 0jPg/73RVDkpDw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICqDCCAi2gAwIBAgIQNBdlEkA7t1aALYDLeVWmHjAKBggqhkjOPQQDAzCBlDEL MAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBD bGFzcyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0g RzQwHhcNMTExMDA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBlDELMAkGA1UEBhMC VVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1h bnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAyIFB1 YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAATR2UqOTA2ESlG6fO/TzPo6mrWnYxM9AeBJPvrBR8mS szrX/m+c95o6D/UOCgrDP8jnEhSO1dVtmCyzcTIK6yq99tdqIAtnRZzSsr9TImYJ XdsR8/EFM1ij4rjPfM2Cm72jQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBQ9MvM6qQyQhPmijGkGYVQvh3L+BTAKBggqhkjOPQQD AwNpADBmAjEAyKapr0F/tckRQhZoaUxcuCcYtpjxwH+QbYfTjEYX8D5P/OqwCMR6 S7wIL8fip29lAjEA1lnehs5fDspU1cbQFQ78i5Ry1I4AWFPPfrFLDeVQhuuea9// KabYR9mglhjb8kWz -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID9jCCAt6gAwIBAgIQZIKe/DcedF38l/+XyLH/QTANBgkqhkiG9w0BAQsFADCB lDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w HQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRl YyBDbGFzcyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5WjCBlDELMAkGA1UE BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZT eW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAy IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNzOkFyGOFyz9AYxe9GPo15gRn V2WYKaRPyVyPDzTS+NqoE2KquB5QZ3iwFkygOakVeq7t0qLA8JA3KRgmXOgNPLZs ST/B4NzZS7YUGQum05bh1gnjGSYc+R9lS/kaQxwAg9bQqkmi1NvmYji6UBRDbfkx +FYW2TgCkc/rbN27OU6Z4TBnRfHU8I3D3/7yOAchfQBeVkSz5GC9kSucq1sEcg+y KNlyqwUgQiWpWwNqIBDMMfAr2jUs0Pual07wgksr2F82owstr2MNHSV/oW5cYqGN KD6h/Bwg+AEvulWaEbAZ0shQeWsOagXXqgQ2sqPy4V93p3ec5R7c6d9qwWVdAgMB AAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW BBSHjCCVyJhK0daABkqQNETfHE2/sDANBgkqhkiG9w0BAQsFAAOCAQEAgY6ypWaW tyGltu9vI1pf24HFQqV4wWn99DzX+VxrcHIa/FqXTQCAiIiCisNxDY7FiZss7Y0L 0nJU9X3UXENX6fOupQIR9nYrgVfdfdp0MP1UR/bgFm6mtApI5ud1Bw8pGTnOefS2 bMVfmdUfS/rfbSw8DVSAcPCIC4DPxmiiuB1w2XaM/O6lyc+tHc+ZJVdaYkXLFmu9 Sc2lo4xpeSWuuExsi0BmSxY/zwIa3eFsawdhanYVKZl/G92IgMG/tY9zxaaWI4Sm KIYkM2oBLldzJbZev4/mHWGoQClnHYebHX+bn5nNMdZUvmK7OaxoEkiRIKXLsd3+ b/xa5IJVWa8xqQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICpzCCAi2gAwIBAgIQTHm1miicdjFk9YlE0JEC3jAKBggqhkjOPQQDAzCBlDEL MAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBD bGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0g RzQwHhcNMTIxMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5WjCBlDELMAkGA1UEBhMC VVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1h bnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAzIFB1 YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAARXz+qzOU0/oSHgbi84csaHl/OFC0fnD1HI0fSZm8pZ Zf9M+eoLtyXV0vbsMS0yYhLXdoan+jjJZdT+c+KEOfhMSWIT3brViKBfPchPsD+P oVAR5JNGrcNfy/GkapVW6MCjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBQknbzScfcdwiW+IvGJpSwVOzQeXjAKBggqhkjOPQQD AwNoADBlAjEAuWZoZdsF0Dh9DvPIdWG40CjEsUozUVj78jwQyK5HeHbKZiQXhj5Q Vm6lLZmIuL0kAjAD6qfnqDzqnWLGX1TamPR3vU+PGJyRXEdrQE0QHbPhicoLIsga xcX+i93B3294n5E= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF9jCCA96gAwIBAgIQZWNxhdNvRcaPfzH5CYeSgjANBgkqhkiG9w0BAQwFADCB lDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w HQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRl YyBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRzYwHhcNMTIxMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5WjCBlDELMAkGA1UE BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZT eW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAz IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC3DrL6TbyachX7d1vb/UMPywv3 YC6zK34Mu1PyzE5l8xm7/zUd99Opu0Attd141Kb5N+qFBXttt+YTSwZ8+3ZjjyAd LTgrBIXy6LDRX01KIclq2JTqHgJQpqqQB6BHIepm+QSg5oPwxPVeluInTWHDs8GM IrZmoQDRVin77cF/JMo9+lqUsITDx7pDHP1kDvEo+0dZ8ibhMblE+avd+76+LDfj rAsY0/wBovGkCjWCR0yrvYpe3xOF/CDMSFmvr0FvyyPNypOn3dVfyGQ7/wEDoApP LW49hL6vyDKyUymQFfewBZoKPPa5BpDJpeFdoDuw/qi2v/WJKFckOiGGceTciotB VeweMCRZ0cBZuHivqlp03iWAMJjtMERvIXAc2xJTDtamKGaTLB/MTzwbgcW59nhv 0DI6CHLbaw5GF4WU87zvvPekXo7p6bVk5bdLRRIsTDe3YEMKTXEGAJQmNXQfu3o5 XE475rgD4seTi4QsJUlF3X8jlGAfy+nN9quX92Hn+39igcjcCjBcGHzmzu/Hbh6H fLPpysh7avRo/IOlDFa0urKNSgrHl5fFiDAVPRAIVBVycmczM/R8t84AJ1NlziTx WmTnNi/yLgLCl99y6AIeoPc9tftoYAP6M6nmEm0G4amoXU48/tnnAGWsthlNe4N/ NEfq4RhtsYsceavnnQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUOXEIAD7eyIbnkP/k/SEPziQZFvYwDQYJKoZIhvcN AQEMBQADggIBAFBriE1gSM5a4yLOZ3yEp80c/ekMA4w2rwqHDmquV64B0Da78v25 c8FftaiuTKL6ScsHRhY2vePIVzh+OOS/JTNgxtw3nGO7XpgeGrKC8K6mdxGAREeh KcXwszrOmPC47NMOgAZ3IzBM/3lkYyJbd5NDS3Wz2ztuO0rd8ciutTeKlYg6EGhw OLlbcH7VQ8n8X0/l5ns27vAg7UdXEyYQXhQGDXt2B8LGLRb0rqdsD7yID08sAraj 1yLmmUc12I2lT4ESOhF9s8wLdfMecKMbA+r6mujmLjY5zJnOOj8Mt674Q5mwk25v qtkPajGRu5zTtCj7g0x6c4JQZ9IOrO1gxbJdNZjPh34eWR0kvFa62qRa2MzmvB4Q jxuMjvPB27e+1LBbZY8WaPNWxSoZFk0PuGWHbSSDuGLc4EdhGoh7zk5//dzGDVqa pPO1TPbdMaboHREhMzAEYX0c4D5PjT+1ixIAWn2poQDUg+twuxj4pNIcgS23CBHI Jnu21OUPA0Zy1CVAHr5JXW2T8VyyO3VUaTqg7kwiuqya4gitRWMFSlI1dsQ09V4H Mq3cfCbRW4+t5OaqG3Wf61206MCpFXxOSgdy30bJ1JGSdVaw4e43NmUoxRXIK3bM bW8Zg/T92hXiQeczeUaDV/nxpbZt07zXU+fucW14qZen7iCcGRVyFT0E -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDcTCCAlmgAwIBAgIVAOYJ/nrqAGiM4CS07SAbH+9StETRMA0GCSqGSIb3DQEB BQUAMFAxCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGlj emVuaW93YSBTLkEuMRcwFQYDVQQDDA5TWkFGSVIgUk9PVCBDQTAeFw0xMTEyMDYx MTEwNTdaFw0zMTEyMDYxMTEwNTdaMFAxCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRcwFQYDVQQDDA5TWkFGSVIg Uk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxHL49ZMTml 6g3wpYwrvQKkvc0Kc6oJ5sxfgmp1qZfluwbv88BdocHSiXlY8NzrVYzuWBp7J/9K ULMAoWoTIzOQ6C9TNm4YbA9A1jdX1wYNL5Akylf8W5L/I4BXhT9KnlI6x+a7BVAm nr/Ttl+utT/Asms2fRfEsF2vZPMxH4UFqOAhFjxTkmJWf2Cu4nvRQJHcttB+cEAo ag/hERt/+tzo4URz6x6r19toYmxx4FjjBkUhWQw1X21re//Hof2+0YgiwYT84zLb eqDqCOMOXxvH480yGDkh/QoazWX3U75HQExT/iJlwnu7I1V6HXztKIwCBjsxffbH 3jOshCJtywcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFFOSo33/gnbwM9TrkmdHYTMbaDsqMA0GCSqGSIb3DQEBBQUA A4IBAQA5UFWd5EL/pBviIMm1zD2JLUCpp0mJG7JkwznIOzawhGmFFaxGoxAhQBEg haP+E0KR66oAwVC6xe32QUVSHfWqWndzbODzLB8yj7WAR0cDM45ZngSBPBuFE3Wu GLJX9g100ETfIX+4YBR/4NR/uvTnpnd9ete7Whl0ZfY94yuu4xQqB5QFv+P7IXXV lTOjkjuGXEcyQAjQzbFaT9vIABSbeCXWBbjvOXukJy6WgAiclzGNSYprre8Ryydd fmjW9HIGwsIO03EldivvqEYL1Hv1w/Pur+6FUEOaL68PEIUovfgwIB2BAw+vZDuw cH0mX548PojGyg434cDjkSXa3mHF -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi 1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP BSeOE6Fuwg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN 8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ 1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT 91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ /jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs 81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG 9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx 0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA 2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu MdRAGmI0Nj81Aa6sY6A= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta 3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk 6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 /qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 jVaMaA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGHDCCBASgAwIBAgIES45gAzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJE SzESMBAGA1UEChMJVFJVU1QyNDA4MSIwIAYDVQQDExlUUlVTVDI0MDggT0NFUyBQ cmltYXJ5IENBMB4XDTEwMDMwMzEyNDEzNFoXDTM3MTIwMzEzMTEzNFowRTELMAkG A1UEBhMCREsxEjAQBgNVBAoTCVRSVVNUMjQwODEiMCAGA1UEAxMZVFJVU1QyNDA4 IE9DRVMgUHJpbWFyeSBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB AJlJodr3U1Fa+v8HnyACHV81/wLevLS0KUk58VIABl6Wfs3LLNoj5soVAZv4LBi5 gs7E8CZ9w0F2CopW8vzM8i5HLKE4eedPdnaFqHiBZ0q5aaaQArW+qKJx1rT/AaXt alMB63/yvJcYlXS2lpexk5H/zDBUXeEQyvfmK+slAySWT6wKxIPDwVapauFY9QaG +VBhCa5jBstWS7A5gQfEvYqn6csZ3jW472kW6OFNz6ftBcTwufomGJBMkonf4ZLr 6t0AdRi9jflBPz3MNNRGxyjIuAmFqGocYFA/OODBRjvSHB2DygqQ8k+9tlpvzMRr kU7jq3RKL+83G1dJ3/LTjCLz4ryEMIC/OJ/gNZfE0qXddpPtzflIPtUFVffXdbFV 1t6XZFhJ+wBHQCpJobq/BjqLWUA86upsDbfwnePtmIPRCemeXkY0qabC+2Qmd2Fe xyZphwTyMnbqy6FG1tB65dYf3mOqStmLa3RcHn9+2dwNfUkh0tjO2FXD7drWcU0O I9DW8oAypiPhm/QCjMU6j6t+0pzqJ/S0tdAo+BeiXK5hwk6aR+sRb608QfBbRAs3 U/q8jSPByenggac2BtTN6cl+AA1Mfcgl8iXWNFVGegzd/VS9vINClJCe3FNVoUnR YCKkj+x0fqxvBLopOkJkmuZw/yhgMxljUi2qYYGn90OzAgMBAAGjggESMIIBDjAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjARBgNVHSAECjAIMAYGBFUd IAAwgZcGA1UdHwSBjzCBjDAsoCqgKIYmaHR0cDovL2NybC5vY2VzLnRydXN0MjQw OC5jb20vb2Nlcy5jcmwwXKBaoFikVjBUMQswCQYDVQQGEwJESzESMBAGA1UEChMJ VFJVU1QyNDA4MSIwIAYDVQQDExlUUlVTVDI0MDggT0NFUyBQcmltYXJ5IENBMQ0w CwYDVQQDEwRDUkwxMB8GA1UdIwQYMBaAFPZt+LFIs0FDAduGROUYBbdezAY3MB0G A1UdDgQWBBT2bfixSLNBQwHbhkTlGAW3XswGNzANBgkqhkiG9w0BAQsFAAOCAgEA VPAQGrT7dIjD3/sIbQW86f9CBPu0c7JKN6oUoRUtKqgJ2KCdcB5ANhCoyznHpu3m /dUfVUI5hc31CaPgZyY37hch1q4/c9INcELGZVE/FWfehkH+acpdNr7j8UoRZlkN 15b/0UUBfGeiiJG/ugo4llfoPrp8bUmXEGggK3wyqIPcJatPtHwlb6ympfC2b/Ld v/0IdIOzIOm+A89Q0utx+1cOBq72OHy8gpGb6MfncVFMoL2fjP652Ypgtr8qN9Ka /XOazktiIf+2Pzp7hLi92hRc9QMYexrV/nnFSQoWdU8TqULFUoZ3zTEC3F/g2yj+ FhbrgXHGo5/A4O74X+lpbY2XV47aSuw+DzcPt/EhMj2of7SA55WSgbjPMbmNX0rb oenSIte2HRFW5Tr2W+qqkc/StixgkKdyzGLoFx/xeTWdJkZKwyjqge2wJqws2upY EiThhC497+/mTiSuXd69eVUwKyqYp9SD2rTtNmF6TCghRM/dNsJOl+osxDVGcwvt WIVFF/Onlu5fu1NHXdqNEfzldKDUvCfii3L2iATTZyHwU9CALE+2eIA+PIaLgnM1 1oCfUnYBkQurTrihvzz9PryCVkLxiqRmBVvUz+D4N5G/wvvKDS6t6cPCS+hqM482 cbBsn0R9fFLO4El62S9eH1tqOzO20OAOK65yJIsOpSE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA 0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN ZetX2fNXlrtIzYE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF 10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz 0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc 46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm 4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL 1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh 15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW 6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx 3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2 ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0 aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/ BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM 7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs yZyQ2uypQjyttgI= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG +7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M 733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5 mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFkjCCA3qgAwIBAgIBCDANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJDTjER MA8GA1UEChMIVW5pVHJ1c3QxGDAWBgNVBAMTD1VDQSBHbG9iYWwgUm9vdDAeFw0w ODAxMDEwMDAwMDBaFw0zNzEyMzEwMDAwMDBaMDoxCzAJBgNVBAYTAkNOMREwDwYD VQQKEwhVbmlUcnVzdDEYMBYGA1UEAxMPVUNBIEdsb2JhbCBSb290MIICIjANBgkq hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2rPlBlA/9nP3xDK/RqUlYjOHsGj+p9+I A2N9Apb964fJ7uIIu527u+RBj8cwiQ9tJMAEbBSUgU2gDXRm8/CFr/hkGd656YGT 0CiFmUdCSiw8OCdKzP/5bBnXtfPvm65bNAbXj6ITBpyKhELVs6OQaG2BkO5NhOxM cE4t3iQ5zhkAQ5N4+QiGHUPR9HK8BcBn+sBR0smFBySuOR56zUHSNqth6iur8CBV mTxtLRwuLnWW2HKX4AzKaXPudSsVCeCObbvaE/9GqOgADKwHLx25urnRoPeZnnRc GQVmMc8+KlL+b5/zub35wYH1N9ouTIElXfbZlJrTNYsgKDdfUet9Ysepk9H50DTL qScmLCiQkjtVY7cXDlRzq6987DqrcDOsIfsiJrOGrCOp139tywgg8q9A9f9ER3Hd J90TKKHqdjn5EKCgTUCkJ7JZFStsLSS3JGN490MYeg9NEePorIdCjedYcaSrbqLA l3y74xNLytu7awj5abQEctXDRrl36v+6++nwOgw19o8PrgaEFt2UVdTvyie3AzzF HCYq9TyopZWbhvGKiWf4xwxmse1Bv4KmAGg6IjTuHuvlb4l0T2qqaqhXZ1LUIGHB zlPL/SR/XybfoQhplqCe/klD4tPq2sTxiDEhbhzhzfN1DiBEFsx9c3Q1RSw7gdQg 7LYJjD5IskkCAwEAAaOBojCBnzALBgNVHQ8EBAMCAQYwDAYDVR0TBAUwAwEB/zBj BgNVHSUEXDBaBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcD BAYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEFBQcDBwYIKwYBBQUHAwgGCCsGAQUF BwMJMB0GA1UdDgQWBBTZw9P4gJJnzF3SOqLXcaK0xDiALTANBgkqhkiG9w0BAQUF AAOCAgEA0Ih5ygiq9ws0oE4Jwul+NUiJcIQjL1HDKy9e21NrW3UIKlS6Mg7VxnGF sZdJgPaE0PC6t3GUyHlrpsVE6EKirSUtVy/m1jEp+hmJVCl+t35HNmktbjK81HXa QnO4TuWDQHOyXd/URHOmYgvbqm4FjMh/Rk85hZCdvBtUKayl1/7lWFZXbSyZoUkh 1WHGjGHhdSTBAd0tGzbDLxLMC9Z4i3WA6UG5iLHKPKkWxk4V43I29tSgQYWvimVw TbVEEFDs7d9t5tnGwBLxSzovc+k8qe4bqi81pZufTcU0hF8mFGmzI7GJchT46U1R IgP/SobEHOh7eQrbRyWBfvw0hKxZuFhD5D1DCVR0wtD92e9uWfdyYJl2b/Unp7uD pEqB7CmB9HdL4UISVdSGKhK28FWbAS7d9qjjGcPORy/AeGEYWsdl/J1GW1fcfA67 loMQfFUYCQSu0feLKj6g5lDWMDbX54s4U+xJRODPpN/xU3uLWrb2EZBL1nXz/gLz Ka/wI3J9FO2pXd96gZ6bkiL8HvgBRUGXx2sBYb4zaPKgZYRmvOAqpGjTcezHCN6j w8k2SjTxF+KAryAhk5Qe5hXTVGLxtTgv48y5ZwSpuuXu+RBuyy5+E6+SFP7zJ3N7 OPxzbbm5iPZujAv1/P8JDrMtXnt145Ik4ubhWD5LKAN1axibRww= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDhDCCAmygAwIBAgIBCTANBgkqhkiG9w0BAQUFADAzMQswCQYDVQQGEwJDTjER MA8GA1UEChMIVW5pVHJ1c3QxETAPBgNVBAMTCFVDQSBSb290MB4XDTA0MDEwMTAw MDAwMFoXDTI5MTIzMTAwMDAwMFowMzELMAkGA1UEBhMCQ04xETAPBgNVBAoTCFVu aVRydXN0MREwDwYDVQQDEwhVQ0EgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBALNdB8qGJn1r4vs4CQ7MgsJqGgCiFV/W6dQBt1YDAVmP9ThpJHbC XivF9iu/r/tB/Q9a/KvXg3BNMJjRnrJ2u5LWu+kQKGkoNkTo8SzXWHwk1n8COvCB a2FgP/Qz3m3l6ihST/ypHWN8C7rqrsRoRuTej8GnsrZYWm0dLNmMOreIy4XU9+gD Xv2yTVDo1h//rgI/i0+WITyb1yXJHT/7mLFZ5PCpO6+zzYUs4mBGzG+OoOvwNMXx QhhgrhLtRnUc5dipllq+3lrWeGeWW5N3UPJuG96WUUqm1ktDdSFmjXfsAoR2XEQQ th1hbOSjIH23jboPkXXHjd+8AmCoKai9PUMCAwEAAaOBojCBnzALBgNVHQ8EBAMC AQYwDAYDVR0TBAUwAwEB/zBjBgNVHSUEXDBaBggrBgEFBQcDAQYIKwYBBQUHAwIG CCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEFBQcD BwYIKwYBBQUHAwgGCCsGAQUFBwMJMB0GA1UdDgQWBBTbHzXza0z/QjFkm827Wh4d SBC37jANBgkqhkiG9w0BAQUFAAOCAQEAOGy3iPGt+lg3dNHocN6cJ1nL5BXXoMNg 14iABMUwTD3UGusGXllH5rxmy+AI/Og17GJ9ysDawXiv5UZv+4mCI4/211NmVaDe JRI7cTYWVRJ2+z34VFsxugAG+H1V5ad2g6pcSpemKijfvcZsCyOVjjN/Hl5AHxNU LJzltQ7dFyiuawHTUin1Ih+QOfTcYmjwPIZH7LgFRbu3DJaUxmfLI3HQjnQi1kHr A6i26r7EARK1s11AdgYg1GS4KUYGis4fk5oQ7vuqWrTcL9Ury/bXBYSYBZELhPc9 +tb5evosFeo2gkO3t7jj83EB7UNDogVFwygFBzXjAaU4HoDU18PZ3g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B 3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT 79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs 8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK 4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv 2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 mfnGV/TJVTl4uix5yaaIK/QI -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9 hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM 1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws 6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50 aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u 7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0 xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn 0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM //bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t 3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3 dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo 5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+ pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU 4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5 81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4 nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO 8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2 6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4 wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4 E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u 7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te 2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC /Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC 4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y 5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ 4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF 9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN /BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz 4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 7M2CYfE45k+XmCpajQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h 2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq 299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd 7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw ++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt 398znM/jra6O1I7mT1GvFpLgXPYHDw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIID+TCCAuGgAwIBAgIQW1fXqEywr9nTb0ugMbTW4jANBgkqhkiG9w0BAQUFADB5 MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xKjAoBgNVBAMTIVZpc2EgSW5m b3JtYXRpb24gRGVsaXZlcnkgUm9vdCBDQTAeFw0wNTA2MjcxNzQyNDJaFw0yNTA2 MjkxNzQyNDJaMHkxCzAJBgNVBAYTAlVTMQ0wCwYDVQQKEwRWSVNBMS8wLQYDVQQL EyZWaXNhIEludGVybmF0aW9uYWwgU2VydmljZSBBc3NvY2lhdGlvbjEqMCgGA1UE AxMhVmlzYSBJbmZvcm1hdGlvbiBEZWxpdmVyeSBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyREA4R/QkkfpLx0cYjga/EhIPZpchH0MZsRZ FfP6C2ITtf/Wc+MtgD4yTK0yoiXvni3d+aCtEgK3GDvkdgYrgF76ROJFZwUQjQ9l x42gRT05DbXvWFoy7dTglCZ9z/Tt2Cnktv9oxKgmkeHY/CyfpCBg1S8xth2JlGMR 0ug/GMO5zANuegZOv438p5Lt5So+du2Gl+RMFQqEPwqN5uJSqAe0VtmB4gWdQ8on Bj2ZAM2R73QW7UW0Igt2vA4JaSiNtaAG/Y/58VXWHGgbq7rDtNK1R30X0kJV0rGA ib3RSwB3LpG7bOjbIucV5mQgJoVjoA1e05w6g1x/KmNTmOGRVwIDAQABo30wezAP BgNVHRMBAf8EBTADAQH/MDkGA1UdIAQyMDAwLgYFZ4EDAgEwJTAVBggrBgEFBQcC ARYJMS4yLjMuNC41MAwGCCsGAQUFBwICMAAwDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBRPitp2/2d3I5qmgH1924h1hfeBejANBgkqhkiG9w0BAQUFAAOCAQEACUW1 QdUHdDJydgDPmYt+telnG/Su+DPaf1cregzlN43bJaJosMP7NwjoJY/H2He4XLWb 5rXEkl+xH1UyUwF7mtaUoxbGxEvt8hPZSTB4da2mzXgwKvXuHyzF5Qjy1hOB0/pS WaF9ARpVKJJ7TOJQdGKBsF2Ty4fSCLqZLgfxbqwMsd9sysXI3rDXjIhekqvbgeLz PqZr+pfgFhwCCLSMQWl5Ll3u7Qk9wR094DZ6jj6+JCVCRUS3HyabH4OlM0Vc2K+j INsF/64Or7GNtRf9HYEJvrPxHINxl3JVwhYj4ASeaO4KwhVbwtw94Tc/XrGcexDo c5lC3rAi4/UZqweYCw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEGjCCAwKgAwIBAgIDAYagMA0GCSqGSIb3DQEBBQUAMIGjMQswCQYDVQQGEwJG STEQMA4GA1UECBMHRmlubGFuZDEhMB8GA1UEChMYVmFlc3RvcmVraXN0ZXJpa2Vz a3VzIENBMSkwJwYDVQQLEyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBTZXJ2aWNl czEZMBcGA1UECxMQVmFybWVubmVwYWx2ZWx1dDEZMBcGA1UEAxMQVlJLIEdvdi4g Um9vdCBDQTAeFw0wMjEyMTgxMzUzMDBaFw0yMzEyMTgxMzUxMDhaMIGjMQswCQYD VQQGEwJGSTEQMA4GA1UECBMHRmlubGFuZDEhMB8GA1UEChMYVmFlc3RvcmVraXN0 ZXJpa2Vza3VzIENBMSkwJwYDVQQLEyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBT ZXJ2aWNlczEZMBcGA1UECxMQVmFybWVubmVwYWx2ZWx1dDEZMBcGA1UEAxMQVlJL IEdvdi4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCF FdrIAzfQo0Y3bBseljDCWoUSZyPyu5/nioFgJ/gTqTy894aqqvTzJSm0/nWuHoGG igWyHWWyOOi0zCia+xc28ZPVec7Bg4shT8MNrUHfeJ1I4x9CRPw8bSEga60ihCRC jxdNwlAfZM0tOSJWiP2yY51U2kJpwMhP1xjiPshphJQ9LIDGfM6911Mf64i5psu7 hVfvV3ZdDIvTXhJBnyHAOfQmbQj6OLOhd7HuFtjQaNq0mKWgZUZKa41+qk1guPjI DfxxPu45h4G02fhukO4/DmHXHSto5i7hQkQmeCxY8n0Wf2HASSQqiYe2XS8pGfim 545SnkFLWg6quMJmQlMCAwEAAaNVMFMwDwYDVR0TAQH/BAUwAwEB/zARBglghkgB hvhCAQEEBAMCAAcwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBTb6eGb0tEkC/yr 46Bn6q6cS3f0sDANBgkqhkiG9w0BAQUFAAOCAQEArX1ID1QRnljurw2bEi8hpM2b uoRH5sklVSPj3xhYKizbXvfNVPVRJHtiZ+GxH0mvNNDrsczZog1Sf0JLiGCXzyVy t08pLWKfT6HAVVdWDsRol5EfnGTCKTIB6dTI2riBmCguGMcs/OubUpbf9MiQGS0j 8/G7cdqehSO9Gu8u5Hp5t8OdhkktY7ktdM9lDzJmid87Ie4pbzlj2RXBbvbfgD5Q eBmK3QOjFKU3p7UsfLYRh+cF8ry23tT/l4EohP7+bEaFEEGfTXWMB9SZZ291im/k UJL2mdUQuMSpe/cXjUu/15WfCdxEDx4yw8DP03kN5Mc7h/CQNIghYkmSBAQfvA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ O+7ETPTsJ3xCwnR8gooJybQDJbw= -----END CERTIFICATE----- ` google-certificate-transparency-go-2308f62/x509/root_darwin_test.go000066400000000000000000000105011462611535200252600ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto/rsa" "os" "os/exec" "path/filepath" "runtime" "testing" "time" ) func TestSystemRoots(t *testing.T) { switch runtime.GOARCH { case "arm", "arm64": t.Skipf("skipping on %s/%s, no system root", runtime.GOOS, runtime.GOARCH) } t0 := time.Now() sysRoots := systemRootsPool() // actual system roots sysRootsDuration := time.Since(t0) t1 := time.Now() execRoots, err := execSecurityRoots() // non-cgo roots execSysRootsDuration := time.Since(t1) if err != nil { t.Fatalf("failed to read system roots: %v", err) } t.Logf(" cgo sys roots: %v", sysRootsDuration) t.Logf("non-cgo sys roots: %v", execSysRootsDuration) // On Mavericks, there are 212 bundled certs, at least there was at // one point in time on one machine. (Maybe it was a corp laptop // with extra certs?) Other OS X users report 135, 142, 145... // Let's try requiring at least 100, since this is just a sanity // check. if want, have := 100, len(sysRoots.certs); have < want { t.Errorf("want at least %d system roots, have %d", want, have) } // Fetch any intermediate certificate that verify-cert might be aware of. out, err := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/Library/Keychains/System.keychain", filepath.Join(os.Getenv("HOME"), "/Library/Keychains/login.keychain"), filepath.Join(os.Getenv("HOME"), "/Library/Keychains/login.keychain-db")).Output() if err != nil { t.Fatal(err) } allCerts := NewCertPool() allCerts.AppendCertsFromPEM(out) // Check that the two cert pools are the same. sysPool := make(map[string]*Certificate, len(sysRoots.certs)) for _, c := range sysRoots.certs { sysPool[string(c.Raw)] = c } for _, c := range execRoots.certs { if _, ok := sysPool[string(c.Raw)]; ok { delete(sysPool, string(c.Raw)) } else { // verify-cert lets in certificates that are not trusted roots, but // are signed by trusted roots. This is not great, but unavoidable // until we parse real policies without cgo, so confirm that's the // case and skip them. if _, err := c.Verify(VerifyOptions{ Roots: sysRoots, Intermediates: allCerts, KeyUsages: []ExtKeyUsage{ExtKeyUsageAny}, CurrentTime: c.NotBefore, // verify-cert does not check expiration }); err != nil { t.Errorf("certificate only present in non-cgo pool: %v (verify error: %v)", c.Subject, err) } else { t.Logf("signed certificate only present in non-cgo pool (acceptable): %v", c.Subject) } } } for _, c := range sysPool { // The nocgo codepath uses verify-cert with the ssl policy, which also // happens to check EKUs, so some certificates will appear only in the // cgo pool. We can't easily make them consistent because the EKU check // is only applied to the certificates passed to verify-cert. var ekuOk bool for _, eku := range c.ExtKeyUsage { if eku == ExtKeyUsageServerAuth || eku == ExtKeyUsageNetscapeServerGatedCrypto || eku == ExtKeyUsageMicrosoftServerGatedCrypto || eku == ExtKeyUsageAny { ekuOk = true } } if len(c.ExtKeyUsage) == 0 && len(c.UnknownExtKeyUsage) == 0 { ekuOk = true } if !ekuOk { t.Logf("off-EKU certificate only present in cgo pool (acceptable): %v", c.Subject) continue } // Same for expired certificates. We don't chain to them anyway. now := time.Now() if now.Before(c.NotBefore) || now.After(c.NotAfter) { t.Logf("expired certificate only present in cgo pool (acceptable): %v", c.Subject) continue } // On 10.11 there are five unexplained roots that only show up from the // C API. They have in common the fact that they are old, 1024-bit // certificates. It's arguably better to ignore them anyway. if key, ok := c.PublicKey.(*rsa.PublicKey); ok && key.N.BitLen() == 1024 { t.Logf("1024-bit certificate only present in cgo pool (acceptable): %v", c.Subject) continue } t.Errorf("certificate only present in cgo pool: %v", c.Subject) } if t.Failed() && debugDarwinRoots { cmd := exec.Command("security", "dump-trust-settings") cmd.Stdout, cmd.Stderr = os.Stderr, os.Stderr cmd.Run() cmd = exec.Command("security", "dump-trust-settings", "-d") cmd.Stdout, cmd.Stderr = os.Stderr, os.Stderr cmd.Run() } } google-certificate-transparency-go-2308f62/x509/root_js.go000066400000000000000000000007461462611535200233630ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build js && wasm // +build js,wasm package x509 // Possible certificate files; stop after finding one. var certFiles = []string{} func loadSystemRoots() (*CertPool, error) { return NewCertPool(), nil } func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { return nil, nil } google-certificate-transparency-go-2308f62/x509/root_linux.go000066400000000000000000000012541462611535200241010ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 // Possible certificate files; stop after finding one. var certFiles = []string{ "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 "/etc/ssl/ca-bundle.pem", // OpenSUSE "/etc/pki/tls/cacert.pem", // OpenELEC "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 "/etc/ssl/cert.pem", // Alpine Linux } google-certificate-transparency-go-2308f62/x509/root_nocgo_darwin.go000066400000000000000000000004301462611535200254060ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !cgo // +build !cgo package x509 func loadSystemRoots() (*CertPool, error) { return execSecurityRoots() } google-certificate-transparency-go-2308f62/x509/root_plan9.go000066400000000000000000000015141462611535200237640ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build plan9 // +build plan9 package x509 import ( "os" ) // Possible certificate files; stop after finding one. var certFiles = []string{ "/sys/lib/tls/ca.pem", } func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { return nil, nil } func loadSystemRoots() (*CertPool, error) { roots := NewCertPool() var bestErr error for _, file := range certFiles { data, err := os.ReadFile(file) if err == nil { roots.AppendCertsFromPEM(data) return roots, nil } if bestErr == nil || (os.IsNotExist(bestErr) && !os.IsNotExist(err)) { bestErr = err } } if bestErr == nil { return roots, nil } return nil, bestErr } google-certificate-transparency-go-2308f62/x509/root_solaris.go000066400000000000000000000006431462611535200244170ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 // Possible certificate files; stop after finding one. var certFiles = []string{ "/etc/certs/ca-certificates.crt", // Solaris 11.2+ "/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS "/etc/ssl/cacert.pem", // OmniOS } google-certificate-transparency-go-2308f62/x509/root_unix.go000066400000000000000000000043361462611535200237310ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build aix || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos // +build aix dragonfly freebsd linux netbsd openbsd solaris zos package x509 import ( "os" ) // Possible directories with certificate files; stop after successfully // reading at least one file from a directory. var certDirectories = []string{ "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 "/system/etc/security/cacerts", // Android "/usr/local/share/certs", // FreeBSD "/etc/pki/tls/certs", // Fedora/RHEL "/etc/openssl/certs", // NetBSD "/var/ssl/certs", // AIX } const ( // certFileEnv is the environment variable which identifies where to locate // the SSL certificate file. If set this overrides the system default. certFileEnv = "SSL_CERT_FILE" // certDirEnv is the environment variable which identifies which directory // to check for SSL certificate files. If set this overrides the system default. certDirEnv = "SSL_CERT_DIR" ) func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { return nil, nil } func loadSystemRoots() (*CertPool, error) { roots := NewCertPool() files := certFiles if f := os.Getenv(certFileEnv); f != "" { files = []string{f} } var firstErr error for _, file := range files { data, err := os.ReadFile(file) if err == nil { roots.AppendCertsFromPEM(data) break } if firstErr == nil && !os.IsNotExist(err) { firstErr = err } } dirs := certDirectories if d := os.Getenv(certDirEnv); d != "" { dirs = []string{d} } for _, directory := range dirs { fis, err := os.ReadDir(directory) if err != nil { if firstErr == nil && !os.IsNotExist(err) { firstErr = err } continue } rootsAdded := false for _, fi := range fis { data, err := os.ReadFile(directory + "/" + fi.Name()) if err == nil && roots.AppendCertsFromPEM(data) { rootsAdded = true } } if rootsAdded { return roots, nil } } if len(roots.certs) > 0 || firstErr == nil { return roots, nil } return nil, firstErr } google-certificate-transparency-go-2308f62/x509/root_unix_test.go000066400000000000000000000061471462611535200247720ustar00rootroot00000000000000// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos // +build dragonfly freebsd linux netbsd openbsd solaris zos package x509 import ( "fmt" "os" "testing" ) const ( testDir = "testdata" testDirCN = "test-dir" testFile = "test-file.crt" testFileCN = "test-file" testMissing = "missing" ) func TestEnvVars(t *testing.T) { testCases := []struct { name string fileEnv string dirEnv string files []string dirs []string cns []string }{ { // Environment variables override the default locations preventing fall through. name: "override-defaults", fileEnv: testMissing, dirEnv: testMissing, files: []string{testFile}, dirs: []string{testDir}, cns: nil, }, { // File environment overrides default file locations. name: "file", fileEnv: testFile, dirEnv: "", files: nil, dirs: nil, cns: []string{testFileCN}, }, { // Directory environment overrides default directory locations. name: "dir", fileEnv: "", dirEnv: testDir, files: nil, dirs: nil, cns: []string{testDirCN}, }, { // File & directory environment overrides both default locations. name: "file+dir", fileEnv: testFile, dirEnv: testDir, files: nil, dirs: nil, cns: []string{testFileCN, testDirCN}, }, { // Environment variable empty / unset uses default locations. name: "empty-fall-through", fileEnv: "", dirEnv: "", files: []string{testFile}, dirs: []string{testDir}, cns: []string{testFileCN, testDirCN}, }, } // Save old settings so we can restore before the test ends. origCertFiles, origCertDirectories := certFiles, certDirectories origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv) defer func() { certFiles = origCertFiles certDirectories = origCertDirectories os.Setenv(certFileEnv, origFile) os.Setenv(certDirEnv, origDir) }() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if err := os.Setenv(certFileEnv, tc.fileEnv); err != nil { t.Fatalf("setenv %q failed: %v", certFileEnv, err) } if err := os.Setenv(certDirEnv, tc.dirEnv); err != nil { t.Fatalf("setenv %q failed: %v", certDirEnv, err) } certFiles, certDirectories = tc.files, tc.dirs r, err := loadSystemRoots() if err != nil { t.Fatal("unexpected failure:", err) } if r == nil { t.Fatal("nil roots") } // Verify that the returned certs match, otherwise report where the mismatch is. for i, cn := range tc.cns { if i >= len(r.certs) { t.Errorf("missing cert %v @ %v", cn, i) } else if r.certs[i].Subject.CommonName != cn { fmt.Printf("%#v\n", r.certs[0].Subject) t.Errorf("unexpected cert common name %q, want %q", r.certs[i].Subject.CommonName, cn) } } if len(r.certs) > len(tc.cns) { t.Errorf("got %v certs, which is more than %v wanted", len(r.certs), len(tc.cns)) } }) } } google-certificate-transparency-go-2308f62/x509/root_wasip1.go000066400000000000000000000007411462611535200241460ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build wasip1 // +build wasip1 package x509 // Possible certificate files; stop after finding one. var certFiles = []string{} func loadSystemRoots() (*CertPool, error) { return NewCertPool(), nil } func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { return nil, nil } google-certificate-transparency-go-2308f62/x509/root_windows.go000066400000000000000000000225321462611535200244360ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "errors" "syscall" "unsafe" ) // Creates a new *syscall.CertContext representing the leaf certificate in an in-memory // certificate store containing itself and all of the intermediate certificates specified // in the opts.Intermediates CertPool. // // A pointer to the in-memory store is available in the returned CertContext's Store field. // The store is automatically freed when the CertContext is freed using // syscall.CertFreeCertificateContext. func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertContext, error) { var storeCtx *syscall.CertContext leafCtx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &leaf.Raw[0], uint32(len(leaf.Raw))) if err != nil { return nil, err } defer syscall.CertFreeCertificateContext(leafCtx) handle, err := syscall.CertOpenStore(syscall.CERT_STORE_PROV_MEMORY, 0, 0, syscall.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, 0) if err != nil { return nil, err } defer syscall.CertCloseStore(handle, 0) err = syscall.CertAddCertificateContextToStore(handle, leafCtx, syscall.CERT_STORE_ADD_ALWAYS, &storeCtx) if err != nil { return nil, err } if opts.Intermediates != nil { for _, intermediate := range opts.Intermediates.certs { ctx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &intermediate.Raw[0], uint32(len(intermediate.Raw))) if err != nil { return nil, err } err = syscall.CertAddCertificateContextToStore(handle, ctx, syscall.CERT_STORE_ADD_ALWAYS, nil) syscall.CertFreeCertificateContext(ctx) if err != nil { return nil, err } } } return storeCtx, nil } // extractSimpleChain extracts the final certificate chain from a CertSimpleChain. func extractSimpleChain(simpleChain **syscall.CertSimpleChain, count int) (chain []*Certificate, err error) { if simpleChain == nil || count == 0 { return nil, errors.New("x509: invalid simple chain") } simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(simpleChain))[:count:count] lastChain := simpleChains[count-1] elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:lastChain.NumElements:lastChain.NumElements] for i := 0; i < int(lastChain.NumElements); i++ { // Copy the buf, since ParseCertificate does not create its own copy. cert := elements[i].CertContext encodedCert := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length:cert.Length] buf := make([]byte, cert.Length) copy(buf, encodedCert) parsedCert, err := ParseCertificate(buf) if err != nil { return nil, err } chain = append(chain, parsedCert) } return chain, nil } // checkChainTrustStatus checks the trust status of the certificate chain, translating // any errors it finds into Go errors in the process. func checkChainTrustStatus(c *Certificate, chainCtx *syscall.CertChainContext) error { if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR { status := chainCtx.TrustStatus.ErrorStatus switch status { case syscall.CERT_TRUST_IS_NOT_TIME_VALID: return CertificateInvalidError{c, Expired, ""} default: return UnknownAuthorityError{c, nil, nil} } } return nil } // checkChainSSLServerPolicy checks that the certificate chain in chainCtx is valid for // use as a certificate chain for a SSL/TLS server. func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContext, opts *VerifyOptions) error { servernamep, err := syscall.UTF16PtrFromString(opts.DNSName) if err != nil { return err } sslPara := &syscall.SSLExtraCertChainPolicyPara{ AuthType: syscall.AUTHTYPE_SERVER, ServerName: servernamep, } sslPara.Size = uint32(unsafe.Sizeof(*sslPara)) para := &syscall.CertChainPolicyPara{ ExtraPolicyPara: convertToPolicyParaType(unsafe.Pointer(sslPara)), } para.Size = uint32(unsafe.Sizeof(*para)) status := syscall.CertChainPolicyStatus{} err = syscall.CertVerifyCertificateChainPolicy(syscall.CERT_CHAIN_POLICY_SSL, chainCtx, para, &status) if err != nil { return err } // TODO(mkrautz): use the lChainIndex and lElementIndex fields // of the CertChainPolicyStatus to provide proper context, instead // using c. if status.Error != 0 { switch status.Error { case syscall.CERT_E_EXPIRED: return CertificateInvalidError{c, Expired, ""} case syscall.CERT_E_CN_NO_MATCH: return HostnameError{c, opts.DNSName} case syscall.CERT_E_UNTRUSTEDROOT: return UnknownAuthorityError{c, nil, nil} default: return UnknownAuthorityError{c, nil, nil} } } return nil } // systemVerify is like Verify, except that it uses CryptoAPI calls // to build certificate chains and verify them. func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { hasDNSName := opts != nil && len(opts.DNSName) > 0 storeCtx, err := createStoreContext(c, opts) if err != nil { return nil, err } defer syscall.CertFreeCertificateContext(storeCtx) para := new(syscall.CertChainPara) para.Size = uint32(unsafe.Sizeof(*para)) // If there's a DNSName set in opts, assume we're verifying // a certificate from a TLS server. if hasDNSName { oids := []*byte{ &syscall.OID_PKIX_KP_SERVER_AUTH[0], // Both IE and Chrome allow certificates with // Server Gated Crypto as well. Some certificates // in the wild require them. &syscall.OID_SERVER_GATED_CRYPTO[0], &syscall.OID_SGC_NETSCAPE[0], } para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_OR para.RequestedUsage.Usage.Length = uint32(len(oids)) para.RequestedUsage.Usage.UsageIdentifiers = &oids[0] } else { para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND para.RequestedUsage.Usage.Length = 0 para.RequestedUsage.Usage.UsageIdentifiers = nil } var verifyTime *syscall.Filetime if opts != nil && !opts.CurrentTime.IsZero() { ft := syscall.NsecToFiletime(opts.CurrentTime.UnixNano()) verifyTime = &ft } // CertGetCertificateChain will traverse Windows's root stores // in an attempt to build a verified certificate chain. Once // it has found a verified chain, it stops. MSDN docs on // CERT_CHAIN_CONTEXT: // // When a CERT_CHAIN_CONTEXT is built, the first simple chain // begins with an end certificate and ends with a self-signed // certificate. If that self-signed certificate is not a root // or otherwise trusted certificate, an attempt is made to // build a new chain. CTLs are used to create the new chain // beginning with the self-signed certificate from the original // chain as the end certificate of the new chain. This process // continues building additional simple chains until the first // self-signed certificate is a trusted certificate or until // an additional simple chain cannot be built. // // The result is that we'll only get a single trusted chain to // return to our caller. var chainCtx *syscall.CertChainContext err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, 0, 0, &chainCtx) if err != nil { return nil, err } defer syscall.CertFreeCertificateChain(chainCtx) err = checkChainTrustStatus(c, chainCtx) if err != nil { return nil, err } if hasDNSName { err = checkChainSSLServerPolicy(c, chainCtx, opts) if err != nil { return nil, err } } chain, err := extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount)) if err != nil { return nil, err } if len(chain) < 1 { return nil, errors.New("x509: internal error: system verifier returned an empty chain") } // Mitigate CVE-2020-0601, where the Windows system verifier might be // tricked into using custom curve parameters for a trusted root, by // double-checking all ECDSA signatures. If the system was tricked into // using spoofed parameters, the signature will be invalid for the correct // ones we parsed. (We don't support custom curves ourselves.) for i, parent := range chain[1:] { if parent.PublicKeyAlgorithm != ECDSA { continue } if err := parent.CheckSignature(chain[i].SignatureAlgorithm, chain[i].RawTBSCertificate, chain[i].Signature); err != nil { return nil, err } } return [][]*Certificate{chain}, nil } func loadSystemRoots() (*CertPool, error) { // TODO: restore this functionality on Windows. We tried to do // it in Go 1.8 but had to revert it. See Issue 18609. // Returning (nil, nil) was the old behavior, prior to CL 30578. // The if statement here avoids vet complaining about // unreachable code below. if true { return nil, nil } const CRYPT_E_NOT_FOUND = 0x80092004 store, err := syscall.CertOpenSystemStore(0, syscall.StringToUTF16Ptr("ROOT")) if err != nil { return nil, err } defer syscall.CertCloseStore(store, 0) roots := NewCertPool() var cert *syscall.CertContext for { cert, err = syscall.CertEnumCertificatesInStore(store, cert) if err != nil { if errno, ok := err.(syscall.Errno); ok { if errno == CRYPT_E_NOT_FOUND { break } } return nil, err } if cert == nil { break } // Copy the buf, since ParseCertificate does not create its own copy. buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length:cert.Length] buf2 := make([]byte, cert.Length) copy(buf2, buf) if c, err := ParseCertificate(buf2); err == nil { roots.AddCert(c) } } return roots, nil } google-certificate-transparency-go-2308f62/x509/root_zos.go000066400000000000000000000005061462611535200235540ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build zos // +build zos package x509 // Possible certificate files; stop after finding one. var certFiles = []string{ "/etc/cacert.pem", // IBM zOS default } google-certificate-transparency-go-2308f62/x509/rpki.go000066400000000000000000000206111462611535200226420ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "encoding/binary" "errors" "fmt" "github.com/google/certificate-transparency-go/asn1" ) // IPAddressPrefix describes an IP address prefix as an ASN.1 bit string, // where the BitLength field holds the prefix length. type IPAddressPrefix asn1.BitString // IPAddressRange describes an (inclusive) IP address range. type IPAddressRange struct { Min IPAddressPrefix Max IPAddressPrefix } // Most relevant values for AFI from: // http://www.iana.org/assignments/address-family-numbers. const ( IPv4AddressFamilyIndicator = uint16(1) IPv6AddressFamilyIndicator = uint16(2) ) // IPAddressFamilyBlocks describes a set of ranges of IP addresses. type IPAddressFamilyBlocks struct { // AFI holds an address family indicator from // http://www.iana.org/assignments/address-family-numbers. AFI uint16 // SAFI holds a subsequent address family indicator from // http://www.iana.org/assignments/safi-namespace. SAFI byte // InheritFromIssuer indicates that the set of addresses should // be taken from the issuer's certificate. InheritFromIssuer bool // AddressPrefixes holds prefixes if InheritFromIssuer is false. AddressPrefixes []IPAddressPrefix // AddressRanges holds ranges if InheritFromIssuer is false. AddressRanges []IPAddressRange } // Internal types for asn1 unmarshalling. type ipAddressFamily struct { AddressFamily []byte // 2-byte AFI plus optional 1 byte SAFI Choice asn1.RawValue } // Internally, use raw asn1.BitString rather than the IPAddressPrefix // type alias (so that asn1.Unmarshal() decodes properly). type ipAddressRange struct { Min asn1.BitString Max asn1.BitString } func parseRPKIAddrBlocks(data []byte, nfe *NonFatalErrors) []*IPAddressFamilyBlocks { // RFC 3779 2.2.3 // IPAddrBlocks ::= SEQUENCE OF IPAddressFamily // // IPAddressFamily ::= SEQUENCE { -- AFI & optional SAFI -- // addressFamily OCTET STRING (SIZE (2..3)), // ipAddressChoice IPAddressChoice } // // IPAddressChoice ::= CHOICE { // inherit NULL, -- inherit from issuer -- // addressesOrRanges SEQUENCE OF IPAddressOrRange } // // IPAddressOrRange ::= CHOICE { // addressPrefix IPAddress, // addressRange IPAddressRange } // // IPAddressRange ::= SEQUENCE { // min IPAddress, // max IPAddress } // // IPAddress ::= BIT STRING var addrBlocks []ipAddressFamily if rest, err := asn1.Unmarshal(data, &addrBlocks); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ipAddrBlocks extension: %v", err)) return nil } else if len(rest) != 0 { nfe.AddError(errors.New("trailing data after ipAddrBlocks extension")) return nil } var results []*IPAddressFamilyBlocks for i, block := range addrBlocks { var fam IPAddressFamilyBlocks if l := len(block.AddressFamily); l < 2 || l > 3 { nfe.AddError(fmt.Errorf("invalid address family length (%d) for ipAddrBlock.addressFamily", l)) continue } fam.AFI = binary.BigEndian.Uint16(block.AddressFamily[0:2]) if len(block.AddressFamily) > 2 { fam.SAFI = block.AddressFamily[2] } // IPAddressChoice is an ASN.1 CHOICE where the chosen alternative is indicated by (implicit) // tagging of the alternatives -- here, either NULL or SEQUENCE OF. if bytes.Equal(block.Choice.FullBytes, asn1.NullBytes) { fam.InheritFromIssuer = true results = append(results, &fam) continue } var addrRanges []asn1.RawValue if _, err := asn1.Unmarshal(block.Choice.FullBytes, &addrRanges); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ipAddrBlocks[%d].ipAddressChoice.addressesOrRanges: %v", i, err)) continue } for j, ar := range addrRanges { // Each IPAddressOrRange is a CHOICE where the alternatives have distinct (implicit) // tags -- here, either BIT STRING or SEQUENCE. switch ar.Tag { case asn1.TagBitString: // BIT STRING for single prefix IPAddress var val asn1.BitString if _, err := asn1.Unmarshal(ar.FullBytes, &val); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ipAddrBlocks[%d].ipAddressChoice.addressesOrRanges[%d].addressPrefix: %v", i, j, err)) continue } fam.AddressPrefixes = append(fam.AddressPrefixes, IPAddressPrefix(val)) case asn1.TagSequence: var val ipAddressRange if _, err := asn1.Unmarshal(ar.FullBytes, &val); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ipAddrBlocks[%d].ipAddressChoice.addressesOrRanges[%d].addressRange: %v", i, j, err)) continue } fam.AddressRanges = append(fam.AddressRanges, IPAddressRange{Min: IPAddressPrefix(val.Min), Max: IPAddressPrefix(val.Max)}) default: nfe.AddError(fmt.Errorf("unexpected ASN.1 type in ipAddrBlocks[%d].ipAddressChoice.addressesOrRanges[%d]: %+v", i, j, ar)) } } results = append(results, &fam) } return results } // ASIDRange describes an inclusive range of AS Identifiers (AS numbers or routing // domain identifiers). type ASIDRange struct { Min int Max int } // ASIdentifiers describes a collection of AS Identifiers (AS numbers or routing // domain identifiers). type ASIdentifiers struct { // InheritFromIssuer indicates that the set of AS identifiers should // be taken from the issuer's certificate. InheritFromIssuer bool // ASIDs holds AS identifiers if InheritFromIssuer is false. ASIDs []int // ASIDs holds AS identifier ranges (inclusive) if InheritFromIssuer is false. ASIDRanges []ASIDRange } type asIdentifiers struct { ASNum asn1.RawValue `asn1:"optional,tag:0"` RDI asn1.RawValue `asn1:"optional,tag:1"` } func parseASIDChoice(val asn1.RawValue, nfe *NonFatalErrors) *ASIdentifiers { // RFC 3779 2.3.2 // ASIdentifierChoice ::= CHOICE { // inherit NULL, -- inherit from issuer -- // asIdsOrRanges SEQUENCE OF ASIdOrRange } // ASIdOrRange ::= CHOICE { // id ASId, // range ASRange } // ASRange ::= SEQUENCE { // min ASId, // max ASId } // ASId ::= INTEGER if len(val.FullBytes) == 0 { // OPTIONAL return nil } // ASIdentifierChoice is an ASN.1 CHOICE where the chosen alternative is indicated by (implicit) // tagging of the alternatives -- here, either NULL or SEQUENCE OF. if bytes.Equal(val.Bytes, asn1.NullBytes) { return &ASIdentifiers{InheritFromIssuer: true} } var ids []asn1.RawValue if rest, err := asn1.Unmarshal(val.Bytes, &ids); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges: %v", err)) return nil } else if len(rest) != 0 { nfe.AddError(errors.New("trailing data after ASIdentifiers.asIdsOrRanges")) return nil } var asID ASIdentifiers for i, id := range ids { // Each ASIdOrRange is a CHOICE where the alternatives have distinct (implicit) // tags -- here, either INTEGER or SEQUENCE. switch id.Tag { case asn1.TagInteger: var val int if _, err := asn1.Unmarshal(id.FullBytes, &val); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges[%d].id: %v", i, err)) continue } asID.ASIDs = append(asID.ASIDs, val) case asn1.TagSequence: var val ASIDRange if _, err := asn1.Unmarshal(id.FullBytes, &val); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges[%d].range: %v", i, err)) continue } asID.ASIDRanges = append(asID.ASIDRanges, val) default: nfe.AddError(fmt.Errorf("unexpected value in ASIdentifiers.asIdsOrRanges[%d]: %+v", i, id)) } } return &asID } func parseRPKIASIdentifiers(data []byte, nfe *NonFatalErrors) (*ASIdentifiers, *ASIdentifiers) { // RFC 3779 2.3.2 // ASIdentifiers ::= SEQUENCE { // asnum [0] EXPLICIT ASIdentifierChoice OPTIONAL, // rdi [1] EXPLICIT ASIdentifierChoice OPTIONAL} var asIDs asIdentifiers if rest, err := asn1.Unmarshal(data, &asIDs); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal ASIdentifiers extension: %v", err)) return nil, nil } else if len(rest) != 0 { nfe.AddError(errors.New("trailing data after ASIdentifiers extension")) return nil, nil } return parseASIDChoice(asIDs.ASNum, nfe), parseASIDChoice(asIDs.RDI, nfe) } google-certificate-transparency-go-2308f62/x509/rpki_test.go000066400000000000000000000306221462611535200237040ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "reflect" "strings" "testing" ) func TestParseRPKIAddrBlocks(t *testing.T) { tests := []struct { desc string in string // hex-encoded want []*IPAddressFamilyBlocks wantErr string }{ { desc: "ValidSingleIPv4", in: "300e" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING want: []*IPAddressFamilyBlocks{ { AFI: 1, AddressPrefixes: []IPAddressPrefix{ {Bytes: fromHex("d596c8"), BitLength: 21}, }, }, }, }, { desc: "ValidSingleIPv4WithSAFI", in: "300f" + // SEQUENCE OF IPAddressFamily ("300d" + // IPAddressFamily SEQUENCE ("0403" + "000120") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING want: []*IPAddressFamilyBlocks{ { AFI: 1, SAFI: 0x20, AddressPrefixes: []IPAddressPrefix{ {Bytes: fromHex("d596c8"), BitLength: 21}, }, }, }, }, { desc: "ValidSingleIPv4Inherit", in: "3008" + // SEQUENCE OF IPAddressFamily ("3006" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("0500")), // inherit NULL want: []*IPAddressFamilyBlocks{ { AFI: 1, InheritFromIssuer: true, }, }, }, { desc: "ValidMultipleIPv4", in: "3014" + // SEQUENCE OF IPAddressFamily ("3012" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("300c" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8" + // addressPrefix BIT STRING "0304" + "03" + "d696c8")), // addressPrefix BIT STRING want: []*IPAddressFamilyBlocks{ { AFI: 1, AddressPrefixes: []IPAddressPrefix{ {Bytes: fromHex("d596c8"), BitLength: 21}, {Bytes: fromHex("d696c8"), BitLength: 21}, }, }, }, }, { desc: "ValidSingleIPv4Range", in: "3016" + // SEQUENCE OF IPAddressFamily ("3014" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("300e" + // addressesOrRanges SEQUENCE OF IPAddressOrRange ("300c" + // addressRange SEQUENCE "0304" + "03" + "d596c8" + // min BIT STRING "0304" + "03" + "d596d8"))), // max BIT STRING want: []*IPAddressFamilyBlocks{ { AFI: 1, AddressRanges: []IPAddressRange{ { Min: IPAddressPrefix{Bytes: fromHex("d596c8"), BitLength: 21}, Max: IPAddressPrefix{Bytes: fromHex("d596d8"), BitLength: 21}, }, }, }, }, }, { desc: "InvalidOuterSequence", in: "310e" + // SET OF not SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "failed to asn1.Unmarshal ipAddrBlocks ", }, { desc: "TrailingOuterSequence", in: "300e" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8") + // addressPrefix BIT STRING "ff"), // trailing byte wantErr: "trailing data after ipAddrBlocks ", }, { desc: "InvalidInnerSequence", in: "300e" + // SEQUENCE OF IPAddressFamily ("310c" + // SET not SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "failed to asn1.Unmarshal ipAddrBlocks ", }, { desc: "TrailingInnerSequence", in: "300f" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")) + // addressPrefix BIT STRING "ff", // trailing byte wantErr: "failed to asn1.Unmarshal ipAddrBlocks ", }, { desc: "InvalidAddressFamily", in: "300e" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0302" + "0001") + // BIT STRING not OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "failed to asn1.Unmarshal ipAddrBlocks ", }, { desc: "InvalidAddressFamilyTooShort", in: "300d" + // SEQUENCE OF IPAddressFamily ("300b" + // IPAddressFamily SEQUENCE ("0401" + "01") + // addressFamily OCTET STRING too short ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "invalid address family length (1)", }, { desc: "InvalidAddressFamilyTooLong", in: "3010" + // SEQUENCE OF IPAddressFamily ("300e" + // IPAddressFamily SEQUENCE ("0404" + "00010a0b") + // addressFamily OCTET STRING too long ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "invalid address family length (4)", }, { desc: "InvalidChoiceSequence", in: "300e" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3106" + // SET not SEQUENCE "0304" + "03" + "d596c8")), // addressPrefix BIT STRING wantErr: "failed to asn1.Unmarshal ipAddrBlocks[0].ipAddressChoice", }, { desc: "InvalidIPv4Prefix", in: "300e" + // SEQUENCE OF IPAddressFamily ("300c" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("3006" + // addressesOrRanges SEQUENCE OF IPAddressOrRange "8304" + "03d596c8")), // addressPrefix BIT STRING, Not class=Universal want: []*IPAddressFamilyBlocks{{AFI: 1}}, wantErr: "failed to asn1.Unmarshal ipAddrBlocks[0].ipAddressChoice.addressesOrRanges[0].addressPrefix", }, { desc: "InvalidIPv4Range", in: "3016" + // SEQUENCE OF IPAddressFamily ("3014" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("300e" + // addressesOrRanges SEQUENCE OF IPAddressOrRange ("310c" + // SET not SEQUENCE "0304" + "03" + "d596c8" + // addressPrefix BIT STRING "0304" + "03" + "d596d8"))), // addressPrefix BIT STRING want: []*IPAddressFamilyBlocks{{AFI: 1}}, wantErr: "unexpected ASN.1 type in ipAddrBlocks[0].ipAddressChoice.addressesOrRanges[0]", }, { desc: "InvalidIPv4RangeContents", in: "3016" + // SEQUENCE OF IPAddressFamily ("3014" + // IPAddressFamily SEQUENCE ("0402" + "0001") + // addressFamily OCTET STRING ("300e" + // addressesOrRanges SEQUENCE OF IPAddressOrRange ("300c" + "0404" + "03d596c8" + // OCTET STRING not BIT STRING "0304" + "03" + "d596d8"))), // addressPrefix BIT STRING want: []*IPAddressFamilyBlocks{{AFI: 1}}, wantErr: "failed to asn1.Unmarshal ipAddrBlocks[0].ipAddressChoice.addressesOrRanges[0].addressRange", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { var nfe NonFatalErrors got := parseRPKIAddrBlocks(fromHex(test.in), &nfe) if !reflect.DeepEqual(got, test.want) { t.Errorf("parseRPKIAddrBlocks(%s)=%+v,%v; want %+v,_", test.in, got, nfe, test.want) } if !strings.Contains(nfe.Error(), test.wantErr) { t.Errorf("parseRPKIAddrBlocks(%s)=_,%v; want _, err containing %q", test.in, nfe, test.wantErr) } }) } } func TestParseRPKIASIdentifiers(t *testing.T) { tests := []struct { desc string in string // hex-encoded wantAS, wantRDI *ASIdentifiers wantErr string }{ { desc: "ValidASRange", in: ("3010" + // SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantAS: &ASIdentifiers{ ASIDRanges: []ASIDRange{ {Min: 0, Max: 0x00ffffffff}, }, }, }, { desc: "ValidASNumbers", in: ("300b" + // SEQUENCE ("a009" + // Tag:0 Class:Context-specific Compound:Y ("3007" + // SEQUENCE OF ASIdOrRange "0201" + "01" + // INTEGER "0202" + "0123"))), // INTEGER wantAS: &ASIdentifiers{ASIDs: []int{1, 0x123}}, }, { desc: "ValidASInherit", in: ("3004" + // SEQUENCE ("8002" + // Tag:0 Class:Context-specific Compound:N "0500")), // NULL wantAS: &ASIdentifiers{InheritFromIssuer: true}, }, { desc: "ValidRDIRange", in: ("3010" + // SEQUENCE ("a10e" + // Tag:1 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantRDI: &ASIdentifiers{ ASIDRanges: []ASIDRange{ {Min: 0, Max: 0x00ffffffff}, }, }, }, { desc: "InvalidASRange", in: ("3110" + // SET not SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantErr: "failed to asn1.Unmarshal ASIdentifiers extension", }, { desc: "TrailingASRange", in: ("3010" + // SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff"))) + // INTEGER "ff"), wantErr: "trailing data after ASIdentifiers extension", }, { desc: "InvalidAsIdsOrRanges", in: ("3010" + // SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("310c" + // SET not SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantErr: "failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges", }, { desc: "TrailingAsIdsOrRanges", in: ("3011" + // SEQUENCE ("a00f" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("300a" + // SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")) + "ff")), // INTEGER wantErr: "trailing data after ASIdentifiers.asIdsOrRanges", }, { desc: "InvalidAsIdsOrRangesType", in: ("3010" + // SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("310a" + // SET not SEQUENCE (ASRange) "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantAS: &ASIdentifiers{}, wantErr: "unexpected value in ASIdentifiers.asIdsOrRanges[0]", }, { desc: "InvalidASRange", in: ("3010" + // SEQUENCE ("a00e" + // Tag:0 Class:Context-specific Compound:Y ("300c" + // SEQUENCE OF ASIdOrRange ("100a" + // SEQUENCE (ASRange) but not Constructed:Y "0201" + "00" + // INTEGER "0205" + "00ffffffff")))), // INTEGER wantAS: &ASIdentifiers{}, wantErr: "failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges[0].range", }, { desc: "InvalidASId", in: ("300b" + // SEQUENCE ("a009" + // Tag:0 Class:Context-specific Compound:Y ("3007" + // SEQUENCE OF ASIdOrRange "8201" + "01" + // INTEGER but Constructed:Y "0202" + "0123"))), // INTEGER wantAS: &ASIdentifiers{ASIDs: []int{0x123}}, wantErr: "failed to asn1.Unmarshal ASIdentifiers.asIdsOrRanges[0].id", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { var nfe NonFatalErrors gotAS, gotRDI := parseRPKIASIdentifiers(fromHex(test.in), &nfe) if !reflect.DeepEqual(gotAS, test.wantAS) { t.Errorf("parseRPKIASIdentifiers(%s)=%+v,_,%v; want %+v,_", test.in, gotAS, nfe, test.wantAS) } if !reflect.DeepEqual(gotRDI, test.wantRDI) { t.Errorf("parseRPKIASIdentifiers(%s)=_,%+v,%v; want _,%+v", test.in, gotRDI, nfe, test.wantRDI) } if !strings.Contains(nfe.Error(), test.wantErr) { t.Errorf("parseRPKIASIdentifiers(%s)=_,_,%v; want _,_, err containing %q", test.in, nfe, test.wantErr) } }) } } google-certificate-transparency-go-2308f62/x509/sec1.go000066400000000000000000000105141462611535200225310ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto/ecdsa" "crypto/elliptic" "errors" "fmt" "math/big" "github.com/google/certificate-transparency-go/asn1" ) const ecPrivKeyVersion = 1 // ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure. // References: // // RFC 5915 // SEC1 - http://www.secg.org/sec1-v2.pdf // // Per RFC 5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in // most cases it is not. type ecPrivateKey struct { Version int PrivateKey []byte NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` } // ParseECPrivateKey parses an EC private key in SEC 1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "EC PRIVATE KEY". func ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) { return parseECPrivateKey(nil, der) } // MarshalECPrivateKey converts an EC private key to SEC 1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "EC PRIVATE KEY". // For a more flexible key format which is not EC specific, use // MarshalPKCS8PrivateKey. func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) { oid, ok := OIDFromNamedCurve(key.Curve) if !ok { return nil, errors.New("x509: unknown elliptic curve") } return marshalECPrivateKeyWithOID(key, oid) } // marshalECPrivateKey marshals an EC private key into ASN.1, DER format and // sets the curve ID to the given OID, or omits it if OID is nil. func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) { privateKeyBytes := key.D.Bytes() paddedPrivateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8) copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes) return asn1.Marshal(ecPrivateKey{ Version: 1, PrivateKey: paddedPrivateKey, NamedCurveOID: oid, PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}, }) } // parseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure. // The OID for the named curve may be provided from another source (such as // the PKCS8 container) - if it is provided then use this instead of the OID // that may exist in the EC private key structure. func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *ecdsa.PrivateKey, err error) { var privKey ecPrivateKey if _, err := asn1.Unmarshal(der, &privKey); err != nil { if _, err := asn1.Unmarshal(der, &pkcs8{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)") } if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)") } return nil, errors.New("x509: failed to parse EC private key: " + err.Error()) } if privKey.Version != ecPrivKeyVersion { return nil, fmt.Errorf("x509: unknown EC private key version %d", privKey.Version) } var nfe NonFatalErrors var curve elliptic.Curve if namedCurveOID != nil { curve = namedCurveFromOID(*namedCurveOID, &nfe) } else { curve = namedCurveFromOID(privKey.NamedCurveOID, &nfe) } if curve == nil { return nil, errors.New("x509: unknown elliptic curve") } k := new(big.Int).SetBytes(privKey.PrivateKey) curveOrder := curve.Params().N if k.Cmp(curveOrder) >= 0 { return nil, errors.New("x509: invalid elliptic curve private key value") } priv := new(ecdsa.PrivateKey) priv.Curve = curve priv.D = k privateKey := make([]byte, (curveOrder.BitLen()+7)/8) // Some private keys have leading zero padding. This is invalid // according to [SEC1], but this code will ignore it. for len(privKey.PrivateKey) > len(privateKey) { if privKey.PrivateKey[0] != 0 { return nil, errors.New("x509: invalid private key length") } privKey.PrivateKey = privKey.PrivateKey[1:] } // Some private keys remove all leading zeros, this is also invalid // according to [SEC1] but since OpenSSL used to do this, we ignore // this too. copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey) priv.X, priv.Y = curve.ScalarBaseMult(privateKey) return priv, nil } google-certificate-transparency-go-2308f62/x509/sec1_test.go000066400000000000000000000110141462611535200235640ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "encoding/hex" "strings" "testing" ) var ecKeyTests = []struct { derHex string shouldReserialize bool }{ // Generated using: // openssl ecparam -genkey -name secp384r1 -outform PEM {"3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50", true}, // This key was generated by GnuTLS and has illegal zero-padding of the // private key. See https://golang.org/issues/13699. {"3078020101042100f9f43a04b9bdc3ab01f53be6df80e7a7bc3eaf7b87fc24e630a4a0aa97633645a00a06082a8648ce3d030107a1440342000441a51bc318461b4c39a45048a16d4fc2a935b1ea7fe86e8c1fa219d6f2438f7c7fd62957d3442efb94b6a23eb0ea66dda663dc42f379cda6630b21b7888a5d3d", false}, // This was generated using an old version of OpenSSL and is missing a // leading zero byte in the private key that should be present. {"3081db0201010441607b4f985774ac21e633999794542e09312073480baa69550914d6d43d8414441e61b36650567901da714f94dffb3ce0e2575c31928a0997d51df5c440e983ca17a00706052b81040023a181890381860004001661557afedd7ac8d6b70e038e576558c626eb62edda36d29c3a1310277c11f67a8c6f949e5430a37dcfb95d902c1b5b5379c389873b9dd17be3bdb088a4774a7401072f830fb9a08d93bfa50a03dd3292ea07928724ddb915d831917a338f6b0aecfbc3cf5352c4a1295d356890c41c34116d29eeb93779aab9d9d78e2613437740f6", false}, } func TestParseECPrivateKey(t *testing.T) { for i, test := range ecKeyTests { derBytes, _ := hex.DecodeString(test.derHex) key, err := ParseECPrivateKey(derBytes) if err != nil { t.Fatalf("#%d: failed to decode EC private key: %s", i, err) } serialized, err := MarshalECPrivateKey(key) if err != nil { t.Fatalf("#%d: failed to encode EC private key: %s", i, err) } matches := bytes.Equal(serialized, derBytes) if matches != test.shouldReserialize { t.Fatalf("#%d: when serializing key: matches=%t, should match=%t: original %x, reserialized %x", i, matches, test.shouldReserialize, serialized, derBytes) } } } const hexPKCS1TestPKCS8Key = "30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031" const hexPKCS1TestECKey = "3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50" var pkcs1MismatchKeyTests = []struct { hexKey string errorContains string }{ {hexKey: hexPKCS1TestPKCS8Key, errorContains: "use ParsePKCS8PrivateKey instead"}, {hexKey: hexPKCS1TestECKey, errorContains: "use ParseECPrivateKey instead"}, } func TestPKCS1MismatchKeyFormat(t *testing.T) { for i, test := range pkcs1MismatchKeyTests { derBytes, _ := hex.DecodeString(test.hexKey) _, err := ParsePKCS1PrivateKey(derBytes) if !strings.Contains(err.Error(), test.errorContains) { t.Errorf("#%d: expected error containing %q, got %s", i, test.errorContains, err) } } } google-certificate-transparency-go-2308f62/x509/test-dir.crt000066400000000000000000000036231462611535200236170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIJAL8a/lsnspOqMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz dHMxETAPBgNVBAMMCHRlc3QtZGlyMB4XDTE3MDIwMTIzNTAyN1oXDTI3MDEzMDIz NTAyN1owTDELMAkGA1UEBhMCVUsxEzARBgNVBAgMClRlc3QtU3RhdGUxFTATBgNV BAoMDEdvbGFuZyBUZXN0czERMA8GA1UEAwwIdGVzdC1kaXIwggIiMA0GCSqGSIb3 DQEBAQUAA4ICDwAwggIKAoICAQDzBoi43Yn30KN13PKFHu8LA4UmgCRToTukLItM WK2Je45grs/axg9n3YJOXC6hmsyrkOnyBcx1xVNgSrOAll7fSjtChRIX72Xrloxu XewtWVIrijqz6oylbvEmbRT3O8uynu5rF82Pmdiy8oiSfdywjKuPnE0hjV1ZSCql MYcXqA+f0JFD8kMv4pbtxjGH8f2DkYQz+hHXLrJH4/MEYdVMQXoz/GDzLyOkrXBN hpMaBBqg1p0P+tRdfLXuliNzA9vbZylzpF1YZ0gvsr0S5Y6LVtv7QIRygRuLY4kF k+UYuFq8NrV8TykS7FVnO3tf4XcYZ7r2KV5FjYSrJtNNo85BV5c3xMD3fJ2XcOWk +oD1ATdgAM3aKmSOxNtNItKKxBe1mkqDH41NbWx7xMad78gDznyeT0tjEOltN2bM uXU1R/jgR/vq5Ec0AhXJyL/ziIcmuV2fSl/ZxT4ARD+16tgPiIx+welTf0v27/JY adlfkkL5XsPRrbSguISrj7JeaO/gjG3KnDVHcZvYBpDfHqRhCgrosfe26TZcTXx2 cRxOfvBjMz1zJAg+esuUzSkerreyRhzD7RpeZTwi6sxvx82MhYMbA3w1LtgdABio 9JRqZy3xqsIbNv7N46WO/qXL1UMRKb1UyHeW8g8btboz+B4zv1U0Nj+9qxPBbQui dgL9LQIDAQABo1AwTjAdBgNVHQ4EFgQUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwHwYD VR0jBBgwFoAUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwDAYDVR0TBAUwAwEB/zANBgkq hkiG9w0BAQsFAAOCAgEAvEVnUYsIOt87rggmLPqEueynkuQ+562M8EDHSQl82zbe xDCxeg3DvPgKb+RvaUdt1362z/szK10SoeMgx6+EQLoV9LiVqXwNqeYfixrhrdw3 ppAhYYhymdkbUQCEMHypmXP1vPhAz4o8Bs+eES1M+zO6ErBiD7SqkmBElT+GixJC 6epC9ZQFs+dw3lPlbiZSsGE85sqc3VAs0/JgpL/pb1/Eg4s0FUhZD2C2uWdSyZGc g0/v3aXJCp4j/9VoNhI1WXz3M45nysZIL5OQgXymLqJElQa1pZ3Wa4i/nidvT4AT Xlxc/qijM8set/nOqp7hVd5J0uG6qdwLRILUddZ6OpXd7ZNi1EXg+Bpc7ehzGsDt 3UFGzYXDjxYnK2frQfjLS8stOQIqSrGthW6x0fdkVx0y8BByvd5J6+JmZl4UZfzA m99VxXSt4B9x6BvnY7ktzcFDOjtuLc4B/7yg9fv1eQuStA4cHGGAttsCg1X/Kx8W PvkkeH0UWDZ9vhH9K36703z89da6MWF+bz92B0+4HoOmlVaXRkvblsNaynJnL0LC Ayry7QBxuh5cMnDdRwJB3AVJIiJ1GVpb7aGvBOnx+s2lwRv9HWtghb+cbwwktx1M JHyBf3GZNSWTpKY7cD8V+NnBv3UuioOVVo+XAU4LF/bYUjdRpxWADJizNtZrtFo= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/test-file.crt000066400000000000000000000036301462611535200237560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFbTCCA1WgAwIBAgIJAN338vEmMtLsMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz dHMxEjAQBgNVBAMMCXRlc3QtZmlsZTAeFw0xNzAyMDEyMzUyMDhaFw0yNzAxMzAy MzUyMDhaME0xCzAJBgNVBAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYD VQQKDAxHb2xhbmcgVGVzdHMxEjAQBgNVBAMMCXRlc3QtZmlsZTCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAPMGiLjdiffQo3Xc8oUe7wsDhSaAJFOhO6Qs i0xYrYl7jmCuz9rGD2fdgk5cLqGazKuQ6fIFzHXFU2BKs4CWXt9KO0KFEhfvZeuW jG5d7C1ZUiuKOrPqjKVu8SZtFPc7y7Ke7msXzY+Z2LLyiJJ93LCMq4+cTSGNXVlI KqUxhxeoD5/QkUPyQy/ilu3GMYfx/YORhDP6Edcuskfj8wRh1UxBejP8YPMvI6St cE2GkxoEGqDWnQ/61F18te6WI3MD29tnKXOkXVhnSC+yvRLljotW2/tAhHKBG4tj iQWT5Ri4Wrw2tXxPKRLsVWc7e1/hdxhnuvYpXkWNhKsm002jzkFXlzfEwPd8nZdw 5aT6gPUBN2AAzdoqZI7E200i0orEF7WaSoMfjU1tbHvExp3vyAPOfJ5PS2MQ6W03 Zsy5dTVH+OBH++rkRzQCFcnIv/OIhya5XZ9KX9nFPgBEP7Xq2A+IjH7B6VN/S/bv 8lhp2V+SQvlew9GttKC4hKuPsl5o7+CMbcqcNUdxm9gGkN8epGEKCuix97bpNlxN fHZxHE5+8GMzPXMkCD56y5TNKR6ut7JGHMPtGl5lPCLqzG/HzYyFgxsDfDUu2B0A GKj0lGpnLfGqwhs2/s3jpY7+pcvVQxEpvVTId5byDxu1ujP4HjO/VTQ2P72rE8Ft C6J2Av0tAgMBAAGjUDBOMB0GA1UdDgQWBBTLT/RbyfBB/Pa07oBnaM+QSJPO9TAf BgNVHSMEGDAWgBTLT/RbyfBB/Pa07oBnaM+QSJPO9TAMBgNVHRMEBTADAQH/MA0G CSqGSIb3DQEBCwUAA4ICAQB3sCntCcQwhMgRPPyvOCMyTcQ/Iv+cpfxz2Ck14nlx AkEAH2CH0ov5GWTt07/ur3aa5x+SAKi0J3wTD1cdiw4U/6Uin6jWGKKxvoo4IaeK SbM8w/6eKx6UbmHx7PA/eRABY9tTlpdPCVgw7/o3WDr03QM+IAtatzvaCPPczake pbdLwmBZB/v8V+6jUajy6jOgdSH0PyffGnt7MWgDETmNC6p/Xigp5eh+C8Fb4NGT xgHES5PBC+sruWp4u22bJGDKTvYNdZHsnw/CaKQWNsQqwisxa3/8N5v+PCff/pxl r05pE3PdHn9JrCl4iWdVlgtiI9BoPtQyDfa/OEFaScE8KYR8LxaAgdgp3zYncWls BpwQ6Y/A2wIkhlD9eEp5Ib2hz7isXOs9UwjdriKqrBXqcIAE5M+YIk3+KAQKxAtd 4YsK3CSJ010uphr12YKqlScj4vuKFjuOtd5RyyMIxUG3lrrhAu2AzCeKCLdVgA8+ 75FrYMApUdvcjp4uzbBoED4XRQlx9kdFHVbYgmE/+yddBYJM8u4YlgAL0hW2/D8p z9JWIfxVmjJnBnXaKGBuiUyZ864A3PJndP6EMMo7TzS2CDnfCYuJjvI0KvDjFNmc rQA04+qfMSEz3nmKhbbZu4eYLzlADhfH8tT4GMtXf71WLA5AUHGf2Y4+HIHTsmHG vQ== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/000077500000000000000000000000001462611535200231575ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/testdata/invalid/000077500000000000000000000000001462611535200246055ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-der-invalid-nonminimal-int.pem000066400000000000000000000021171462611535200330510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDAjCCAeqgAwIBAgIEAAASNDANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJH QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo b3JpdHkwHhcNMTgwNDE0MTQwMDQ0WhcNMjAxMjE0MTQwMDQ0WjCBuTELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZp ZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMxFTATBgNVBAMMDCouZ29vZ2xlLmNvbTEb MBkGA1UEBAwSUkZDNTI4MCBhcHBlbmRpeCBBMTQwMgYDVQQqDCtOb24tbWluaW1h bCBJTlRFR0VSIGluIERFUiBmb3Igc2VyaWFsTnVtYmVyMFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAEut2u8/ADA9VfIlXhvx0WzfDAHoR2+XAiPBqzGvlJJMSgFCn2 saAuS4Y7n3HzrRSe8tHEqNgUQ4qw63jON6MkEKMkMCIwDwYDVR0jBAgwBoAEAQID BDAPBgNVHQ8BAf8EBQMDB4CAMA0GCSqGSIb3DQEBCwUAA4IBAQAxoWewnxpOM2Pw bJ2X/s3XftR9iIk8yVf1toWMU45ka4VeL3wsjpvl2UvtYmrMsHBB3dw8tdjwWW/o X2B8RHQNbApBTNOY5l7cx6T66xkOtGVFMPNGG8h3UCGGlv/qsBjuKWGq1Q61/xwU FYgJdQAidyBDKfkaQhTVA+bNlbDAfyJYBNo4UNPI35KzaGJlDmMlWMKwjIhKSXLY zAOl8Zu2r7T7LcTU57YH5pO+qcJGIpeoNNSRyqaP/+nKhxrsAkPDlbtHRZySU4fF deuTmiF5r6g7iQbIMlt5v9NAVAYctdIzYS8Jh+WgflP8DszR/vAuvDeHTfxbduFl mGJs+Rj5 -----END CERTIFICATE----- xf-der-pubkey-rsa-nonminimal-int.pem000066400000000000000000000025371462611535200334340ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/testdata/invalid-----BEGIN CERTIFICATE----- MIIDyjCCArKgAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCR0Ix DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n bGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRlQXV0aG9y aXR5MB4XDTE4MDYxNTE0MzQzM1oXDTIxMDIxNDE0MzQzM1owgbcxCzAJBgNVBAYT AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3 MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5jb20xGzAZ BgNVBAQMElJGQzUyODAgYXBwZW5kaXggQTEyMDAGA1UEKgwpTm9uLW1pbmltYWwg SU5URUdFUiBpbiBERVIgZm9yIHB1YmxpYyBrZXkwggEjMA0GCSqGSIb3DQEBAQUA A4IBEAAwggELAoIBAQCNrS0YYd2+cZ0sdkHrfbZF22072GhyRFkC4tiFR+adM7c0 qBt3Kj9fb6KWY9buwAhw9qHd//8dk1RoLPsOhr1U1RCiKXt3ugb6dJeEMRupyp9O tcRuzlXcudG2mMYEgtmI6CBdSTQAet19CLuCMul3jncMXwyBFnbsF0XJBtPhCmQW QiBoJKuXsmBOqW3wTzwYAY8slDGsWzq0bmAAsr0a2Ko6k5HUSJUcFVWdWcuCtrXr 0BM6Ue9O9O9g9iG7sZHOOVQehq7OBJBFffkfyeiNrgLnOB0OwMZvlUpC+YkvWIBz 38b51AQefehgtMzpWL0+FoX+eVqVoBufF/EBtFuvAgQAAQABoyQwIjAPBgNVHSME CDAGgAQBAgMEMA8GA1UdDwEB/wQFAwMHgIAwDQYJKoZIhvcNAQELBQADggEBAIoA +OB2BJf0EWYLusoIUzGWqi0Qm3D4R7cqyUrooh/U7litWKduoI3SKC+1zXxJv/Xj Jv5PU/GNZwmLadpGt9tUlKmn1RMymw40vcO5nH+uV+me5yfNMvPNPr7FDwP8J32o xFoMBOKbEA3D8AdGDHODgvZolTNz9XUoiW7eRn1XcWURviizSFaGAQq/aarU/o4g t7kkExlpcFB9FDh/noajzNDcav0InaLlISzmvEM81g6wC6AtmiUK558Emy4BVj+K YA2GOi70V+TzoxyFBR0R5NLA2P1cYFBByUK+6Squze/VYIZuGp6oVrcvk6cYEZ3F 5Zbp8fUPD3zbQPxnujM= -----END CERTIFICATE----- xf-ext-extended-key-usage-empty-oid.pem000066400000000000000000000022741462611535200340440ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509/testdata/invalid-----BEGIN CERTIFICATE----- MIIDUjCCAjqgAwIBAgIIAfJxAympAgowDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDgwNDEzMjg1MloXDTIxMDQwNTEzMjg1MlowgfUxCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xWTBXBgNVBAQMUFJGQzUyODAgczQuMi4xLjEyICdFeHRLZXlVc2FnZVN5bnRh eCA6Oj0gU0VRVUVOQ0UgU0laRSAoMS4uTUFYKSBPRiBLZXlQdXJwb3NlSWQnMTIw MAYDVQQqDClFbXB0eSBPSUQgaW4gRXh0ZW5kZWQgS2V5IFVzYWdlIGV4dGVuc2lv bjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLrdrvPwAwPVXyJV4b8dFs3wwB6E dvlwIjwasxr5SSTEoBQp9rGgLkuGO59x860UnvLRxKjYFEOKsOt4zjejJBCjNDAy MA8GA1UdIwQIMAaABAECAwQwHwYDVR0lBBgwFgYIKwYBBQUHAwEGCCsGAQUFBwMC BgAwDQYJKoZIhvcNAQELBQADggEBAI8Wc2Yzai2RwombdXSEd6MqMLaaqtlCPiJn obn8PaxXTQWovxVjkFO491/Kik3DtporajmuTb1+rIlx8cl6VA8CWINajEo5f1e/ DtvuRMr/b5gg3kQM230vvI8CabnkLIv0uYXOqiO0MBwmTdZX7+Y5hRwaoARuc1NY 0ZLgooiUkXApxj5w0f0TPEGNcBM4NiZ9uBU8ows8bcE8OhwMXA6CjyGKKwhASgSe RK+A6XtoNZE6Iv0L4jXLspgrlOIBSLQeIkpumlilm+MMU3EnE9okV5m/YEHq32A/ kORZmQeRu7flROvVfD0AT9q7zjuZh3hyTx0zkGd7g9JhojvvMgo= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-ext-extended-key-usage-empty.pem000066400000000000000000000022241462611535200333450ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDMzCCAhugAwIBAgIIAVVnkeRhvtcwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDYwOTEwNDMyN1oXDTIxMDIwODEwNDMyN1owge4xCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xWTBXBgNVBAQMUFJGQzUyODAgczQuMi4xLjEyICdFeHRLZXlVc2FnZVN5bnRh eCA6Oj0gU0VRVUVOQ0UgU0laRSAoMS4uTUFYKSBPRiBLZXlQdXJwb3NlSWQnMSsw KQYDVQQqDCJFbXB0eSBFeHRlbmRlZCBLZXkgVXNhZ2UgZXh0ZW5zaW9uMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEut2u8/ADA9VfIlXhvx0WzfDAHoR2+XAiPBqz GvlJJMSgFCn2saAuS4Y7n3HzrRSe8tHEqNgUQ4qw63jON6MkEKMcMBowDwYDVR0j BAgwBoAEAQIDBDAHBgNVHSUEADANBgkqhkiG9w0BAQsFAAOCAQEAqpjTB/lJgjVy yp7YfJf+nWDXgMi5fxkOhcjB3dYWCvK/2Ks0WgeiQjJcsIhnZKshTVbbXmg7VPp0 o3NcwX7IpFjMaZfO+T74Ph8G1mvJWf3t6WSPXsJhqsXr0lBhe7opKUDLTlnOHAsd H+M43vIgiLfou8XvkIjrJJmTjjBwfymxovU5zwK2SC2CTteaN7hYCh+pHsYSUOzL u36PLffyfc6Ll1F/U7drP4Fx6J+o6qIwUwQWa/c0CLDv0a48X5Qg1f9GH38+1mvs a2JgqxGcLlphgJ8OvMNRMQw0I1bXGyuLHjuNocRgtkPQxHhA6nj9zil1P6HKXF1+ K7GLnBspbg== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-ext-subject-info-empty.pem000066400000000000000000000022641462611535200322510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDTTCCAjWgAwIBAgIIAf7LDuxWHJgwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDQxNDE0MDA0MloXDTIwMTIxNDE0MDA0MlowggEAMQsw CQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRh aW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEVMBMGA1UEAwwMKi5nb29nbGUu Y29tMWMwYQYDVQQEDFpSRkM1MjgwIHM0LjIuMi4yICdTdWJqZWN0SW5mb0FjY2Vz c1N5bnRheCA6Oj0gU0VRVUVOQ0UgU0laRSAoMS4uTUFYKSBPRiBBY2Nlc3NEZXNj cmlwdGlvbicxMzAxBgNVBCoMKkVtcHR5IFN1YmplY3QgSW5mb3JtYXRpb24gQWNj ZXNzIGV4dGVuc2lvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLrdrvPwAwPV XyJV4b8dFs3wwB6EdvlwIjwasxr5SSTEoBQp9rGgLkuGO59x860UnvLRxKjYFEOK sOt4zjejJBCjIzAhMA8GA1UdIwQIMAaABAECAwQwDgYIKwYBBQUHAQsEAjAAMA0G CSqGSIb3DQEBCwUAA4IBAQBTgk11aYa5VkuqWx8DefCQ/0CoeoOMBDK4MCc12GRn AAgKRh9wlBNINx+fO4LuCExK9jJP2qPMYzJtmxGfbtznp8dSnnewgF3FmQmairpT MjHGngjJ+IVKyfBWN4hn69+jIEjX7GeK1lOGHsy6ot5y4QVfQKPq2E/eYcbGWjJP xIBCOpEph0H8mt+w6Jg47qJmTnM7xvTiAeCzXKQ+n/RfwOLMtnGanNdY6HSNyaMa DtMcivF2PynvqW+h4rU/yXydo3USWVaJl/NgzwKQeMXNQVZhfzd3nb52UB095UmF 7eK24/vw0E0IjzL1lL6VPOb2dRbYpf78A+Q0L3mWHFIy -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-pubkey-ecdsa-secp192r1.pem000066400000000000000000000020661462611535200317320ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC7jCCAdagAwIBAgIIAfW40bAMOG0wDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDYwOTEyMjM0N1oXDTIxMDIwODEyMjM0N1owgcIxCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xIzAhBgNVBAQMGlJGQzU0ODAgczIuMS4xLjEgc2VjcDE5MnIxMTUwMwYDVQQq DCxFQ0RTQSBwdWJsaWMga2V5IHVzaW5nIHNlY3AxOTJyMSAodG9vIHNob3J0KTBJ MBMGByqGSM49AgEGCCqGSM49AwEBAzIABGlQ1rIiPYYc3qiDS+OfcxUaSrJQ1KC6 a5JNv/sDsQJW1XK8JOqi0z1MJh7h5aBF46MTMBEwDwYDVR0jBAgwBoAEAQIDBDAN BgkqhkiG9w0BAQsFAAOCAQEAifoS7Fii07UBJ6csbpFgqeFf3GFKoa61nGpz5EBE JhsryV3CVn++rf1Zdh5gF+MJ+FXh5YqUjT5thg9pYLPKN05n0iR2NfHTqGajDndb 81HsYLcSarbsrjOwEGg7F0kzw0Nipn3A01YrrE+ZoTiCSpLS6yJ39uMMo/JJqsKm YpbZtlV2LJs56cLx7KtOph6uxP3jswmlTnCqCjhFBKb61LKedMWhapgAcr/PagSf 5e01kGgjPR/55cqFgXUjx1A29cSOixk9UohnttgNQHslc/LZjDPVMmRHBoLA+zaO 3k0NJrbU/AZQb7MLaQl1C1D38O+mhTV+6av0elXHgEsdaw== -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-pubkey-rsa-modulus-negative.pem000066400000000000000000000025021462611535200332720ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIIAcsEZnJb7VgwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDQxNDE0MDA0M1oXDTIwMTIxNDE0MDA0M1owga4xCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xFzAVBgNVBAQMDlJGQzMyNzkgczIuMy4xMS0wKwYDVQQqDCRSU0EgcHVibGlj IGtleSB3aXRoIG5lZ2F0aXZlIG1vZHVsdXMwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAYG84vfLyJC1AYuwoG7t8qXsyfGiXU4wCY7x+z5scmYEP5gnaSdL 8j+TnpOlgxTktzMvF1ylMHXfVUXHXyAyLckbdnaBUzwPe73rQZJPlL5TpF+39osk lUeURC0zMwV+KHw82hokmrG3YW9wE6FLbNpNB3FPbf0nE0NBWVFEnm8h9GGKB5kh YHwRdvSrGSt09MQ1hGO6LDDJbfgHo1vxhIaUvZlR9MNeiv/v5+YnQNfcaU0TEaVD AOiDc6kzByGCxOxlwUx3NQ2BA/PzffN5fq9//0FHvl0UTGqDtpniwpx7pN/8E+kz BKwoe99iZ+T3iihZAruHB1vZmD35pObB9gkZAgMBAAGjEzARMA8GA1UdIwQIMAaA BAECAwQwDQYJKoZIhvcNAQELBQADggEBAAwaQtsyI9TX9FepydCZDqhm5ifNN5MP bqXo+pH5UIsvQ3oeupUeqDxwclio7Var+vHSASRHV4nts7ZM8p7lXPveiWu8mp3D YuVM40UbfXcJQ+7S0IO3/ZMuho8pLv6m2Ewgz6BO+0/3ianyZUmROhZS5CCEWGk1 CYbZ+/4laT6U1xd1xMOpUa5+W50lXjx7CU08tfsY/BJ0C7wCnAXlzlEPCvzZyxB2 MGbzYa7M/zYSlWSXmXeZM0oMAU5HMjiFkGY9itJPxlvN69QgGAXHJKzflfqAEBnp s9ixhjhlCdOXVUN9j+FCi5KHf+UZ+5CvZYsnWxl3KfYWKv235bCn4t4= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-pubkey-rsa-param-nonnull.pem000066400000000000000000000025231462611535200325700ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDwTCCAqmgAwIBAgIIAW/ubtcaSUUwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE2MDcyODA5MDExMVoXDTE5MDMzMDA5MDExMVowgbExCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xFzAVBgNVBAQMDlJGQzMyNzkgczIuMy4xMTAwLgYDVQQqDCdSU0EgcHVibGlj IGtleSB3aXRoIG5vbi1OVUxMIHBhcmFtZXRlcnMwggErMBYGCSqGSIb3DQEBAQYJ KoZIhvcNAQEBA4IBDwAwggEKAoIBAQC84vfLyJC1AYuwoG7t8qXsyfGiXU4wCY7x +z5scmYEP5gnaSdL8j+TnpOlgxTktzMvF1ylMHXfVUXHXyAyLckbdnaBUzwPe73r QZJPlL5TpF+39osklUeURC0zMwV+KHw82hokmrG3YW9wE6FLbNpNB3FPbf0nE0NB WVFEnm8h9GGKB5khYHwRdvSrGSt09MQ1hGO6LDDJbfgHo1vxhIaUvZlR9MNeiv/v 5+YnQNfcaU0TEaVDAOiDc6kzByGCxOxlwUx3NQ2BA/PzffN5fq9//0FHvl0UTGqD tpniwpx7pN/8E+kzBKwoe99iZ+T3iihZAruHB1vZmD35pObB9gkZAgMBAAGjEzAR MA8GA1UdIwQIMAaABAECAwQwDQYJKoZIhvcNAQELBQADggEBADnuAvR/bU6Y7/NE vY585WFyTJanfHDgZX3jGM96/x/axHs8Yi2iqa1wFvhhIb0eh921qcw+QIYmyGzD byYTYxtvSQXfsGSIY+CIGnV2ajVn5DHUgt3o99rdUx3TTh0RFljQtduHAYgIN6ub NIxQwF6znpyLSd0W/AB1B5bk1+b9ZbRL8bw9IrA/W94ED2tb+8qetacSe8cQlIt+ 6wl0rl4spaBx01rVDNmJEITg/i5CcUQAbD3i2jMHAONHXMnMWY2+J1yBIZh9pOUp 874FkWFtERGJc44pxeE+r2bM5TLBl7MrVdkQUTFFBmY06PNvwgVswN/gksphTjnw tqH/xtg= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/invalid/xf-subject-nonprintable.pem000066400000000000000000000022401462611535200320510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDPjCCAiagAwIBAgIIAZ57K0BfmDMwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE BhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQK EwZHb29nbGUxDDAKBgNVBAsTA0VuZzEhMB8GA1UEAxMYRmFrZUNlcnRpZmljYXRl QXV0aG9yaXR5MB4XDTE4MDQxNDE0MDA0MFoXDTIwMTIxNDE0MDA0MFowgfExCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp biBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdvb2dsZS5j b20xTjBMBgNVBAQTRVguNjgwIDM3LjQgVGFibGUgODsgVGhlIEAgY2hhcmFjdGVy IGlzIG5vdCB2YWxpZCBpbiBhIFByaW50YWJsZVN0cmluZzE5MDcGA1UEKgwwU3Vi amVjdCBQcmludGFibGVTdHJpbmcgd2l0aCBub24tcHJpbnRhYmxlIGNoYXJzMFkw EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEut2u8/ADA9VfIlXhvx0WzfDAHoR2+XAi PBqzGvlJJMSgFCn2saAuS4Y7n3HzrRSe8tHEqNgUQ4qw63jON6MkEKMkMCIwDwYD VR0jBAgwBoAEAQIDBDAPBgNVHQ8BAf8EBQMDB4CAMA0GCSqGSIb3DQEBCwUAA4IB AQAGQkBpzlopZVN4o9T0wsh9d8J3KLHudRuUXMkZgQt2L2PdlMUIvb878oltain0 6hsjeNPj6e4b+bEoCHJ4MsFF2ULiBhO/qW+ieDXuTamHKlJmmRlV7RUA/jg6z2qQ QWlEMqE0ZtvQJHPSCRXi2tiwxAHju2Aa7Geszf6MRaJbdtaWcbZ9ZGjGZrgXzw42 9m0eBCSwEHkXQCKfXCYjvPhTF1LrdhT/wXYVbBeVi5GeEAmpkoLR5tPbZV97AUlI dgqrBKWB/vmRAJDViOckWVbalm+W9IVLCQIzOYpKU7fpYrRraKaVZiTDdHSc4Vsf pXSWxd0fJ5PnQXfXkclYI4sQ -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/testdata/test-dir.crt000066400000000000000000000036231462611535200254300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIJAL8a/lsnspOqMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz dHMxETAPBgNVBAMMCHRlc3QtZGlyMB4XDTE3MDIwMTIzNTAyN1oXDTI3MDEzMDIz NTAyN1owTDELMAkGA1UEBhMCVUsxEzARBgNVBAgMClRlc3QtU3RhdGUxFTATBgNV BAoMDEdvbGFuZyBUZXN0czERMA8GA1UEAwwIdGVzdC1kaXIwggIiMA0GCSqGSIb3 DQEBAQUAA4ICDwAwggIKAoICAQDzBoi43Yn30KN13PKFHu8LA4UmgCRToTukLItM WK2Je45grs/axg9n3YJOXC6hmsyrkOnyBcx1xVNgSrOAll7fSjtChRIX72Xrloxu XewtWVIrijqz6oylbvEmbRT3O8uynu5rF82Pmdiy8oiSfdywjKuPnE0hjV1ZSCql MYcXqA+f0JFD8kMv4pbtxjGH8f2DkYQz+hHXLrJH4/MEYdVMQXoz/GDzLyOkrXBN hpMaBBqg1p0P+tRdfLXuliNzA9vbZylzpF1YZ0gvsr0S5Y6LVtv7QIRygRuLY4kF k+UYuFq8NrV8TykS7FVnO3tf4XcYZ7r2KV5FjYSrJtNNo85BV5c3xMD3fJ2XcOWk +oD1ATdgAM3aKmSOxNtNItKKxBe1mkqDH41NbWx7xMad78gDznyeT0tjEOltN2bM uXU1R/jgR/vq5Ec0AhXJyL/ziIcmuV2fSl/ZxT4ARD+16tgPiIx+welTf0v27/JY adlfkkL5XsPRrbSguISrj7JeaO/gjG3KnDVHcZvYBpDfHqRhCgrosfe26TZcTXx2 cRxOfvBjMz1zJAg+esuUzSkerreyRhzD7RpeZTwi6sxvx82MhYMbA3w1LtgdABio 9JRqZy3xqsIbNv7N46WO/qXL1UMRKb1UyHeW8g8btboz+B4zv1U0Nj+9qxPBbQui dgL9LQIDAQABo1AwTjAdBgNVHQ4EFgQUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwHwYD VR0jBBgwFoAUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwDAYDVR0TBAUwAwEB/zANBgkq hkiG9w0BAQsFAAOCAgEAvEVnUYsIOt87rggmLPqEueynkuQ+562M8EDHSQl82zbe xDCxeg3DvPgKb+RvaUdt1362z/szK10SoeMgx6+EQLoV9LiVqXwNqeYfixrhrdw3 ppAhYYhymdkbUQCEMHypmXP1vPhAz4o8Bs+eES1M+zO6ErBiD7SqkmBElT+GixJC 6epC9ZQFs+dw3lPlbiZSsGE85sqc3VAs0/JgpL/pb1/Eg4s0FUhZD2C2uWdSyZGc g0/v3aXJCp4j/9VoNhI1WXz3M45nysZIL5OQgXymLqJElQa1pZ3Wa4i/nidvT4AT Xlxc/qijM8set/nOqp7hVd5J0uG6qdwLRILUddZ6OpXd7ZNi1EXg+Bpc7ehzGsDt 3UFGzYXDjxYnK2frQfjLS8stOQIqSrGthW6x0fdkVx0y8BByvd5J6+JmZl4UZfzA m99VxXSt4B9x6BvnY7ktzcFDOjtuLc4B/7yg9fv1eQuStA4cHGGAttsCg1X/Kx8W PvkkeH0UWDZ9vhH9K36703z89da6MWF+bz92B0+4HoOmlVaXRkvblsNaynJnL0LC Ayry7QBxuh5cMnDdRwJB3AVJIiJ1GVpb7aGvBOnx+s2lwRv9HWtghb+cbwwktx1M JHyBf3GZNSWTpKY7cD8V+NnBv3UuioOVVo+XAU4LF/bYUjdRpxWADJizNtZrtFo= -----END CERTIFICATE----- google-certificate-transparency-go-2308f62/x509/verify.go000066400000000000000000001026461462611535200232120ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "errors" "fmt" "net" "net/url" "os" "reflect" "runtime" "strings" "time" "unicode/utf8" ) // ignoreCN disables interpreting Common Name as a hostname. See issue 24151. var ignoreCN = strings.Contains(os.Getenv("GODEBUG"), "x509ignoreCN=1") type InvalidReason int const ( // NotAuthorizedToSign results when a certificate is signed by another // which isn't marked as a CA certificate. NotAuthorizedToSign InvalidReason = iota // Expired results when a certificate has expired, based on the time // given in the VerifyOptions. Expired // CANotAuthorizedForThisName results when an intermediate or root // certificate has a name constraint which doesn't permit a DNS or // other name (including IP address) in the leaf certificate. CANotAuthorizedForThisName // TooManyIntermediates results when a path length constraint is // violated. TooManyIntermediates // IncompatibleUsage results when the certificate's key usage indicates // that it may only be used for a different purpose. IncompatibleUsage // NameMismatch results when the subject name of a parent certificate // does not match the issuer name in the child. NameMismatch // NameConstraintsWithoutSANs results when a leaf certificate doesn't // contain a Subject Alternative Name extension, but a CA certificate // contains name constraints, and the Common Name can be interpreted as // a hostname. // // You can avoid this error by setting the experimental GODEBUG environment // variable to "x509ignoreCN=1", disabling Common Name matching entirely. // This behavior might become the default in the future. NameConstraintsWithoutSANs // UnconstrainedName results when a CA certificate contains permitted // name constraints, but leaf certificate contains a name of an // unsupported or unconstrained type. UnconstrainedName // TooManyConstraints results when the number of comparison operations // needed to check a certificate exceeds the limit set by // VerifyOptions.MaxConstraintComparisions. This limit exists to // prevent pathological certificates can consuming excessive amounts of // CPU time to verify. TooManyConstraints // CANotAuthorizedForExtKeyUsage results when an intermediate or root // certificate does not permit a requested extended key usage. CANotAuthorizedForExtKeyUsage ) // CertificateInvalidError results when an odd error occurs. Users of this // library probably want to handle all these errors uniformly. type CertificateInvalidError struct { Cert *Certificate Reason InvalidReason Detail string } func (e CertificateInvalidError) Error() string { switch e.Reason { case NotAuthorizedToSign: return "x509: certificate is not authorized to sign other certificates" case Expired: return "x509: certificate has expired or is not yet valid: " + e.Detail case CANotAuthorizedForThisName: return "x509: a root or intermediate certificate is not authorized to sign for this name: " + e.Detail case CANotAuthorizedForExtKeyUsage: return "x509: a root or intermediate certificate is not authorized for an extended key usage: " + e.Detail case TooManyIntermediates: return "x509: too many intermediates for path length constraint" case IncompatibleUsage: return "x509: certificate specifies an incompatible key usage" case NameMismatch: return "x509: issuer name does not match subject from issuing certificate" case NameConstraintsWithoutSANs: return "x509: issuer has name constraints but leaf doesn't have a SAN extension" case UnconstrainedName: return "x509: issuer has name constraints but leaf contains unknown or unconstrained name: " + e.Detail } return "x509: unknown error" } // HostnameError results when the set of authorized names doesn't match the // requested name. type HostnameError struct { Certificate *Certificate Host string } func (h HostnameError) Error() string { c := h.Certificate if !c.hasSANExtension() && !validHostname(c.Subject.CommonName) && matchHostnames(toLowerCaseASCII(c.Subject.CommonName), toLowerCaseASCII(h.Host)) { // This would have validated, if it weren't for the validHostname check on Common Name. return "x509: Common Name is not a valid hostname: " + c.Subject.CommonName } var valid string if ip := net.ParseIP(h.Host); ip != nil { // Trying to validate an IP if len(c.IPAddresses) == 0 { return "x509: cannot validate certificate for " + h.Host + " because it doesn't contain any IP SANs" } for _, san := range c.IPAddresses { if len(valid) > 0 { valid += ", " } valid += san.String() } } else { if c.commonNameAsHostname() { valid = c.Subject.CommonName } else { valid = strings.Join(c.DNSNames, ", ") } } if len(valid) == 0 { return "x509: certificate is not valid for any names, but wanted to match " + h.Host } return "x509: certificate is valid for " + valid + ", not " + h.Host } // UnknownAuthorityError results when the certificate issuer is unknown type UnknownAuthorityError struct { Cert *Certificate // hintErr contains an error that may be helpful in determining why an // authority wasn't found. hintErr error // hintCert contains a possible authority certificate that was rejected // because of the error in hintErr. hintCert *Certificate } func (e UnknownAuthorityError) Error() string { s := "x509: certificate signed by unknown authority" if e.hintErr != nil { certName := e.hintCert.Subject.CommonName if len(certName) == 0 { if len(e.hintCert.Subject.Organization) > 0 { certName = e.hintCert.Subject.Organization[0] } else { certName = "serial:" + e.hintCert.SerialNumber.String() } } s += fmt.Sprintf(" (possibly because of %q while trying to verify candidate authority certificate %q)", e.hintErr, certName) } return s } // SystemRootsError results when we fail to load the system root certificates. type SystemRootsError struct { Err error } func (se SystemRootsError) Error() string { msg := "x509: failed to load system roots and no roots provided" if se.Err != nil { return msg + "; " + se.Err.Error() } return msg } // errNotParsed is returned when a certificate without ASN.1 contents is // verified. Platform-specific verification needs the ASN.1 contents. var errNotParsed = errors.New("x509: missing ASN.1 contents; use ParseCertificate") // VerifyOptions contains parameters for Certificate.Verify. It's a structure // because other PKIX verification APIs have ended up needing many options. type VerifyOptions struct { DNSName string Intermediates *CertPool Roots *CertPool // if nil, the system roots are used CurrentTime time.Time // if zero, the current time is used // Options to disable various verification checks. DisableTimeChecks bool DisableCriticalExtensionChecks bool DisableNameChecks bool DisableEKUChecks bool DisablePathLenChecks bool DisableNameConstraintChecks bool // KeyUsage specifies which Extended Key Usage values are acceptable. A leaf // certificate is accepted if it contains any of the listed values. An empty // list means ExtKeyUsageServerAuth. To accept any key usage, include // ExtKeyUsageAny. // // Certificate chains are required to nest these extended key usage values. // (This matches the Windows CryptoAPI behavior, but not the spec.) KeyUsages []ExtKeyUsage // MaxConstraintComparisions is the maximum number of comparisons to // perform when checking a given certificate's name constraints. If // zero, a sensible default is used. This limit prevents pathological // certificates from consuming excessive amounts of CPU time when // validating. MaxConstraintComparisions int } const ( leafCertificate = iota intermediateCertificate rootCertificate ) // rfc2821Mailbox represents a “mailbox” (which is an email address to most // people) by breaking it into the “local” (i.e. before the '@') and “domain” // parts. type rfc2821Mailbox struct { local, domain string } // parseRFC2821Mailbox parses an email address into local and domain parts, // based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280, // Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The // format of an rfc822Name is a "Mailbox" as defined in RFC 2821, Section 4.1.2”. func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { if len(in) == 0 { return mailbox, false } localPartBytes := make([]byte, 0, len(in)/2) if in[0] == '"' { // Quoted-string = DQUOTE *qcontent DQUOTE // non-whitespace-control = %d1-8 / %d11 / %d12 / %d14-31 / %d127 // qcontent = qtext / quoted-pair // qtext = non-whitespace-control / // %d33 / %d35-91 / %d93-126 // quoted-pair = ("\" text) / obs-qp // text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text // // (Names beginning with “obs-” are the obsolete syntax from RFC 2822, // Section 4. Since it has been 16 years, we no longer accept that.) in = in[1:] QuotedString: for { if len(in) == 0 { return mailbox, false } c := in[0] in = in[1:] switch { case c == '"': break QuotedString case c == '\\': // quoted-pair if len(in) == 0 { return mailbox, false } if in[0] == 11 || in[0] == 12 || (1 <= in[0] && in[0] <= 9) || (14 <= in[0] && in[0] <= 127) { localPartBytes = append(localPartBytes, in[0]) in = in[1:] } else { return mailbox, false } case c == 11 || c == 12 || // Space (char 32) is not allowed based on the // BNF, but RFC 3696 gives an example that // assumes that it is. Several “verified” // errata continue to argue about this point. // We choose to accept it. c == 32 || c == 33 || c == 127 || (1 <= c && c <= 8) || (14 <= c && c <= 31) || (35 <= c && c <= 91) || (93 <= c && c <= 126): // qtext localPartBytes = append(localPartBytes, c) default: return mailbox, false } } } else { // Atom ("." Atom)* NextChar: for len(in) > 0 { // atext from RFC 2822, Section 3.2.4 c := in[0] switch { case c == '\\': // Examples given in RFC 3696 suggest that // escaped characters can appear outside of a // quoted string. Several “verified” errata // continue to argue the point. We choose to // accept it. in = in[1:] if len(in) == 0 { return mailbox, false } fallthrough case ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '/' || c == '=' || c == '?' || c == '^' || c == '_' || c == '`' || c == '{' || c == '|' || c == '}' || c == '~' || c == '.': localPartBytes = append(localPartBytes, in[0]) in = in[1:] default: break NextChar } } if len(localPartBytes) == 0 { return mailbox, false } // From RFC 3696, Section 3: // “period (".") may also appear, but may not be used to start // or end the local part, nor may two or more consecutive // periods appear.” twoDots := []byte{'.', '.'} if localPartBytes[0] == '.' || localPartBytes[len(localPartBytes)-1] == '.' || bytes.Contains(localPartBytes, twoDots) { return mailbox, false } } if len(in) == 0 || in[0] != '@' { return mailbox, false } in = in[1:] // The RFC species a format for domains, but that's known to be // violated in practice so we accept that anything after an '@' is the // domain part. if _, ok := domainToReverseLabels(in); !ok { return mailbox, false } mailbox.local = string(localPartBytes) mailbox.domain = in return mailbox, true } // domainToReverseLabels converts a textual domain name like foo.example.com to // the list of labels in reverse order, e.g. ["com", "example", "foo"]. func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { for len(domain) > 0 { if i := strings.LastIndexByte(domain, '.'); i == -1 { reverseLabels = append(reverseLabels, domain) domain = "" } else { reverseLabels = append(reverseLabels, domain[i+1:]) domain = domain[:i] } } if len(reverseLabels) > 0 && len(reverseLabels[0]) == 0 { // An empty label at the end indicates an absolute value. return nil, false } for _, label := range reverseLabels { if len(label) == 0 { // Empty labels are otherwise invalid. return nil, false } for _, c := range label { if c < 33 || c > 126 { // Invalid character. return nil, false } } } return reverseLabels, true } func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) { // If the constraint contains an @, then it specifies an exact mailbox // name. if strings.Contains(constraint, "@") { constraintMailbox, ok := parseRFC2821Mailbox(constraint) if !ok { return false, fmt.Errorf("x509: internal error: cannot parse constraint %q", constraint) } return mailbox.local == constraintMailbox.local && strings.EqualFold(mailbox.domain, constraintMailbox.domain), nil } // Otherwise the constraint is like a DNS constraint of the domain part // of the mailbox. return matchDomainConstraint(mailbox.domain, constraint) } func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { // From RFC 5280, Section 4.2.1.10: // “a uniformResourceIdentifier that does not include an authority // component with a host name specified as a fully qualified domain // name (e.g., if the URI either does not include an authority // component or includes an authority component in which the host name // is specified as an IP address), then the application MUST reject the // certificate.” host := uri.Host if len(host) == 0 { return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String()) } if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") { var err error host, _, err = net.SplitHostPort(uri.Host) if err != nil { return false, err } } if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") || net.ParseIP(host) != nil { return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) } return matchDomainConstraint(host, constraint) } func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { if len(ip) != len(constraint.IP) { return false, nil } for i := range ip { if mask := constraint.Mask[i]; ip[i]&mask != constraint.IP[i]&mask { return false, nil } } return true, nil } func matchDomainConstraint(domain, constraint string) (bool, error) { // The meaning of zero length constraints is not specified, but this // code follows NSS and accepts them as matching everything. if len(constraint) == 0 { return true, nil } domainLabels, ok := domainToReverseLabels(domain) if !ok { return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) } // RFC 5280 says that a leading period in a domain name means that at // least one label must be prepended, but only for URI and email // constraints, not DNS constraints. The code also supports that // behaviour for DNS constraints. mustHaveSubdomains := false if constraint[0] == '.' { mustHaveSubdomains = true constraint = constraint[1:] } constraintLabels, ok := domainToReverseLabels(constraint) if !ok { return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) } if len(domainLabels) < len(constraintLabels) || (mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) { return false, nil } for i, constraintLabel := range constraintLabels { if !strings.EqualFold(constraintLabel, domainLabels[i]) { return false, nil } } return true, nil } // checkNameConstraints checks that c permits a child certificate to claim the // given name, of type nameType. The argument parsedName contains the parsed // form of name, suitable for passing to the match function. The total number // of comparisons is tracked in the given count and should not exceed the given // limit. func (c *Certificate) checkNameConstraints(count *int, maxConstraintComparisons int, nameType string, name string, parsedName interface{}, match func(parsedName, constraint interface{}) (match bool, err error), permitted, excluded interface{}) error { excludedValue := reflect.ValueOf(excluded) *count += excludedValue.Len() if *count > maxConstraintComparisons { return CertificateInvalidError{c, TooManyConstraints, ""} } for i := 0; i < excludedValue.Len(); i++ { constraint := excludedValue.Index(i).Interface() match, err := match(parsedName, constraint) if err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } if match { return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)} } } permittedValue := reflect.ValueOf(permitted) *count += permittedValue.Len() if *count > maxConstraintComparisons { return CertificateInvalidError{c, TooManyConstraints, ""} } ok := true for i := 0; i < permittedValue.Len(); i++ { constraint := permittedValue.Index(i).Interface() var err error if ok, err = match(parsedName, constraint); err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } if ok { break } } if !ok { return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)} } return nil } // isValid performs validity checks on c given that it is a candidate to append // to the chain in currentChain. func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error { if !opts.DisableCriticalExtensionChecks && len(c.UnhandledCriticalExtensions) > 0 { return UnhandledCriticalExtension{ID: c.UnhandledCriticalExtensions[0]} } if !opts.DisableNameChecks && len(currentChain) > 0 { child := currentChain[len(currentChain)-1] if !bytes.Equal(child.RawIssuer, c.RawSubject) { return CertificateInvalidError{c, NameMismatch, ""} } } if !opts.DisableTimeChecks { now := opts.CurrentTime if now.IsZero() { now = time.Now() } if now.Before(c.NotBefore) { return CertificateInvalidError{ Cert: c, Reason: Expired, Detail: fmt.Sprintf("current time %s is before %s", now.Format(time.RFC3339), c.NotBefore.Format(time.RFC3339)), } } else if now.After(c.NotAfter) { return CertificateInvalidError{ Cert: c, Reason: Expired, Detail: fmt.Sprintf("current time %s is after %s", now.Format(time.RFC3339), c.NotAfter.Format(time.RFC3339)), } } } maxConstraintComparisons := opts.MaxConstraintComparisions if maxConstraintComparisons == 0 { maxConstraintComparisons = 250000 } comparisonCount := 0 var leaf *Certificate if certType == intermediateCertificate || certType == rootCertificate { if len(currentChain) == 0 { return errors.New("x509: internal error: empty chain when appending CA cert") } leaf = currentChain[0] } checkNameConstraints := !opts.DisableNameConstraintChecks && (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() if checkNameConstraints && leaf.commonNameAsHostname() { // This is the deprecated, legacy case of depending on the commonName as // a hostname. We don't enforce name constraints against the CN, but // VerifyHostname will look for hostnames in there if there are no SANs. // In order to ensure VerifyHostname will not accept an unchecked name, // return an error here. return CertificateInvalidError{c, NameConstraintsWithoutSANs, ""} } else if checkNameConstraints && leaf.hasSANExtension() { err := forEachSAN(leaf.getSANExtension(), func(tag int, data []byte) error { switch tag { case nameTypeEmail: name := string(data) mailbox, ok := parseRFC2821Mailbox(name) if !ok { return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox) } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, func(parsedName, constraint interface{}) (bool, error) { return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { return err } case nameTypeDNS: name := string(data) if _, ok := domainToReverseLabels(name); !ok { return fmt.Errorf("x509: cannot parse dnsName %q", name) } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, func(parsedName, constraint interface{}) (bool, error) { return matchDomainConstraint(parsedName.(string), constraint.(string)) }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { return err } case nameTypeURI: name := string(data) uri, err := url.Parse(name) if err != nil { return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name) } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, func(parsedName, constraint interface{}) (bool, error) { return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { return err } case nameTypeIP: ip := net.IP(data) if l := len(ip); l != net.IPv4len && l != net.IPv6len { return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data) } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, func(parsedName, constraint interface{}) (bool, error) { return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { return err } default: // Unknown SAN types are ignored. } return nil }) if err != nil { return err } } // KeyUsage status flags are ignored. From Engineering Security, Peter // Gutmann: A European government CA marked its signing certificates as // being valid for encryption only, but no-one noticed. Another // European CA marked its signature keys as not being valid for // signatures. A different CA marked its own trusted root certificate // as being invalid for certificate signing. Another national CA // distributed a certificate to be used to encrypt data for the // country’s tax authority that was marked as only being usable for // digital signatures but not for encryption. Yet another CA reversed // the order of the bit flags in the keyUsage due to confusion over // encoding endianness, essentially setting a random keyUsage in // certificates that it issued. Another CA created a self-invalidating // certificate by adding a certificate policy statement stipulating // that the certificate had to be used strictly as specified in the // keyUsage, and a keyUsage containing a flag indicating that the RSA // encryption key could only be used for Diffie-Hellman key agreement. if certType == intermediateCertificate && (!c.BasicConstraintsValid || !c.IsCA) { return CertificateInvalidError{c, NotAuthorizedToSign, ""} } if !opts.DisablePathLenChecks && c.BasicConstraintsValid && c.MaxPathLen >= 0 { numIntermediates := len(currentChain) - 1 if numIntermediates > c.MaxPathLen { return CertificateInvalidError{c, TooManyIntermediates, ""} } } return nil } // Verify attempts to verify c by building one or more chains from c to a // certificate in opts.Roots, using certificates in opts.Intermediates if // needed. If successful, it returns one or more chains where the first // element of the chain is c and the last element is from opts.Roots. // // If opts.Roots is nil and system roots are unavailable the returned error // will be of type SystemRootsError. // // Name constraints in the intermediates will be applied to all names claimed // in the chain, not just opts.DNSName. Thus it is invalid for a leaf to claim // example.com if an intermediate doesn't permit it, even if example.com is not // the name being validated. Note that DirectoryName constraints are not // supported. // // Extended Key Usage values are enforced down a chain, so an intermediate or // root that enumerates EKUs prevents a leaf from asserting an EKU not in that // list. // // WARNING: this function doesn't do any revocation checking. func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { // Platform-specific verification needs the ASN.1 contents so // this makes the behavior consistent across platforms. if len(c.Raw) == 0 { return nil, errNotParsed } if opts.Intermediates != nil { for _, intermediate := range opts.Intermediates.certs { if len(intermediate.Raw) == 0 { return nil, errNotParsed } } } // Use Windows's own verification and chain building. if opts.Roots == nil && runtime.GOOS == "windows" { return c.systemVerify(&opts) } if opts.Roots == nil { opts.Roots = systemRootsPool() if opts.Roots == nil { return nil, SystemRootsError{systemRootsErr} } } err = c.isValid(leafCertificate, nil, &opts) if err != nil { return } if len(opts.DNSName) > 0 { err = c.VerifyHostname(opts.DNSName) if err != nil { return } } var candidateChains [][]*Certificate if opts.Roots.contains(c) { candidateChains = append(candidateChains, []*Certificate{c}) } else { if candidateChains, err = c.buildChains(nil, []*Certificate{c}, nil, &opts); err != nil { return nil, err } } keyUsages := opts.KeyUsages if len(keyUsages) == 0 { keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth} } // If any key usage is acceptable then we're done. for _, usage := range keyUsages { if usage == ExtKeyUsageAny { return candidateChains, nil } } for _, candidate := range candidateChains { if opts.DisableEKUChecks || checkChainForKeyUsage(candidate, keyUsages) { chains = append(chains, candidate) } } if len(chains) == 0 { return nil, CertificateInvalidError{c, IncompatibleUsage, ""} } return chains, nil } func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate { n := make([]*Certificate, len(chain)+1) copy(n, chain) n[len(chain)] = cert return n } // maxChainSignatureChecks is the maximum number of CheckSignatureFrom calls // that an invocation of buildChains will (tranistively) make. Most chains are // less than 15 certificates long, so this leaves space for multiple chains and // for failed checks due to different intermediates having the same Subject. const maxChainSignatureChecks = 100 func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) { var ( hintErr error hintCert *Certificate ) considerCandidate := func(certType int, candidate *Certificate) { for _, cert := range currentChain { if cert.Equal(candidate) { return } } if sigChecks == nil { sigChecks = new(int) } *sigChecks++ if *sigChecks > maxChainSignatureChecks { err = errors.New("x509: signature check attempts limit reached while verifying certificate chain") return } if err := c.CheckSignatureFrom(candidate); err != nil { if hintErr == nil { hintErr = err hintCert = candidate } return } err = candidate.isValid(certType, currentChain, opts) if err != nil { return } switch certType { case rootCertificate: chains = append(chains, appendToFreshChain(currentChain, candidate)) case intermediateCertificate: if cache == nil { cache = make(map[*Certificate][][]*Certificate) } childChains, ok := cache[candidate] if !ok { childChains, err = candidate.buildChains(cache, appendToFreshChain(currentChain, candidate), sigChecks, opts) cache[candidate] = childChains } chains = append(chains, childChains...) } } for _, rootNum := range opts.Roots.findPotentialParents(c) { considerCandidate(rootCertificate, opts.Roots.certs[rootNum]) } for _, intermediateNum := range opts.Intermediates.findPotentialParents(c) { considerCandidate(intermediateCertificate, opts.Intermediates.certs[intermediateNum]) } if len(chains) > 0 { err = nil } if len(chains) == 0 && err == nil { err = UnknownAuthorityError{c, hintErr, hintCert} } return } // validHostname reports whether host is a valid hostname that can be matched or // matched against according to RFC 6125 2.2, with some leniency to accommodate // legacy values. func validHostname(host string) bool { host = strings.TrimSuffix(host, ".") if len(host) == 0 { return false } for i, part := range strings.Split(host, ".") { if part == "" { // Empty label. return false } if i == 0 && part == "*" { // Only allow full left-most wildcards, as those are the only ones // we match, and matching literal '*' characters is probably never // the expected behavior. continue } for j, c := range part { if 'a' <= c && c <= 'z' { continue } if '0' <= c && c <= '9' { continue } if 'A' <= c && c <= 'Z' { continue } if c == '-' && j != 0 { continue } if c == '_' || c == ':' { // Not valid characters in hostnames, but commonly // found in deployments outside the WebPKI. continue } return false } } return true } // commonNameAsHostname reports whether the Common Name field should be // considered the hostname that the certificate is valid for. This is a legacy // behavior, disabled if the Subject Alt Name extension is present. // // It applies the strict validHostname check to the Common Name field, so that // certificates without SANs can still be validated against CAs with name // constraints if there is no risk the CN would be matched as a hostname. // See NameConstraintsWithoutSANs and issue 24151. func (c *Certificate) commonNameAsHostname() bool { return !ignoreCN && !c.hasSANExtension() && validHostname(c.Subject.CommonName) } func matchHostnames(pattern, host string) bool { host = strings.TrimSuffix(host, ".") pattern = strings.TrimSuffix(pattern, ".") if len(pattern) == 0 || len(host) == 0 { return false } patternParts := strings.Split(pattern, ".") hostParts := strings.Split(host, ".") if len(patternParts) != len(hostParts) { return false } for i, patternPart := range patternParts { if i == 0 && patternPart == "*" { continue } if patternPart != hostParts[i] { return false } } return true } // toLowerCaseASCII returns a lower-case version of in. See RFC 6125 6.4.1. We use // an explicitly ASCII function to avoid any sharp corners resulting from // performing Unicode operations on DNS labels. func toLowerCaseASCII(in string) string { // If the string is already lower-case then there's nothing to do. isAlreadyLowerCase := true for _, c := range in { if c == utf8.RuneError { // If we get a UTF-8 error then there might be // upper-case ASCII bytes in the invalid sequence. isAlreadyLowerCase = false break } if 'A' <= c && c <= 'Z' { isAlreadyLowerCase = false break } } if isAlreadyLowerCase { return in } out := []byte(in) for i, c := range out { if 'A' <= c && c <= 'Z' { out[i] += 'a' - 'A' } } return string(out) } // VerifyHostname returns nil if c is a valid certificate for the named host. // Otherwise it returns an error describing the mismatch. func (c *Certificate) VerifyHostname(h string) error { // IP addresses may be written in [ ]. candidateIP := h if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' { candidateIP = h[1 : len(h)-1] } if ip := net.ParseIP(candidateIP); ip != nil { // We only match IP addresses against IP SANs. // See RFC 6125, Appendix B.2. for _, candidate := range c.IPAddresses { if ip.Equal(candidate) { return nil } } return HostnameError{c, candidateIP} } lowered := toLowerCaseASCII(h) if c.commonNameAsHostname() { if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) { return nil } } else { for _, match := range c.DNSNames { if matchHostnames(toLowerCaseASCII(match), lowered) { return nil } } } return HostnameError{c, h} } func checkChainForKeyUsage(chain []*Certificate, keyUsages []ExtKeyUsage) bool { usages := make([]ExtKeyUsage, len(keyUsages)) copy(usages, keyUsages) if len(chain) == 0 { return false } usagesRemaining := len(usages) // We walk down the list and cross out any usages that aren't supported // by each certificate. If we cross out all the usages, then the chain // is unacceptable. NextCert: for i := len(chain) - 1; i >= 0; i-- { cert := chain[i] if len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 { // The certificate doesn't have any extended key usage specified. continue } for _, usage := range cert.ExtKeyUsage { if usage == ExtKeyUsageAny { // The certificate is explicitly good for any usage. continue NextCert } } const invalidUsage ExtKeyUsage = -1 NextRequestedUsage: for i, requestedUsage := range usages { if requestedUsage == invalidUsage { continue } for _, usage := range cert.ExtKeyUsage { if requestedUsage == usage { continue NextRequestedUsage } else if requestedUsage == ExtKeyUsageServerAuth && (usage == ExtKeyUsageNetscapeServerGatedCrypto || usage == ExtKeyUsageMicrosoftServerGatedCrypto) { // In order to support COMODO // certificate chains, we have to // accept Netscape or Microsoft SGC // usages as equal to ServerAuth. continue NextRequestedUsage } } usages[i] = invalidUsage usagesRemaining-- if usagesRemaining == 0 { return false } } } return true } google-certificate-transparency-go-2308f62/x509/verify_test.go000066400000000000000000002761431462611535200242550ustar00rootroot00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/pem" "errors" "fmt" "math/big" "runtime" "strings" "testing" "time" "github.com/google/certificate-transparency-go/x509/pkix" ) type verifyTest struct { leaf string intermediates []string roots []string currentTime int64 dnsName string systemSkip bool keyUsages []ExtKeyUsage testSystemRootsError bool sha2 bool ignoreCN bool disableTimeChecks bool disableCriticalExtensionChecks bool disableNameChecks bool errorCallback func(*testing.T, int, error) bool expectedChains [][]string } var verifyTests = []verifyTest{ { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, currentTime: 1395785200, dnsName: "www.google.com", testSystemRootsError: true, // Without any roots specified we should get a system roots // error. errorCallback: expectSystemRootsError, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "www.google.com", expectedChains: [][]string{ {"Google", "Google Internet Authority", "GeoTrust"}, }, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "WwW.GooGLE.coM", expectedChains: [][]string{ {"Google", "Google Internet Authority", "GeoTrust"}, }, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "www.example.com", errorCallback: expectHostnameError("certificate is valid for"), }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "1.2.3.4", errorCallback: expectHostnameError("doesn't contain any IP SANs"), }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1, dnsName: "www.example.com", errorCallback: expectExpired, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1, dnsName: "www.google.com", disableTimeChecks: true, expectedChains: [][]string{ {"Google", "Google Internet Authority", "GeoTrust"}, }, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 10000000000, dnsName: "www.google.com", errorCallback: expectExpired, }, { leaf: googleLeaf, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 10000000000, dnsName: "www.google.com", disableTimeChecks: true, expectedChains: [][]string{ {"Google", "Google Internet Authority", "GeoTrust"}, }, }, { leaf: googleLeaf, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "www.google.com", // Skip when using systemVerify, since Windows // *will* find the missing intermediate cert. systemSkip: true, errorCallback: expectAuthorityUnknown, }, { leaf: googleLeaf, intermediates: []string{geoTrustRoot, giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "www.google.com", expectedChains: [][]string{ {"Google", "Google Internet Authority", "GeoTrust"}, }, // CAPI doesn't build the chain with the duplicated GeoTrust // entry so the results don't match. Thus we skip this test // until that's fixed. systemSkip: true, }, { leaf: dnssecExpLeaf, intermediates: []string{startComIntermediate}, roots: []string{startComRoot}, currentTime: 1302726541, expectedChains: [][]string{ {"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"}, }, }, { leaf: dnssecExpLeaf, intermediates: []string{startComIntermediate, startComRoot}, roots: []string{startComRoot}, currentTime: 1302726541, expectedChains: [][]string{ {"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"}, }, }, { leaf: googleLeafWithInvalidHash, intermediates: []string{giag2Intermediate}, roots: []string{geoTrustRoot}, currentTime: 1395785200, dnsName: "www.google.com", // The specific error message may not occur when using system // verification. systemSkip: true, errorCallback: expectHashError, }, { // The default configuration should reject an S/MIME chain. leaf: smimeLeaf, roots: []string{smimeIntermediate}, currentTime: 1339436154, // Key usage not implemented for Windows yet. systemSkip: true, errorCallback: expectUsageError, }, { leaf: smimeLeaf, roots: []string{smimeIntermediate}, currentTime: 1339436154, keyUsages: []ExtKeyUsage{ExtKeyUsageServerAuth}, // Key usage not implemented for Windows yet. systemSkip: true, errorCallback: expectUsageError, }, { leaf: smimeLeaf, roots: []string{smimeIntermediate}, currentTime: 1339436154, keyUsages: []ExtKeyUsage{ExtKeyUsageEmailProtection}, // Key usage not implemented for Windows yet. systemSkip: true, expectedChains: [][]string{ {"Ryan Hurst", "GlobalSign PersonalSign 2 CA - G2"}, }, }, { leaf: megaLeaf, intermediates: []string{comodoIntermediate1}, roots: []string{comodoRoot}, currentTime: 1360431182, // CryptoAPI can find alternative validation paths so we don't // perform this test with system validation. systemSkip: true, expectedChains: [][]string{ {"mega.co.nz", "EssentialSSL CA", "COMODO Certification Authority"}, }, }, { // Check that a name constrained intermediate works even when // it lists multiple constraints. leaf: nameConstraintsLeaf, intermediates: []string{nameConstraintsIntermediate1, nameConstraintsIntermediate2}, roots: []string{globalSignRoot}, currentTime: 1382387896, dnsName: "secure.iddl.vt.edu", expectedChains: [][]string{ { "Technology-enhanced Learning and Online Strategies", "Virginia Tech Global Qualified Server CA", "Trusted Root CA G2", "GlobalSign Root CA", }, }, }, { // Check that SHA-384 intermediates (which are popping up) // work. leaf: moipLeafCert, intermediates: []string{comodoIntermediateSHA384, comodoRSAAuthority}, roots: []string{addTrustRoot}, currentTime: 1397502195, dnsName: "api.moip.com.br", // CryptoAPI can find alternative validation paths so we don't // perform this test with system validation. systemSkip: true, sha2: true, expectedChains: [][]string{ { "api.moip.com.br", "COMODO RSA Extended Validation Secure Server CA", "COMODO RSA Certification Authority", "AddTrust External CA Root", }, }, }, { // Putting a certificate as a root directly should work as a // way of saying “exactly this”. leaf: selfSigned, roots: []string{selfSigned}, currentTime: 1471624472, dnsName: "foo.example", systemSkip: true, expectedChains: [][]string{ {"Acme Co"}, }, }, { // Putting a certificate as a root directly should not skip // other checks however. leaf: selfSigned, roots: []string{selfSigned}, currentTime: 1471624472, dnsName: "notfoo.example", systemSkip: true, errorCallback: expectHostnameError("certificate is valid for"), }, { // The issuer name in the leaf doesn't exactly match the // subject name in the root. Go does not perform // canonicalization and so should reject this. See issue 14955. leaf: issuerSubjectMatchLeaf, roots: []string{issuerSubjectMatchRoot}, currentTime: 1475787715, systemSkip: true, errorCallback: expectSubjectIssuerMismatchError, }, { leaf: issuerSubjectMatchLeaf, roots: []string{issuerSubjectMatchRoot}, currentTime: 1475787715, systemSkip: true, disableNameChecks: true, expectedChains: [][]string{ {"Leaf", "Root ca"}, }, }, { // An X.509 v1 certificate should not be accepted as an // intermediate. leaf: x509v1TestLeaf, intermediates: []string{x509v1TestIntermediate}, roots: []string{x509v1TestRoot}, currentTime: 1481753183, systemSkip: true, errorCallback: expectNotAuthorizedError, }, { // If any SAN extension is present (even one without any DNS // names), the CN should be ignored. leaf: ignoreCNWithSANLeaf, dnsName: "foo.example.com", roots: []string{ignoreCNWithSANRoot}, currentTime: 1486684488, systemSkip: true, errorCallback: expectHostnameError("certificate is not valid for any names"), }, { // Test that excluded names are respected. leaf: excludedNamesLeaf, dnsName: "bender.local", intermediates: []string{excludedNamesIntermediate}, roots: []string{excludedNamesRoot}, currentTime: 1486684488, systemSkip: true, errorCallback: expectNameConstraintsError, }, { // Test that unknown critical extensions in a leaf cause a // verify error. leaf: criticalExtLeafWithExt, dnsName: "example.com", intermediates: []string{criticalExtIntermediate}, roots: []string{criticalExtRoot}, currentTime: 1486684488, systemSkip: true, errorCallback: expectUnhandledCriticalExtension, }, { // Test that unknown critical extensions in an intermediate // cause a verify error. leaf: criticalExtLeaf, dnsName: "example.com", intermediates: []string{criticalExtIntermediateWithExt}, roots: []string{criticalExtRoot}, currentTime: 1486684488, systemSkip: true, errorCallback: expectUnhandledCriticalExtension, }, { leaf: criticalExtLeafWithExt, dnsName: "example.com", intermediates: []string{criticalExtIntermediate}, roots: []string{criticalExtRoot}, currentTime: 1486684488, systemSkip: true, disableCriticalExtensionChecks: true, expectedChains: [][]string{ { "example.com", "Intermediate", "Root", }, }, }, { leaf: criticalExtLeaf, dnsName: "example.com", intermediates: []string{criticalExtIntermediateWithExt}, roots: []string{criticalExtRoot}, currentTime: 1486684488, systemSkip: true, disableCriticalExtensionChecks: true, expectedChains: [][]string{ { "example.com", "Intermediate with Critical Extension", "Root", }, }, }, { // Test that invalid CN are ignored. leaf: invalidCNWithoutSAN, dnsName: "foo,invalid", roots: []string{invalidCNRoot}, currentTime: 1540000000, systemSkip: true, errorCallback: expectHostnameError("Common Name is not a valid hostname"), }, { // Test that valid CN are respected. leaf: validCNWithoutSAN, dnsName: "foo.example.com", roots: []string{invalidCNRoot}, currentTime: 1540000000, systemSkip: true, expectedChains: [][]string{ {"foo.example.com", "Test root"}, }, }, // Replicate CN tests with ignoreCN = true { leaf: ignoreCNWithSANLeaf, dnsName: "foo.example.com", roots: []string{ignoreCNWithSANRoot}, currentTime: 1486684488, systemSkip: true, ignoreCN: true, errorCallback: expectHostnameError("certificate is not valid for any names"), }, { leaf: invalidCNWithoutSAN, dnsName: "foo,invalid", roots: []string{invalidCNRoot}, currentTime: 1540000000, systemSkip: true, ignoreCN: true, errorCallback: expectHostnameError("Common Name is not a valid hostname"), }, { leaf: validCNWithoutSAN, dnsName: "foo.example.com", roots: []string{invalidCNRoot}, currentTime: 1540000000, systemSkip: true, ignoreCN: true, errorCallback: expectHostnameError("not valid for any names"), }, { // A certificate with an AKID should still chain to a parent without SKID. // See Issue 30079. leaf: leafWithAKID, roots: []string{rootWithoutSKID}, currentTime: 1550000000, dnsName: "example", systemSkip: true, expectedChains: [][]string{ {"Acme LLC", "Acme Co"}, }, }, } func expectHostnameError(msg string) func(*testing.T, int, error) bool { return func(t *testing.T, i int, err error) (ok bool) { if _, ok := err.(HostnameError); !ok { t.Errorf("#%d: error was not a HostnameError: %v", i, err) return false } if !strings.Contains(err.Error(), msg) { t.Errorf("#%d: HostnameError did not contain %q: %v", i, msg, err) } return true } } func expectExpired(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != Expired { t.Errorf("#%d: error was not Expired: %v", i, err) return false } return true } func expectUsageError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != IncompatibleUsage { t.Errorf("#%d: error was not IncompatibleUsage: %v", i, err) return false } return true } func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) { e, ok := err.(UnknownAuthorityError) if !ok { t.Errorf("#%d: error was not UnknownAuthorityError: %v", i, err) return false } if e.Cert == nil { t.Errorf("#%d: error was UnknownAuthorityError, but missing Cert: %v", i, err) return false } return true } func expectSystemRootsError(t *testing.T, i int, err error) bool { if _, ok := err.(SystemRootsError); !ok { t.Errorf("#%d: error was not SystemRootsError: %v", i, err) return false } return true } func expectHashError(t *testing.T, i int, err error) bool { if err == nil { t.Errorf("#%d: no error resulted from invalid hash", i) return false } if expected := "algorithm unimplemented"; !strings.Contains(err.Error(), expected) { t.Errorf("#%d: error resulting from invalid hash didn't contain '%s', rather it was: %v", i, expected, err) return false } return true } func expectSubjectIssuerMismatchError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NameMismatch { t.Errorf("#%d: error was not a NameMismatch: %v", i, err) return false } return true } func expectNameConstraintsError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != CANotAuthorizedForThisName { t.Errorf("#%d: error was not a CANotAuthorizedForThisName: %v", i, err) return false } return true } func expectNotAuthorizedError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NotAuthorizedToSign { t.Errorf("#%d: error was not a NotAuthorizedToSign: %v", i, err) return false } return true } func expectUnhandledCriticalExtension(t *testing.T, i int, err error) (ok bool) { if _, ok := err.(UnhandledCriticalExtension); !ok { t.Errorf("#%d: error was not an UnhandledCriticalExtension: %v", i, err) return false } return true } func certificateFromPEM(pemBytes string) (*Certificate, error) { block, _ := pem.Decode([]byte(pemBytes)) if block == nil { return nil, errors.New("failed to decode PEM") } return ParseCertificate(block.Bytes) } func testVerify(t *testing.T, useSystemRoots bool) { defer func(savedIgnoreCN bool) { ignoreCN = savedIgnoreCN }(ignoreCN) for i, test := range verifyTests { if useSystemRoots && test.systemSkip { continue } if runtime.GOOS == "windows" && test.testSystemRootsError { continue } ignoreCN = test.ignoreCN opts := VerifyOptions{ Intermediates: NewCertPool(), DNSName: test.dnsName, CurrentTime: time.Unix(test.currentTime, 0), KeyUsages: test.keyUsages, DisableTimeChecks: test.disableTimeChecks, DisableCriticalExtensionChecks: test.disableCriticalExtensionChecks, DisableNameChecks: test.disableNameChecks, } if !useSystemRoots { opts.Roots = NewCertPool() for j, root := range test.roots { ok := opts.Roots.AppendCertsFromPEM([]byte(root)) if !ok { t.Errorf("#%d: failed to parse root #%d", i, j) return } } } for j, intermediate := range test.intermediates { ok := opts.Intermediates.AppendCertsFromPEM([]byte(intermediate)) if !ok { t.Errorf("#%d: failed to parse intermediate #%d", i, j) return } } leaf, err := certificateFromPEM(test.leaf) if IsFatal(err) { t.Errorf("#%d: failed to parse leaf: %v", i, err) return } var oldSystemRoots *CertPool if test.testSystemRootsError { oldSystemRoots = systemRootsPool() systemRoots = nil opts.Roots = nil } chains, err := leaf.Verify(opts) if test.testSystemRootsError { systemRoots = oldSystemRoots } if test.errorCallback == nil && err != nil { t.Errorf("#%d: unexpected error: %v", i, err) } if test.errorCallback != nil { if !test.errorCallback(t, i, err) { return } } if len(chains) != len(test.expectedChains) { t.Errorf("#%d: wanted %d chains, got %d", i, len(test.expectedChains), len(chains)) } // We check that each returned chain matches a chain from // expectedChains but an entry in expectedChains can't match // two chains. seenChains := make([]bool, len(chains)) NextOutputChain: for _, chain := range chains { TryNextExpected: for j, expectedChain := range test.expectedChains { if seenChains[j] { continue } if len(chain) != len(expectedChain) { continue } for k, cert := range chain { if !strings.Contains(nameToKey(&cert.Subject), expectedChain[k]) { continue TryNextExpected } } // we matched seenChains[j] = true continue NextOutputChain } t.Errorf("#%d: No expected chain matched %s", i, chainToDebugString(chain)) } } } func TestGoVerify(t *testing.T) { testVerify(t, false) } func TestSystemVerify(t *testing.T) { if runtime.GOOS != "windows" { t.Skipf("skipping verify test using system APIs on %q", runtime.GOOS) } testVerify(t, true) } func chainToDebugString(chain []*Certificate) string { var chainStr string for _, cert := range chain { if len(chainStr) > 0 { chainStr += " -> " } chainStr += nameToKey(&cert.Subject) } return chainStr } func nameToKey(name *pkix.Name) string { return strings.Join(name.Country, ",") + "/" + strings.Join(name.Organization, ",") + "/" + strings.Join(name.OrganizationalUnit, ",") + "/" + name.CommonName } const geoTrustRoot = `-----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- ` const giag2Intermediate = `-----BEGIN CERTIFICATE----- MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7 qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY /iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/ zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6 yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx -----END CERTIFICATE----- ` const googleLeaf = `-----BEGIN CERTIFICATE----- MIIEdjCCA16gAwIBAgIIcR5k4dkoe04wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMzEyMDkzODMwWhcNMTQwNjEwMDAwMDAw WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3 Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4zYCe m0oUBhwE0EwBr65eBOcgcQO2PaSIAB2dEP/c1EMX2tOy0ov8rk83ePhJ+MWdT1z6 jge9X4zQQI8ZyA9qIiwrKBZOi8DNUvrqNZC7fJAVRrb9aX/99uYOJCypIbpmWG1q fhbHjJewhwf8xYPj71eU4rLG80a+DapWmphtfq3h52lDQIBzLVf1yYbyrTaELaz4 NXF7HXb5YkId/gxIsSzM0aFUVu2o8sJcLYAsJqwfFKBKOMxUcn545nlspf0mTcWZ 0APlbwsKznNs4/xCDwIxxWjjqgHrYAFl6y07i1gzbAOqdNEyR24p+3JWI8WZBlBI dk2KGj0W1fIfsvyxAgMBAAGjggFBMIIBPTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0 MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G A1UdDgQWBBTXD5Bx6iqT+dmEhbFL4OUoHyZn8zAMBgNVHRMBAf8EAjAAMB8GA1Ud IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMBcGA1UdIAQQMA4wDAYKKwYBBAHW eQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lB RzIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCR3RJtHzgDh33b/MI1ugiki+nl8Ikj 5larbJRE/rcA5oite+QJyAr6SU1gJJ/rRrK3ItVEHr9L621BCM7GSdoNMjB9MMcf tJAW0kYGJ+wqKm53wG/JaOADTnnq2Mt/j6F2uvjgN/ouns1nRHufIvd370N0LeH+ orKqTuAPzXK7imQk6+OycYABbqCtC/9qmwRd8wwn7sF97DtYfK8WuNHtFalCAwyi 8LxJJYJCLWoMhZ+V8GZm+FOex5qkQAjnZrtNlbQJ8ro4r+rpKXtmMFFhfa+7L+PA Kom08eUK8skxAzfDDijZPh10VtJ66uBoiDPdT+uCBehcBIcmSTrKjFGX -----END CERTIFICATE----- ` // googleLeafWithInvalidHash is the same as googleLeaf, but the signature // algorithm in the certificate contains a nonsense OID. const googleLeafWithInvalidHash = `-----BEGIN CERTIFICATE----- MIIEdjCCA16gAwIBAgIIcR5k4dkoe04wDQYJKoZIhvcNAWAFBQAwSTELMAkGA1UE BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMzEyMDkzODMwWhcNMTQwNjEwMDAwMDAw WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3 Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4zYCe m0oUBhwE0EwBr65eBOcgcQO2PaSIAB2dEP/c1EMX2tOy0ov8rk83ePhJ+MWdT1z6 jge9X4zQQI8ZyA9qIiwrKBZOi8DNUvrqNZC7fJAVRrb9aX/99uYOJCypIbpmWG1q fhbHjJewhwf8xYPj71eU4rLG80a+DapWmphtfq3h52lDQIBzLVf1yYbyrTaELaz4 NXF7HXb5YkId/gxIsSzM0aFUVu2o8sJcLYAsJqwfFKBKOMxUcn545nlspf0mTcWZ 0APlbwsKznNs4/xCDwIxxWjjqgHrYAFl6y07i1gzbAOqdNEyR24p+3JWI8WZBlBI dk2KGj0W1fIfsvyxAgMBAAGjggFBMIIBPTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0 MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G A1UdDgQWBBTXD5Bx6iqT+dmEhbFL4OUoHyZn8zAMBgNVHRMBAf8EAjAAMB8GA1Ud IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMBcGA1UdIAQQMA4wDAYKKwYBBAHW eQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lB RzIuY3JsMA0GCSqGSIb3DQFgBQUAA4IBAQCR3RJtHzgDh33b/MI1ugiki+nl8Ikj 5larbJRE/rcA5oite+QJyAr6SU1gJJ/rRrK3ItVEHr9L621BCM7GSdoNMjB9MMcf tJAW0kYGJ+wqKm53wG/JaOADTnnq2Mt/j6F2uvjgN/ouns1nRHufIvd370N0LeH+ orKqTuAPzXK7imQk6+OycYABbqCtC/9qmwRd8wwn7sF97DtYfK8WuNHtFalCAwyi 8LxJJYJCLWoMhZ+V8GZm+FOex5qkQAjnZrtNlbQJ8ro4r+rpKXtmMFFhfa+7L+PA Kom08eUK8skxAzfDDijZPh10VtJ66uBoiDPdT+uCBehcBIcmSTrKjFGX -----END CERTIFICATE----- ` const dnssecExpLeaf = `-----BEGIN CERTIFICATE----- MIIGzTCCBbWgAwIBAgIDAdD6MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg MSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTAwNzA0MTQ1MjQ1 WhcNMTEwNzA1MTA1NzA0WjCBwTEgMB4GA1UEDRMXMjIxMTM3LWxpOWE5dHhJRzZM NnNyVFMxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVQZXJzb25hIE5vdCBWYWxpZGF0 ZWQxKTAnBgNVBAsTIFN0YXJ0Q29tIEZyZWUgQ2VydGlmaWNhdGUgTWVtYmVyMRsw GQYDVQQDExJ3d3cuZG5zc2VjLWV4cC5vcmcxKDAmBgkqhkiG9w0BCQEWGWhvc3Rt YXN0ZXJAZG5zc2VjLWV4cC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQDEdF/22vaxrPbqpgVYMWi+alfpzBctpbfLBdPGuqOazJdCT0NbWcK8/+B4 X6OlSOURNIlwLzhkmwVsWdVv6dVSaN7d4yI/fJkvgfDB9+au+iBJb6Pcz8ULBfe6 D8HVvqKdORp6INzHz71z0sghxrQ0EAEkoWAZLh+kcn2ZHdcmZaBNUfjmGbyU6PRt RjdqoP+owIaC1aktBN7zl4uO7cRjlYFdusINrh2kPP02KAx2W84xjxX1uyj6oS6e 7eBfvcwe8czW/N1rbE0CoR7h9+HnIrjnVG9RhBiZEiw3mUmF++Up26+4KTdRKbu3 +BL4yMpfd66z0+zzqu+HkvyLpFn5AgMBAAGjggL/MIIC+zAJBgNVHRMEAjAAMAsG A1UdDwQEAwIDqDATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUy04I5guM drzfh2JQaXhgV86+4jUwHwYDVR0jBBgwFoAU60I00Jiwq5/0G2sI98xkLu8OLEUw LQYDVR0RBCYwJIISd3d3LmRuc3NlYy1leHAub3Jngg5kbnNzZWMtZXhwLm9yZzCC AUIGA1UdIASCATkwggE1MIIBMQYLKwYBBAGBtTcBAgIwggEgMC4GCCsGAQUFBwIB FiJodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kucGRmMDQGCCsGAQUFBwIB FihodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9pbnRlcm1lZGlhdGUucGRmMIG3Bggr BgEFBQcCAjCBqjAUFg1TdGFydENvbSBMdGQuMAMCAQEagZFMaW1pdGVkIExpYWJp bGl0eSwgc2VlIHNlY3Rpb24gKkxlZ2FsIExpbWl0YXRpb25zKiBvZiB0aGUgU3Rh cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUG9saWN5IGF2YWlsYWJsZSBh dCBodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kucGRmMGEGA1UdHwRaMFgw KqAooCaGJGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2NydDEtY3JsLmNybDAqoCig JoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMIGOBggrBgEF BQcBAQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5zdGFydHNzbC5jb20v c3ViL2NsYXNzMS9zZXJ2ZXIvY2EwQgYIKwYBBQUHMAKGNmh0dHA6Ly93d3cuc3Rh cnRzc2wuY29tL2NlcnRzL3N1Yi5jbGFzczEuc2VydmVyLmNhLmNydDAjBgNVHRIE HDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8wDQYJKoZIhvcNAQEFBQADggEB ACXj6SB59KRJPenn6gUdGEqcta97U769SATyiQ87i9er64qLwvIGLMa3o2Rcgl2Y kghUeyLdN/EXyFBYA8L8uvZREPoc7EZukpT/ZDLXy9i2S0jkOxvF2fD/XLbcjGjM iEYG1/6ASw0ri9C0k4oDDoJLCoeH9++yqF7SFCCMcDkJqiAGXNb4euDpa8vCCtEQ CSS+ObZbfkreRt3cNCf5LfCXe9OsTnCfc8Cuq81c0oLaG+SmaLUQNBuToq8e9/Zm +b+/a3RVjxmkV5OCcGVBxsXNDn54Q6wsdw0TBMcjwoEndzpLS7yWgFbbkq5ZiGpw Qibb2+CfKuQ+WFV1GkVQmVA= -----END CERTIFICATE-----` const startComIntermediate = `-----BEGIN CERTIFICATE----- MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0 YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/ ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5 xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0 YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3 dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0 c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0 BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un 0q6Dp6jOW6c= -----END CERTIFICATE-----` const startComRoot = `-----BEGIN CERTIFICATE----- MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w +2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B 26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ 9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= -----END CERTIFICATE-----` const smimeLeaf = `-----BEGIN CERTIFICATE----- MIIFBjCCA+6gAwIBAgISESFvrjT8XcJTEe6rBlPptILlMA0GCSqGSIb3DQEBBQUA MFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSowKAYD VQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAyIENBIC0gRzIwHhcNMTIwMTIz MTYzNjU5WhcNMTUwMTIzMTYzNjU5WjCBlDELMAkGA1UEBhMCVVMxFjAUBgNVBAgT DU5ldyBIYW1zcGhpcmUxEzARBgNVBAcTClBvcnRzbW91dGgxGTAXBgNVBAoTEEds b2JhbFNpZ24sIEluYy4xEzARBgNVBAMTClJ5YW4gSHVyc3QxKDAmBgkqhkiG9w0B CQEWGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQC4ASSTvavmsFQAob60ukSSwOAL9nT/s99ltNUCAf5fPH5j NceMKxaQse2miOmRRIXaykcq1p/TbI70Ztce38r2mbOwqDHHPVi13GxJEyUXWgaR BteDMu5OGyWNG1kchVsGWpbstT0Z4v0md5m1BYFnxB20ebJyOR2lXDxsFK28nnKV +5eMj76U8BpPQ4SCH7yTMG6y0XXsB3cCrBKr2o3TOYgEKv+oNnbaoMt3UxMt9nSf 9jyIshjqfnT5Aew3CUNMatO55g5FXXdIukAweg1YSb1ls05qW3sW00T3d7dQs9/7 NuxCg/A2elmVJSoy8+MLR8JSFEf/aMgjO/TyLg/jAgMBAAGjggGPMIIBizAOBgNV HQ8BAf8EBAMCBaAwTQYDVR0gBEYwRDBCBgorBgEEAaAyASgKMDQwMgYIKwYBBQUH AgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMCQGA1Ud EQQdMBuBGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wCQYDVR0TBAIwADAdBgNV HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwQwYDVR0fBDwwOjA4oDagNIYyaHR0 cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9nc3BlcnNvbmFsc2lnbjJnMi5jcmww VQYIKwYBBQUHAQEESTBHMEUGCCsGAQUFBzAChjlodHRwOi8vc2VjdXJlLmdsb2Jh bHNpZ24uY29tL2NhY2VydC9nc3BlcnNvbmFsc2lnbjJnMi5jcnQwHQYDVR0OBBYE FFWiECe0/L72eVYqcWYnLV6SSjzhMB8GA1UdIwQYMBaAFD8V0m18L+cxnkMKBqiU bCw7xe5lMA0GCSqGSIb3DQEBBQUAA4IBAQAhQi6hLPeudmf3IBF4IDzCvRI0FaYd BKfprSk/H0PDea4vpsLbWpA0t0SaijiJYtxKjlM4bPd+2chb7ejatDdyrZIzmDVy q4c30/xMninGKokpYA11/Ve+i2dvjulu65qasrtQRGybAuuZ67lrp/K3OMFgjV5N C3AHYLzvNU4Dwc4QQ1BaMOg6KzYSrKbABRZajfrpC9uiePsv7mDIXLx/toBPxWNl a5vJm5DrZdn7uHdvBCE6kMykbOLN5pmEK0UIlwKh6Qi5XD0pzlVkEZliFkBMJgub d/eF7xeg7TKPWC5xyOFp9SdMolJM7LTC3wnSO3frBAev+q/nGs9Xxyvs -----END CERTIFICATE-----` const smimeIntermediate = `-----BEGIN CERTIFICATE----- MIIEFjCCAv6gAwIBAgILBAAAAAABL07hL1IwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw MDBaFw0xOTA0MTMxMDAwMDBaMFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i YWxTaWduIG52LXNhMSowKAYDVQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAy IENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBa0H5Nez4 En3dIlFpX7e5E0YndxQ74xOBbz7kdBd+DLX0LOQMjVPU3DAgKL9ujhH+ZhHkURbH 3X/94TQSUL/z2JjsaQvS0NqyZXHhM5eeuquzOJRzEQ8+odETzHg2G0Erv7yjSeww gkwDWDJnYUDlOjYTDUEG6+i+8Mn425reo4I0E277wD542kmVWeW7+oHv5dZo9e1Q yWwiKTEP6BEQVVSBgThXMG4traSSDRUt3T1eQTZx5EObpiBEBO4OTqiBTJfg4vEI YgkXzKLpnfszTB6YMDpR9/QS6p3ANB3kfAb+t6udSO3WCst0DGrwHDLBFGDR4UeY T5KGGnI7cWL7AgMBAAGjgeUwgeIwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI MAYBAf8CAQAwHQYDVR0OBBYEFD8V0m18L+cxnkMKBqiUbCw7xe5lMEcGA1UdIARA MD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWdu LmNvbS9yZXBvc2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmds b2JhbHNpZ24ubmV0L3Jvb3QuY3JsMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTN NKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBDc3nMpMxJMQMcYUCB3+C73UpvwDE8 eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX YEvTWbWwGdPytDFPYIl3/6OqNSXSnZ7DxPcdLJq2uyiga8PB/TTIIHYkdM2+1DE0 7y3rH/7TjwDVD7SLu5/SdOfKskuMPTjOEvz3K161mymW06klVhubCIWOro/Gx1Q2 2FQOZ7/2k4uYoOdBTSlb8kTAuzZNgIE0rB2BIYCTz/P6zZIKW0ogbRSH -----END CERTIFICATE-----` var megaLeaf = `-----BEGIN CERTIFICATE----- MIIFOjCCBCKgAwIBAgIQWYE8Dup170kZ+k11Lg51OjANBgkqhkiG9w0BAQUFADBy MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEYMBYGA1UE AxMPRXNzZW50aWFsU1NMIENBMB4XDTEyMTIxNDAwMDAwMFoXDTE0MTIxNDIzNTk1 OVowfzEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMS4wLAYDVQQL EyVIb3N0ZWQgYnkgSW5zdHJhIENvcnBvcmF0aW9uIFB0eS4gTFREMRUwEwYDVQQL EwxFc3NlbnRpYWxTU0wxEzARBgNVBAMTCm1lZ2EuY28ubnowggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDcxMCClae8BQIaJHBUIVttlLvhbK4XhXPk3RQ3 G5XA6tLZMBQ33l3F9knYJ0YErXtr8IdfYoulRQFmKFMJl9GtWyg4cGQi2Rcr5VN5 S5dA1vu4oyJBxE9fPELcK6Yz1vqaf+n6za+mYTiQYKggVdS8/s8hmNuXP9Zk1pIn +q0pGsf8NAcSHMJgLqPQrTDw+zae4V03DvcYfNKjuno88d2226ld7MAmQZ7uRNsI /CnkdelVs+akZsXf0szefSqMJlf08SY32t2jj4Ra7RApVYxOftD9nij/aLfuqOU6 ow6IgIcIG2ZvXLZwK87c5fxL7UAsTTV+M1sVv8jA33V2oKLhAgMBAAGjggG9MIIB uTAfBgNVHSMEGDAWgBTay+qtWwhdzP/8JlTOSeVVxjj0+DAdBgNVHQ4EFgQUmP9l 6zhyrZ06Qj4zogt+6LKFk4AwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAw NAYDVR0lBC0wKwYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDBglghkgB hvhCBAEwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkGCCsGAQUFBwIBFh1o dHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwBAgEwOwYDVR0fBDQw MjAwoC6gLIYqaHR0cDovL2NybC5jb21vZG9jYS5jb20vRXNzZW50aWFsU1NMQ0Eu Y3JsMG4GCCsGAQUFBwEBBGIwYDA4BggrBgEFBQcwAoYsaHR0cDovL2NydC5jb21v ZG9jYS5jb20vRXNzZW50aWFsU1NMQ0FfMi5jcnQwJAYIKwYBBQUHMAGGGGh0dHA6 Ly9vY3NwLmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcggptZWdhLmNvLm56gg53d3cu bWVnYS5jby5uejANBgkqhkiG9w0BAQUFAAOCAQEAcYhrsPSvDuwihMOh0ZmRpbOE Gw6LqKgLNTmaYUPQhzi2cyIjhUhNvugXQQlP5f0lp5j8cixmArafg1dTn4kQGgD3 ivtuhBTgKO1VYB/VRoAt6Lmswg3YqyiS7JiLDZxjoV7KoS5xdiaINfHDUaBBY4ZH j2BUlPniNBjCqXe/HndUTVUewlxbVps9FyCmH+C4o9DWzdGBzDpCkcmo5nM+cp7q ZhTIFTvZfo3zGuBoyu8BzuopCJcFRm3cRiXkpI7iOMUIixO1szkJS6WpL1sKdT73 UXp08U0LBqoqG130FbzEJBBV3ixbvY6BWMHoCWuaoF12KJnC5kHt2RoWAAgMXA== -----END CERTIFICATE-----` var comodoIntermediate1 = `-----BEGIN CERTIFICATE----- MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf 5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR 0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU 2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv +Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk -----END CERTIFICATE-----` var comodoRoot = `-----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI 2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp +2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW /zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== -----END CERTIFICATE-----` var nameConstraintsLeaf = `-----BEGIN CERTIFICATE----- MIIHMTCCBRmgAwIBAgIIIZaV/3ezOJkwDQYJKoZIhvcNAQEFBQAwgcsxCzAJBgNV BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEj MCEGA1UECxMaR2xvYmFsIFF1YWxpZmllZCBTZXJ2ZXIgQ0ExPDA6BgNVBAoTM1Zp cmdpbmlhIFBvbHl0ZWNobmljIEluc3RpdHV0ZSBhbmQgU3RhdGUgVW5pdmVyc2l0 eTExMC8GA1UEAxMoVmlyZ2luaWEgVGVjaCBHbG9iYWwgUXVhbGlmaWVkIFNlcnZl ciBDQTAeFw0xMzA5MTkxNDM2NTVaFw0xNTA5MTkxNDM2NTVaMIHNMQswCQYDVQQG EwJVUzERMA8GA1UECAwIVmlyZ2luaWExEzARBgNVBAcMCkJsYWNrc2J1cmcxPDA6 BgNVBAoMM1ZpcmdpbmlhIFBvbHl0ZWNobmljIEluc3RpdHV0ZSBhbmQgU3RhdGUg VW5pdmVyc2l0eTE7MDkGA1UECwwyVGVjaG5vbG9neS1lbmhhbmNlZCBMZWFybmlu ZyBhbmQgT25saW5lIFN0cmF0ZWdpZXMxGzAZBgNVBAMMEnNlY3VyZS5pZGRsLnZ0 LmVkdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkOyPpsOK/6IuPG WnIBlVwlHzeYf+cUlggqkLq0b0+vZbiTXgio9/VCuNQ8opSoss7J7o3ygV9to+9Y YwJKVC5WDT/y5JWpQey0CWILymViJnpNSwnxBc8A+Q8w5NUGDd/UhtPx/U8/hqbd WPDYj2hbOqyq8UlRhfS5pwtnv6BbCTaY11I6FhCLK7zttISyTuWCf9p9o/ggiipP ii/5oh4dkl+r5SfuSp5GPNHlYO8lWqys5NAPoDD4fc/kuflcK7Exx7XJ+Oqu0W0/ psjEY/tES1ZgDWU/ParcxxFpFmKHbD5DXsfPOObzkVWXIY6tGMutSlE1Froy/Nn0 OZsAOrcCAwEAAaOCAhMwggIPMIG4BggrBgEFBQcBAQSBqzCBqDBYBggrBgEFBQcw AoZMaHR0cDovL3d3dy5wa2kudnQuZWR1L2dsb2JhbHF1YWxpZmllZHNlcnZlci9j YWNlcnQvZ2xvYmFscXVhbGlmaWVkc2VydmVyLmNydDBMBggrBgEFBQcwAYZAaHR0 cDovL3Z0Y2EtcC5lcHJvdi5zZXRpLnZ0LmVkdTo4MDgwL2VqYmNhL3B1YmxpY3dl Yi9zdGF0dXMvb2NzcDAdBgNVHQ4EFgQUp7xbO6iHkvtZbPE4jmndmnAbSEcwDAYD VR0TAQH/BAIwADAfBgNVHSMEGDAWgBS8YmAn1eM1SBfpS6tFatDIqHdxjDBqBgNV HSAEYzBhMA4GDCsGAQQBtGgFAgICATAOBgwrBgEEAbRoBQICAQEwPwYMKwYBBAG0 aAUCAgMBMC8wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9nbG9i YWwvY3BzLzBKBgNVHR8EQzBBMD+gPaA7hjlodHRwOi8vd3d3LnBraS52dC5lZHUv Z2xvYmFscXVhbGlmaWVkc2VydmVyL2NybC9jYWNybC5jcmwwDgYDVR0PAQH/BAQD AgTwMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHREEFjAUghJz ZWN1cmUuaWRkbC52dC5lZHUwDQYJKoZIhvcNAQEFBQADggIBAEgoYo4aUtatY3gI OyyKp7QlIOaLbTJZywESHqy+L5EGDdJW2DJV+mcE0LDGvqa2/1Lo+AR1ntsZwfOi Y718JwgVVaX/RCd5+QKP25c5/x72xI8hb/L1bgS0ED9b0YAhd7Qm1K1ot82+6mqX DW6WiGeDr8Z07MQ3143qQe2rBlq+QI69DYzm2GOqAIAnUIWv7tCyLUm31b4DwmrJ TeudVreTKUbBNB1TWRFHEPkWhjjXKZnNGRO11wHXcyBu6YekIvVZ+vmx8ePee4jJ 3GFOi7lMuWOeq57jTVL7KOKaKLVXBb6gqo5aq+Wwt8RUD5MakrCAEeQZj7DKaFmZ oQCO0Pxrsl3InCGvxnGzT+bFVO9nJ/BAMj7hknFdm9Jr6Bg5q33Z+gnf909AD9QF ESqUSykaHu2LVdJx2MaCH1CyKnRgMw5tEwE15EXpUjCm24m8FMOYC+rNtf18pgrz 5D8Jhh+oxK9PjcBYqXNtnioIxiMCYcV0q5d4w4BYFEh71tk7/bYB0R55CsBUVPmp timWNOdRd57Tfpk3USaVsumWZAf9MP3wPiC7gb4d5tYEEAG5BuDT8ruFw838wU8G 1VvAVutSiYBg7k3NYO7AUqZ+Ax4klQX3aM9lgonmJ78Qt94UPtbptrfZ4/lSqEf8 GBUwDrQNTb+gsXsDkjd5lcYxNx6l -----END CERTIFICATE-----` var nameConstraintsIntermediate1 = `-----BEGIN CERTIFICATE----- MIINLjCCDBagAwIBAgIRIqpyf/YoGgvHc8HiDAxAI8owDQYJKoZIhvcNAQEFBQAw XDELMAkGA1UEBhMCQkUxFTATBgNVBAsTDFRydXN0ZWQgUm9vdDEZMBcGA1UEChMQ R2xvYmFsU2lnbiBudi1zYTEbMBkGA1UEAxMSVHJ1c3RlZCBSb290IENBIEcyMB4X DTEyMTIxMzAwMDAwMFoXDTE3MTIxMzAwMDAwMFowgcsxCzAJBgNVBAYTAlVTMREw DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEjMCEGA1UECxMa R2xvYmFsIFF1YWxpZmllZCBTZXJ2ZXIgQ0ExPDA6BgNVBAoTM1ZpcmdpbmlhIFBv bHl0ZWNobmljIEluc3RpdHV0ZSBhbmQgU3RhdGUgVW5pdmVyc2l0eTExMC8GA1UE AxMoVmlyZ2luaWEgVGVjaCBHbG9iYWwgUXVhbGlmaWVkIFNlcnZlciBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALgIZhEaptBWADBqdJ45ueFGzMXa GHnzNxoxR1fQIaaRQNdCg4cw3A4dWKMeEgYLtsp65ai3Xfw62Qaus0+KJ3RhgV+r ihqK81NUzkls78fJlADVDI4fCTlothsrE1CTOMiy97jKHai5mVTiWxmcxpmjv7fm 5Nhc+uHgh2hIz6npryq495mD51ZrUTIaqAQN6Pw/VHfAmR524vgriTOjtp1t4lA9 pXGWjF/vkhAKFFheOQSQ00rngo2wHgCqMla64UTN0oz70AsCYNZ3jDLx0kOP0YmM R3Ih91VA63kLqPXA0R6yxmmhhxLZ5bcyAy1SLjr1N302MIxLM/pSy6aquEnbELhz qyp9yGgRyGJay96QH7c4RJY6gtcoPDbldDcHI9nXngdAL4DrZkJ9OkDkJLyqG66W ZTF5q4EIs6yMdrywz0x7QP+OXPJrjYpbeFs6tGZCFnWPFfmHCRJF8/unofYrheq+ 9J7Jx3U55S/k57NXbAM1RAJOuMTlfn9Etf9Dpoac9poI4Liav6rBoUQk3N3JWqnV HNx/NdCyJ1/6UbKMJUZsStAVglsi6lVPo289HHOE4f7iwl3SyekizVOp01wUin3y cnbZB/rXmZbwapSxTTSBf0EIOr9i4EGfnnhCAVA9U5uLrI5OEB69IY8PNX0071s3 Z2a2fio5c8m3JkdrAgMBAAGjggh5MIIIdTAOBgNVHQ8BAf8EBAMCAQYwTAYDVR0g BEUwQzBBBgkrBgEEAaAyATwwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wEgYDVR0TAQH/BAgwBgEB/wIBADCCBtAG A1UdHgSCBscwggbDoIIGvzASghAzZGJsYWNrc2J1cmcub3JnMBiCFmFjY2VsZXJh dGV2aXJnaW5pYS5jb20wGIIWYWNjZWxlcmF0ZXZpcmdpbmlhLm9yZzALgglhY3Zj cC5vcmcwCYIHYmV2Lm5ldDAJggdiZXYub3JnMAuCCWNsaWdzLm9yZzAMggpjbWl3 ZWIub3JnMBeCFWVhc3Rlcm5icm9va3Ryb3V0Lm5ldDAXghVlYXN0ZXJuYnJvb2t0 cm91dC5vcmcwEYIPZWNvcnJpZG9ycy5pbmZvMBOCEWVkZ2FycmVzZWFyY2gub3Jn MBKCEGdldC1lZHVjYXRlZC5jb20wE4IRZ2V0LWVkdWNhdGVkLmluZm8wEYIPZ2V0 ZWR1Y2F0ZWQubmV0MBKCEGdldC1lZHVjYXRlZC5uZXQwEYIPZ2V0ZWR1Y2F0ZWQu b3JnMBKCEGdldC1lZHVjYXRlZC5vcmcwD4INaG9raWVjbHViLmNvbTAQgg5ob2tp ZXBob3RvLmNvbTAPgg1ob2tpZXNob3AuY29tMBGCD2hva2llc3BvcnRzLmNvbTAS ghBob2tpZXRpY2tldHMuY29tMBKCEGhvdGVscm9hbm9rZS5jb20wE4IRaHVtYW53 aWxkbGlmZS5vcmcwF4IVaW5uYXR2aXJnaW5pYXRlY2guY29tMA+CDWlzY2hwMjAx MS5vcmcwD4INbGFuZHJlaGFiLm9yZzAggh5uYXRpb25hbHRpcmVyZXNlYXJjaGNl bnRlci5jb20wFYITbmV0d29ya3ZpcmdpbmlhLm5ldDAMggpwZHJjdnQuY29tMBiC FnBldGVkeWVyaXZlcmNvdXJzZS5jb20wDYILcmFkaW9pcS5vcmcwFYITcml2ZXJj b3Vyc2Vnb2xmLmNvbTALgglzZGltaS5vcmcwEIIOc292YW1vdGlvbi5jb20wHoIc c3VzdGFpbmFibGUtYmlvbWF0ZXJpYWxzLmNvbTAeghxzdXN0YWluYWJsZS1iaW9t YXRlcmlhbHMub3JnMBWCE3RoaXNpc3RoZWZ1dHVyZS5jb20wGIIWdGhpcy1pcy10 aGUtZnV0dXJlLmNvbTAVghN0aGlzaXN0aGVmdXR1cmUubmV0MBiCFnRoaXMtaXMt dGhlLWZ1dHVyZS5uZXQwCoIIdmFkcy5vcmcwDIIKdmFsZWFmLm9yZzANggt2YXRl Y2guaW5mbzANggt2YXRlY2gubW9iaTAcghp2YXRlY2hsaWZlbG9uZ2xlYXJuaW5n LmNvbTAcghp2YXRlY2hsaWZlbG9uZ2xlYXJuaW5nLm5ldDAcghp2YXRlY2hsaWZl bG9uZ2xlYXJuaW5nLm9yZzAKggh2Y29tLmVkdTASghB2aXJnaW5pYXZpZXcubmV0 MDSCMnZpcmdpbmlhcG9seXRlY2huaWNpbnN0aXR1dGVhbmRzdGF0ZXVuaXZlcnNp dHkuY29tMDWCM3ZpcmdpbmlhcG9seXRlY2huaWNpbnN0aXR1dGVhbmRzdGF0ZXVu aXZlcnNpdHkuaW5mbzA0gjJ2aXJnaW5pYXBvbHl0ZWNobmljaW5zdGl0dXRlYW5k c3RhdGV1bml2ZXJzaXR5Lm5ldDA0gjJ2aXJnaW5pYXBvbHl0ZWNobmljaW5zdGl0 dXRlYW5kc3RhdGV1bml2ZXJzaXR5Lm9yZzAZghd2aXJnaW5pYXB1YmxpY3JhZGlv Lm9yZzASghB2aXJnaW5pYXRlY2guZWR1MBOCEXZpcmdpbmlhdGVjaC5tb2JpMByC GnZpcmdpbmlhdGVjaGZvdW5kYXRpb24ub3JnMAiCBnZ0LmVkdTALggl2dGFyYy5v cmcwDIIKdnQtYXJjLm9yZzALggl2dGNyYy5jb20wCoIIdnRpcC5vcmcwDIIKdnRs ZWFuLm9yZzAWghR2dGtub3dsZWRnZXdvcmtzLmNvbTAYghZ2dGxpZmVsb25nbGVh cm5pbmcuY29tMBiCFnZ0bGlmZWxvbmdsZWFybmluZy5uZXQwGIIWdnRsaWZlbG9u Z2xlYXJuaW5nLm9yZzATghF2dHNwb3J0c21lZGlhLmNvbTALggl2dHdlaS5jb20w D4INd2l3YXR3ZXJjLmNvbTAKggh3dnRmLm9yZzAIgQZ2dC5lZHUwd6R1MHMxCzAJ BgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVy ZzE8MDoGA1UEChMzVmlyZ2luaWEgUG9seXRlY2huaWMgSW5zdGl0dXRlIGFuZCBT dGF0ZSBVbml2ZXJzaXR5MCcGA1UdJQQgMB4GCCsGAQUFBwMCBggrBgEFBQcDAQYI KwYBBQUHAwkwPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2NybC5nbG9iYWxzaWdu LmNvbS9ncy90cnVzdHJvb3RnMi5jcmwwgYQGCCsGAQUFBwEBBHgwdjAzBggrBgEF BQcwAYYnaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL3RydXN0cm9vdGcyMD8G CCsGAQUFBzAChjNodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC90 cnVzdHJvb3RnMi5jcnQwHQYDVR0OBBYEFLxiYCfV4zVIF+lLq0Vq0Miod3GMMB8G A1UdIwQYMBaAFBT25YsxtkWASkxt/MKHico2w5BiMA0GCSqGSIb3DQEBBQUAA4IB AQAyJm/lOB2Er4tHXhc/+fSufSzgjohJgYfMkvG4LknkvnZ1BjliefR8tTXX49d2 SCDFWfGjqyJZwavavkl/4p3oXPG/nAMDMvxh4YAT+CfEK9HH+6ICV087kD4BLegi +aFJMj8MMdReWCzn5sLnSR1rdse2mo2arX3Uod14SW+PGrbUmTuWNyvRbz3fVmxp UdbGmj3laknO9YPsBGgHfv73pVVsTJkW4ZfY/7KdD/yaVv6ophpOB3coXfjl2+kd Z4ypn2zK+cx9IL/LSewqd/7W9cD55PCUy4X9OTbEmAccwiz3LB66mQoUGfdHdkoB jUY+v9vLQXmaVwI0AYL7g9LN -----END CERTIFICATE-----` var nameConstraintsIntermediate2 = `-----BEGIN CERTIFICATE----- MIIEXTCCA0WgAwIBAgILBAAAAAABNuk6OrMwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMjA0MjUxMTAw MDBaFw0yNzA0MjUxMTAwMDBaMFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVz dGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRy dXN0ZWQgUm9vdCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AKyuvqrtcMr7g7EuNbu4sKwxM127UsCmx1RxbxxgcArGS7rjiefpBH/w4LYrymjf vcw1ueyMNoqLo9nJMz/ORXupb35NNfE667prQYHa+tTjl1IiKpB7QUwt3wXPuTMF Ja1tXtjKzkqJyuJlNuPKT76HcjgNqgV1s9qG44MD5I2JvI12du8zI1bgdQ+l/KsX kTfbGjUvhOLOlVNWVQDpL+YMIrGqgBYxy5TUNgrAcRtwpNdS2KkF5otSmMweVb5k hoUVv3u8UxQH/WWbNhHq1RrIlg/0rBUfi/ziShYFSB7U+aLx5DxPphTFBiDquQGp tB+FC4JvnukDStFihZCZ1R8CAwEAAaOCASMwggEfMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MEcGA1UdIARAMD4wPAYEVR0gADA0MDIGCCsGAQUFBwIB FiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAdBgNVHQ4E FgQUFPblizG2RYBKTG38woeJyjbDkGIwMwYDVR0fBCwwKjAooCagJIYiaHR0cDov L2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNybDA+BggrBgEFBQcBAQQyMDAwLgYI KwYBBQUHMAGGImh0dHA6Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9yb290cjEwHwYD VR0jBBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQADggEB AL7IG0l+k4LkcpI+a/kvZsSRwSM4uA6zGX34e78A2oytr8RG8bJwVb8+AHMUD+Xe 2kYdh/Uj/waQXfqR0OgxQXL9Ct4ZM+JlR1avsNKXWL5AwYXAXCOB3J5PW2XOck7H Zw0vRbGQhjWjQx+B4KOUFg1b3ov/z6Xkr3yaCfRQhXh7KC0Bc0RXPPG5Nv5lCW+z tbbg0zMm3kyfQITRusMSg6IBsDJqOnjaiaKQRcXiD0Sk43ZXb2bUKMxC7+Td3QL4 RyHcWJbQ7YylLTS/x+jxWIcOQ0oO5/54t5PTQ14neYhOz9x4gUk2AYAW6d1vePwb hcC8roQwkHT7HvfYBoc74FM= -----END CERTIFICATE-----` var globalSignRoot = `-----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE-----` var moipLeafCert = `-----BEGIN CERTIFICATE----- MIIGQDCCBSigAwIBAgIRAPe/cwh7CUWizo8mYSDavLIwDQYJKoZIhvcNAQELBQAw gZIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTgwNgYD VQQDEy9DT01PRE8gUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZl ciBDQTAeFw0xMzA4MTUwMDAwMDBaFw0xNDA4MTUyMzU5NTlaMIIBQjEXMBUGA1UE BRMOMDg3MTg0MzEwMDAxMDgxEzARBgsrBgEEAYI3PAIBAxMCQlIxGjAYBgsrBgEE AYI3PAIBAhMJU2FvIFBhdWxvMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXphdGlv bjELMAkGA1UEBhMCQlIxETAPBgNVBBETCDAxNDUyMDAwMRIwEAYDVQQIEwlTYW8g UGF1bG8xEjAQBgNVBAcTCVNhbyBQYXVsbzEtMCsGA1UECRMkQXZlbmlkYSBCcmln YWRlaXJvIEZhcmlhIExpbWEgLCAyOTI3MR0wGwYDVQQKExRNb2lwIFBhZ2FtZW50 b3MgUy5BLjENMAsGA1UECxMETU9JUDEYMBYGA1UECxMPU1NMIEJsaW5kYWRvIEVW MRgwFgYDVQQDEw9hcGkubW9pcC5jb20uYnIwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQDN0b9x6TrXXA9hPCF8/NjqGJ++2D4LO4ZiMFTjs0VwpXy2Y1Oe s74/HuiLGnAHxTmAtV7IpZMibiOcTxcnDYp9oEWkf+gR+hZvwFZwyOBC7wyb3SR3 UvV0N1ZbEVRYpN9kuX/3vjDghjDmzzBwu8a/T+y5JTym5uiJlngVAWyh/RjtIvYi +NVkQMbyVlPGkoCe6c30pH8DKYuUCZU6DHjUsPTX3jAskqbhDSAnclX9iX0p2bmw KVBc+5Vh/2geyzDuquF0w+mNIYdU5h7uXvlmJnf3d2Cext5dxdL8/jezD3U0dAqI pYSKERbyxSkJWxdvRlhdpM9YXMJcpc88xNp1AgMBAAGjggHcMIIB2DAfBgNVHSME GDAWgBQ52v/KKBSKqHQTCLnkDqnS+n6daTAdBgNVHQ4EFgQU/lXuOa7DMExzZjRj LQWcMWGZY7swDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYw FAYIKwYBBQUHAwEGCCsGAQUFBwMCMEYGA1UdIAQ/MD0wOwYMKwYBBAGyMQECAQUB MCswKQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5jb20vQ1BTMFYG A1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JT QUV4dGVuZGVkVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCBhwYIKwYBBQUH AQEEezB5MFEGCCsGAQUFBzAChkVodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01P RE9SU0FFeHRlbmRlZFZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcnQwJAYIKwYB BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAvBgNVHREEKDAmgg9hcGku bW9pcC5jb20uYnKCE3d3dy5hcGkubW9pcC5jb20uYnIwDQYJKoZIhvcNAQELBQAD ggEBAFoTmPlaDcf+nudhjXHwud8g7/LRyA8ucb+3/vfmgbn7FUc1eprF5sJS1mA+ pbiTyXw4IxcJq2KUj0Nw3IPOe9k84mzh+XMmdCKH+QK3NWkE9Udz+VpBOBc0dlqC 1RH5umStYDmuZg/8/r652eeQ5kUDcJyADfpKWBgDPYaGtwzKVT4h3Aok9SLXRHx6 z/gOaMjEDMarMCMw4VUIG1pvNraZrG5oTaALPaIXXpd8VqbQYPudYJ6fR5eY3FeW H/ofbYFdRcuD26MfBFWE9VGGral9Fgo8sEHffho+UWhgApuQV4/l5fMzxB5YBXyQ jhuy8PqqZS9OuLilTeLu4a8z2JI= -----END CERTIFICATE-----` var comodoIntermediateSHA384 = `-----BEGIN CERTIFICATE----- MIIGDjCCA/agAwIBAgIQBqdDgNTr/tQ1taP34Wq92DANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTIwMjEy MDAwMDAwWhcNMjcwMjExMjM1OTU5WjCBkjELMAkGA1UEBhMCR0IxGzAZBgNVBAgT EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxODA2BgNVBAMTL0NPTU9ETyBSU0EgRXh0ZW5kZWQg VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAlVbeVLTf1QJJe9FbXKKyHo+cK2JMK40SKPMalaPGEP0p3uGf CzhAk9HvbpUQ/OGQF3cs7nU+e2PsYZJuTzurgElr3wDqAwB/L3XVKC/sVmePgIOj vdwDmZOLlJFWW6G4ajo/Br0OksxgnP214J9mMF/b5pTwlWqvyIqvgNnmiDkBfBzA xSr3e5Wg8narbZtyOTDr0VdVAZ1YEZ18bYSPSeidCfw8/QpKdhQhXBZzQCMZdMO6 WAqmli7eNuWf0MLw4eDBYuPCGEUZUaoXHugjddTI0JYT/8ck0YwLJ66eetw6YWNg iJctXQUL5Tvrrs46R3N2qPos3cCHF+msMJn4HwIDAQABo4IBaTCCAWUwHwYDVR0j BBgwFoAUu69+Aj36pvE8hI6t7jiY7NkyMtQwHQYDVR0OBBYEFDna/8ooFIqodBMI ueQOqdL6fp1pMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMD4G A1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5j b21vZG8uY29tL0NQUzBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9k b2NhLmNvbS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggr BgEFBQcBAQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29t L0NPTU9ET1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz cC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAERCnUFRK0iIXZebeV4R AUpSGXtBLMeJPNBy3IX6WK/VJeQT+FhlZ58N/1eLqYVeyqZLsKeyLeCMIs37/3mk jCuN/gI9JN6pXV/kD0fQ22YlPodHDK4ixVAihNftSlka9pOlk7DgG4HyVsTIEFPk 1Hax0VtpS3ey4E/EhOfUoFDuPPpE/NBXueEoU/1Tzdy5H3pAvTA/2GzS8+cHnx8i teoiccsq8FZ8/qyo0QYPFBRSTP5kKwxpKrgNUG4+BAe/eiCL+O5lCeHHSQgyPQ0o fkkdt0rvAucNgBfIXOBhYsvss2B5JdoaZXOcOBCgJjqwyBZ9kzEi7nQLiMBciUEA KKlHMd99SUWa9eanRRrSjhMQ34Ovmw2tfn6dNVA0BM7pINae253UqNpktNEvWS5e ojZh1CSggjMziqHRbO9haKPl0latxf1eYusVqHQSTC8xjOnB3xBLAer2VBvNfzu9 XJ/B288ByvK6YBIhMe2pZLiySVgXbVrXzYxtvp5/4gJYp9vDLVj2dAZqmvZh+fYA tmnYOosxWd2R5nwnI4fdAw+PKowegwFOAWEMUnNt/AiiuSpm5HZNMaBWm9lTjaK2 jwLI5jqmBNFI+8NKAnb9L9K8E7bobTQk+p0pisehKxTxlgBzuRPpwLk6R1YCcYAn pLwltum95OmYdBbxN4SBB7SC -----END CERTIFICATE-----` const comodoRSAAuthority = `-----BEGIN CERTIFICATE----- MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt 4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT 8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR pu/xO28QOG8= -----END CERTIFICATE-----` const addTrustRoot = `-----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE-----` const selfSigned = `-----BEGIN CERTIFICATE----- MIIC/DCCAeSgAwIBAgIRAK0SWRVmi67xU3z0gkgY+PkwDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA4MTkxNjMzNDdaFw0xNzA4MTkxNjMz NDdaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDWkm1kdCwxyKEt6OTmZitkmLGH8cQu9z7rUdrhW8lWNm4kh2SuaUWP pscBjda5iqg51aoKuWJR2rw6ElDne+X5eit2FT8zJgAU8v39lMFjbaVZfS9TFOYF w0Tk0Luo/PyKJpZnwhsP++iiGQiteJbndy8aLKmJ2MpLfpDGIgxEIyNb5dgoDi0D WReDCpE6K9WDYqvKVGnQ2Jvqqra6Gfx0tFkuqJxQuqA8aUOlPHcCH4KBZdNEoXdY YL3E4dCAh0YiDs80wNZx4cHqEM3L8gTEFqW2Tn1TSuPZO6gjJ9QPsuUZVjaMZuuO NVxqLGujZkDzARhC3fBpptMuaAfi20+BAgMBAAGjTTBLMA4GA1UdDwEB/wQEAwIF oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBYGA1UdEQQPMA2C C2Zvby5leGFtcGxlMA0GCSqGSIb3DQEBCwUAA4IBAQBPvvfnDhsHWt+/cfwdAVim 4EDn+hYOMkTQwU0pouYIvY8QXYkZ8MBxpBtBMK4JhFU+ewSWoBAEH2dCCvx/BDxN UGTSJHMbsvJHcFvdmsvvRxOqQ/cJz7behx0cfoeHMwcs0/vWv8ms5wHesb5Ek7L0 pl01FCBGTcncVqr6RK1r4fTpeCCfRIERD+YRJz8TtPH6ydesfLL8jIV40H8NiDfG vRAvOtNiKtPzFeQVdbRPOskC4rcHyPeiDAMAMixeLi63+CFty4da3r5lRezeedCE cw3ESZzThBwWqvPOtJdpXdm+r57pDW8qD+/0lY8wfImMNkQAyCUCLg/1Lxt/hrBj -----END CERTIFICATE-----` const issuerSubjectMatchRoot = ` Certificate: Data: Version: 3 (0x2) Serial Number: 161640039802297062 (0x23e42c281e55ae6) Signature Algorithm: sha256WithRSAEncryption Issuer: O=Golang, CN=Root ca Validity Not Before: Jan 1 00:00:00 2015 GMT Not After : Jan 1 00:00:00 2025 GMT Subject: O=Golang, CN=Root ca Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: 00:e9:0e:7f:11:0c:e6:5a:e6:86:83:70:f6:51:07: 2e:02:78:11:f5:b2:24:92:38:ee:26:62:02:c7:94: f1:3e:a1:77:6a:c0:8f:d5:22:68:b6:5d:e2:4c:da: e0:85:11:35:c2:92:72:49:8d:81:b4:88:97:6b:b7: fc:b2:44:5b:d9:4d:06:70:f9:0c:c6:8f:e9:b3:df: a3:6a:84:6c:43:59:be:9d:b2:d0:76:9b:c3:d7:fa: 99:59:c3:b8:e5:f3:53:03:bd:49:d6:b3:cc:a2:43: fe:ad:c2:0b:b9:01:b8:56:29:94:03:24:a7:0d:28: 21:29:a9:ae:94:5b:4a:f9:9f Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Certificate Sign X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:TRUE X509v3 Subject Key Identifier: 40:37:D7:01:FB:40:2F:B8:1C:7E:54:04:27:8C:59:01 Signature Algorithm: sha256WithRSAEncryption 6f:84:df:49:e0:99:d4:71:66:1d:32:86:56:cb:ea:5a:6b:0e: 00:6a:d1:5a:6e:1f:06:23:07:ff:cb:d1:1a:74:e4:24:43:0b: aa:2a:a0:73:75:25:82:bc:bf:3f:a9:f8:48:88:ac:ed:3a:94: 3b:0d:d3:88:c8:67:44:61:33:df:71:6c:c5:af:ed:16:8c:bf: 82:f9:49:bb:e3:2a:07:53:36:37:25:77:de:91:a4:77:09:7f: 6f:b2:91:58:c4:05:89:ea:8e:fa:e1:3b:19:ef:f8:f6:94:b7: 7b:27:e6:e4:84:dd:2b:f5:93:f5:3c:d8:86:c5:38:01:56:5c: 9f:6d -----BEGIN CERTIFICATE----- MIICIDCCAYmgAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwIzEPMA0GA1UE ChMGR29sYW5nMRAwDgYDVQQDEwdSb290IGNhMB4XDTE1MDEwMTAwMDAwMFoXDTI1 MDEwMTAwMDAwMFowIzEPMA0GA1UEChMGR29sYW5nMRAwDgYDVQQDEwdSb290IGNh MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpDn8RDOZa5oaDcPZRBy4CeBH1 siSSOO4mYgLHlPE+oXdqwI/VImi2XeJM2uCFETXCknJJjYG0iJdrt/yyRFvZTQZw +QzGj+mz36NqhGxDWb6dstB2m8PX+plZw7jl81MDvUnWs8yiQ/6twgu5AbhWKZQD JKcNKCEpqa6UW0r5nwIDAQABo10wWzAOBgNVHQ8BAf8EBAMCAgQwHQYDVR0lBBYw FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wGQYDVR0OBBIE EEA31wH7QC+4HH5UBCeMWQEwDQYJKoZIhvcNAQELBQADgYEAb4TfSeCZ1HFmHTKG VsvqWmsOAGrRWm4fBiMH/8vRGnTkJEMLqiqgc3Ulgry/P6n4SIis7TqUOw3TiMhn RGEz33Fsxa/tFoy/gvlJu+MqB1M2NyV33pGkdwl/b7KRWMQFieqO+uE7Ge/49pS3 eyfm5ITdK/WT9TzYhsU4AVZcn20= -----END CERTIFICATE-----` const issuerSubjectMatchLeaf = ` Certificate: Data: Version: 3 (0x2) Serial Number: 16785088708916013734 (0xe8f09d3fe25beaa6) Signature Algorithm: sha256WithRSAEncryption Issuer: O=Golang, CN=Root CA Validity Not Before: Jan 1 00:00:00 2015 GMT Not After : Jan 1 00:00:00 2025 GMT Subject: O=Golang, CN=Leaf Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: 00:db:46:7d:93:2e:12:27:06:48:bc:06:28:21:ab: 7e:c4:b6:a2:5d:fe:1e:52:45:88:7a:36:47:a5:08: 0d:92:42:5b:c2:81:c0:be:97:79:98:40:fb:4f:6d: 14:fd:2b:13:8b:c2:a5:2e:67:d8:d4:09:9e:d6:22: 38:b7:4a:0b:74:73:2b:c2:34:f1:d1:93:e5:96:d9: 74:7b:f3:58:9f:6c:61:3c:c0:b0:41:d4:d9:2b:2b: 24:23:77:5b:1c:3b:bd:75:5d:ce:20:54:cf:a1:63: 87:1d:1e:24:c4:f3:1d:1a:50:8b:aa:b6:14:43:ed: 97:a7:75:62:f4:14:c8:52:d7 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: 9F:91:16:1F:43:43:3E:49:A6:DE:6D:B6:80:D7:9F:60 X509v3 Authority Key Identifier: keyid:40:37:D7:01:FB:40:2F:B8:1C:7E:54:04:27:8C:59:01 Signature Algorithm: sha256WithRSAEncryption 8d:86:05:da:89:f5:1d:c5:16:14:41:b9:34:87:2b:5c:38:99: e3:d9:5a:5b:7a:5b:de:0b:5c:08:45:09:6f:1c:9d:31:5f:08: ca:7a:a3:99:da:83:0b:22:be:4f:02:35:91:4e:5d:5c:37:bf: 89:22:58:7d:30:76:d2:2f:d0:a0:ee:77:9e:77:c0:d6:19:eb: ec:a0:63:35:6a:80:9b:80:1a:80:de:64:bc:40:38:3c:22:69: ad:46:26:a2:3d:ea:f4:c2:92:49:16:03:96:ae:64:21:b9:7c: ee:64:91:47:81:aa:b4:0c:09:2b:12:1a:b2:f3:af:50:b3:b1: ce:24 -----BEGIN CERTIFICATE----- MIICODCCAaGgAwIBAgIJAOjwnT/iW+qmMA0GCSqGSIb3DQEBCwUAMCMxDzANBgNV BAoTBkdvbGFuZzEQMA4GA1UEAxMHUm9vdCBDQTAeFw0xNTAxMDEwMDAwMDBaFw0y NTAxMDEwMDAwMDBaMCAxDzANBgNVBAoTBkdvbGFuZzENMAsGA1UEAxMETGVhZjCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA20Z9ky4SJwZIvAYoIat+xLaiXf4e UkWIejZHpQgNkkJbwoHAvpd5mED7T20U/SsTi8KlLmfY1Ame1iI4t0oLdHMrwjTx 0ZPlltl0e/NYn2xhPMCwQdTZKyskI3dbHDu9dV3OIFTPoWOHHR4kxPMdGlCLqrYU Q+2Xp3Vi9BTIUtcCAwEAAaN3MHUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQG CCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMBkGA1UdDgQSBBCfkRYf Q0M+SabebbaA159gMBsGA1UdIwQUMBKAEEA31wH7QC+4HH5UBCeMWQEwDQYJKoZI hvcNAQELBQADgYEAjYYF2on1HcUWFEG5NIcrXDiZ49laW3pb3gtcCEUJbxydMV8I ynqjmdqDCyK+TwI1kU5dXDe/iSJYfTB20i/QoO53nnfA1hnr7KBjNWqAm4AagN5k vEA4PCJprUYmoj3q9MKSSRYDlq5kIbl87mSRR4GqtAwJKxIasvOvULOxziQ= -----END CERTIFICATE----- ` const x509v1TestRoot = ` -----BEGIN CERTIFICATE----- MIICIDCCAYmgAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwIzEPMA0GA1UE ChMGR29sYW5nMRAwDgYDVQQDEwdSb290IENBMB4XDTE1MDEwMTAwMDAwMFoXDTI1 MDEwMTAwMDAwMFowIzEPMA0GA1UEChMGR29sYW5nMRAwDgYDVQQDEwdSb290IENB MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpDn8RDOZa5oaDcPZRBy4CeBH1 siSSOO4mYgLHlPE+oXdqwI/VImi2XeJM2uCFETXCknJJjYG0iJdrt/yyRFvZTQZw +QzGj+mz36NqhGxDWb6dstB2m8PX+plZw7jl81MDvUnWs8yiQ/6twgu5AbhWKZQD JKcNKCEpqa6UW0r5nwIDAQABo10wWzAOBgNVHQ8BAf8EBAMCAgQwHQYDVR0lBBYw FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wGQYDVR0OBBIE EEA31wH7QC+4HH5UBCeMWQEwDQYJKoZIhvcNAQELBQADgYEAcIwqeNUpQr9cOcYm YjpGpYkQ6b248xijCK7zI+lOeWN89zfSXn1AvfsC9pSdTMeDklWktbF/Ad0IN8Md h2NtN34ard0hEfHc8qW8mkXdsysVmq6cPvFYaHz+dBtkHuHDoy8YQnC0zdN/WyYB /1JmacUUofl+HusHuLkDxmadogI= -----END CERTIFICATE-----` const x509v1TestIntermediate = ` -----BEGIN CERTIFICATE----- MIIByjCCATMCCQCCdEMsT8ykqTANBgkqhkiG9w0BAQsFADAjMQ8wDQYDVQQKEwZH b2xhbmcxEDAOBgNVBAMTB1Jvb3QgQ0EwHhcNMTUwMTAxMDAwMDAwWhcNMjUwMTAx MDAwMDAwWjAwMQ8wDQYDVQQKEwZHb2xhbmcxHTAbBgNVBAMTFFguNTA5djEgaW50 ZXJtZWRpYXRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJ2QyniAOT+5YL jeinEBJr3NsC/Q2QJ/VKmgvp+xRxuKTHJiVmxVijmp0vWg8AWfkmuE4p3hXQbbqM k5yxrk1n60ONhim2L4VXriEvCE7X2OXhTmBls5Ufr7aqIgPMikwjScCXwz8E8qI8 UxyAhnjeJwMYBU8TuwBImSd4LBHoQQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAIab DRG6FbF9kL9jb/TDHkbVBk+sl/Pxi4/XjuFyIALlARgAkeZcPmL5tNW1ImHkwsHR zWE77kJDibzd141u21ZbLsKvEdUJXjla43bdyMmEqf5VGpC3D4sFt3QVH7lGeRur x5Wlq1u3YDL/j6s1nU2dQ3ySB/oP7J+vQ9V4QeM+ -----END CERTIFICATE-----` const x509v1TestLeaf = ` -----BEGIN CERTIFICATE----- MIICMzCCAZygAwIBAgIJAPo99mqJJrpJMA0GCSqGSIb3DQEBCwUAMDAxDzANBgNV BAoTBkdvbGFuZzEdMBsGA1UEAxMUWC41MDl2MSBpbnRlcm1lZGlhdGUwHhcNMTUw MTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjArMQ8wDQYDVQQKEwZHb2xhbmcxGDAW BgNVBAMTD2Zvby5leGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEApUh60Z+a5/oKJxG//Dn8CihSo2CJHNIIO3zEJZ1EeNSMZCynaIR6D3IPZEIR +RG2oGt+f5EEukAPYxwasp6VeZEezoQWJ+97nPCT6DpwLlWp3i2MF8piK2R9vxkG Z5n0+HzYk1VM8epIrZFUXSMGTX8w1y041PX/yYLxbdEifdcCAwEAAaNaMFgwDgYD VR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV HRMBAf8EAjAAMBkGA1UdDgQSBBBFozXe0SnzAmjy+1U6M/cvMA0GCSqGSIb3DQEB CwUAA4GBADYzYUvaToO/ucBskPdqXV16AaakIhhSENswYVSl97/sODaxsjishKq9 5R7siu+JnIFotA7IbBe633p75xEnLN88X626N/XRFG9iScLzpj0o0PWXBUiB+fxL /jt8qszOXCv2vYdUTPNuPqufXLWMoirpuXrr1liJDmedCcAHepY/ -----END CERTIFICATE-----` const ignoreCNWithSANRoot = ` -----BEGIN CERTIFICATE----- MIIDPzCCAiegAwIBAgIIJkzCwkNrPHMwDQYJKoZIhvcNAQELBQAwMDEQMA4GA1UE ChMHVEVTVElORzEcMBoGA1UEAxMTKipUZXN0aW5nKiogUm9vdCBDQTAeFw0xNTAx MDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMDAxEDAOBgNVBAoTB1RFU1RJTkcxHDAa BgNVBAMTEyoqVGVzdGluZyoqIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQC4YAf5YqlXGcikvbMWtVrNICt+V/NNWljwfvSKdg4Inm7k6BwW P6y4Y+n4qSYIWNU4iRkdpajufzctxQCO6ty13iw3qVktzcC5XBIiS6ymiRhhDgnY VQqyakVGw9MxrPwdRZVlssUv3Hmy6tU+v5Ok31SLY5z3wKgYWvSyYs0b8bKNU8kf 2FmSHnBN16lxGdjhe3ji58F/zFMr0ds+HakrLIvVdFcQFAnQopM8FTHpoWNNzGU3 KaiO0jBbMFkd6uVjVnuRJ+xjuiqi/NWwiwQA+CEr9HKzGkxOF8nAsHamdmO1wW+w OsCrC0qWQ/f5NTOVATTJe0vj88OMTvo3071VAgMBAAGjXTBbMA4GA1UdDwEB/wQE AwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw AwEB/zAZBgNVHQ4EEgQQQDfXAftAL7gcflQEJ4xZATANBgkqhkiG9w0BAQsFAAOC AQEAGOn3XjxHyHbXLKrRmpwV447B7iNBXR5VlhwOgt1kWaHDL2+8f/9/h0HMkB6j fC+/yyuYVqYuOeavqMGVrh33D2ODuTQcFlOx5lXukP46j3j+Lm0jjZ1qNX7vlP8I VlUXERhbelkw8O4oikakwIY9GE8syuSgYf+VeBW/lvuAZQrdnPfabxe05Tre6RXy nJHMB1q07YHpbwIkcV/lfCE9pig2nPXTLwYZz9cl46Ul5RCpPUi+IKURo3x8y0FU aSLjI/Ya0zwUARMmyZ3RRGCyhIarPb20mKSaMf1/Nb23pS3k1QgmZhk5pAnXYsWu BJ6bvwEAasFiLGP6Zbdmxb2hIA== -----END CERTIFICATE-----` const ignoreCNWithSANLeaf = ` -----BEGIN CERTIFICATE----- MIIDaTCCAlGgAwIBAgIJAONakvRTxgJhMA0GCSqGSIb3DQEBCwUAMDAxEDAOBgNV BAoTB1RFU1RJTkcxHDAaBgNVBAMTEyoqVGVzdGluZyoqIFJvb3QgQ0EwHhcNMTUw MTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjAsMRAwDgYDVQQKEwdURVNUSU5HMRgw FgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDBqskp89V/JMIBBqcauKSOVLcMyIE/t0jgSWVrsI4sksBTabLsfMdS ui2n+dHQ1dRBuw3o4g4fPrWwS3nMnV3pZUHEn2TPi5N1xkjTaxObXgKIY2GKmFP3 rJ9vYqHT6mT4K93kCHoRcmJWWySc7S3JAOhTcdB4G+tIdQJN63E+XRYQQfNrn5HZ hxQoOzaguHFx+ZGSD4Ntk6BSZz5NfjqCYqYxe+iCpTpEEYhIpi8joSPSmkTMTxBW S1W2gXbYNQ9KjNkGM6FnQsUJrSPMrWs4v3UB/U88N5LkZeF41SqD9ySFGwbGajFV nyzj12+4K4D8BLhlOc0Eo/F/8GwOwvmxAgMBAAGjgYkwgYYwDgYDVR0PAQH/BAQD AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA MBkGA1UdDgQSBBCjeab27q+5pV43jBGANOJ1MBsGA1UdIwQUMBKAEEA31wH7QC+4 HH5UBCeMWQEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAGZfZ ErTVxxpIg64s22mQpXSk/72THVQsfsKHzlXmztM0CJzH8ccoN67ZqKxJCfdiE/FI Emb6BVV4cGPeIKpcxaM2dwX/Y+Y0JaxpQJvqLxs+EByRL0gPP3shgg86WWCjYLxv AgOn862d/JXGDrC9vIlQ/DDQcyL5g0JV5UjG2G9TUigbnrXxBw7BoWK6wmoSaHnR sZKEHSs3RUJvm7qqpA9Yfzm9jg+i9j32zh1xFacghAOmFRFXa9eCVeigZ/KK2mEY j2kBQyvnyKsXHLAKUoUOpd6t/1PHrfXnGj+HmzZNloJ/BZ1kiWb4eLvMljoLGkZn xZbqP3Krgjj4XNaXjg== -----END CERTIFICATE-----` const excludedNamesLeaf = ` -----BEGIN CERTIFICATE----- MIID4DCCAsigAwIBAgIHDUSFtJknhzANBgkqhkiG9w0BAQsFADCBnjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5 ICgzNzM0NTE1NTYyODA2Mzk3KTEhMB8GA1UEAwwYSW50ZXJtZWRpYXRlIENBIGZv ciAzMzkyMB4XDTE3MDIwODIxMTUwNFoXDTE4MDIwODIwMjQ1OFowgZAxCzAJBgNV BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3Mx FDASBgNVBAoMC05ldGZsaXggSW5jMS0wKwYDVQQLDCRQbGF0Zm9ybSBTZWN1cml0 eSAoMzczNDUxNTc0ODUwMjY5NikxEzARBgNVBAMMCjE3Mi4xNi4wLjEwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZ0oP1bMv6bOeqcKbzinnGpNOpenhA zdFFsgea62znWsH3Wg4+1Md8uPCqlaQIsaJQKZHc50eKD3bg0Io7c6kxHkBQr1b8 Q7cGeK3CjdqG3NwS/aizzrLKOwL693hFwwy7JY7GGCvogbhyQRKn6iV0U9zMm7bu /9pQVV/wx8u01u2uAlLttjyQ5LJkxo5t8cATFVqxdN5J9eY//VSDiTwXnlpQITBP /Ow+zYuZ3kFlzH3CtCOhOEvNG3Ar1NvP3Icq35PlHV+Eki4otnKfixwByoiGpqCB UEIY04VrZJjwBxk08y/3jY2B3VLYGgi+rryyCxIqkB7UpSNPMMWSG4UpAgMBAAGj LzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0RBBYwFIIMYmVuZGVyLmxvY2FshwSsEAAB MA0GCSqGSIb3DQEBCwUAA4IBAQCLW3JO8L7LKByjzj2RciPjCGH5XF87Wd20gYLq sNKcFwCIeyZhnQy5aZ164a5G9AIk2HLvH6HevBFPhA9Ivmyv/wYEfnPd1VcFkpgP hDt8MCFJ8eSjCyKdtZh1MPMLrLVymmJV+Rc9JUUYM9TIeERkpl0rskcO1YGewkYt qKlWE+0S16+pzsWvKn831uylqwIb8ANBPsCX4aM4muFBHavSWAHgRO+P+yXVw8Q+ VQDnMHUe5PbZd1/+1KKVs1K/CkBCtoHNHp1d/JT+2zUQJphwja9CcgfFdVhSnHL4 oEEOFtqVMIuQfR2isi08qW/JGOHc4sFoLYB8hvdaxKWSE19A -----END CERTIFICATE----- ` const excludedNamesIntermediate = ` -----BEGIN CERTIFICATE----- MIIDzTCCArWgAwIBAgIHDUSFqYeczDANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5 ICgzNzM0NTE1NDc5MDY0NjAyKTEcMBoGA1UEAwwTTG9jYWwgUm9vdCBmb3IgMzM5 MjAeFw0xNzAyMDgyMTE1MDRaFw0xODAyMDgyMDI0NThaMIGeMQswCQYDVQQGEwJV UzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRQwEgYD VQQKDAtOZXRmbGl4IEluYzEtMCsGA1UECwwkUGxhdGZvcm0gU2VjdXJpdHkgKDM3 MzQ1MTU1NjI4MDYzOTcpMSEwHwYDVQQDDBhJbnRlcm1lZGlhdGUgQ0EgZm9yIDMz OTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCOyEs6tJ/t9emQTvlx 3FS7uJSou5rKkuqVxZdIuYQ+B2ZviBYUnMRT9bXDB0nsVdKZdp0hdchdiwNXDG/I CiWu48jkcv/BdynVyayOT+0pOJSYLaPYpzBx1Pb9M5651ct9GSbj6Tz0ChVonoIE 1AIZ0kkebucZRRFHd0xbAKVRKyUzPN6HJ7WfgyauUp7RmlC35wTmrmARrFohQLlL 7oICy+hIQePMy9x1LSFTbPxZ5AUUXVC3eUACU3vLClF/Xs8XGHebZpUXCdMQjOGS nq1eFguFHR1poSB8uSmmLqm4vqUH9CDhEgiBAC8yekJ8//kZQ7lUEqZj3YxVbk+Y E4H5AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB ADxrnmNX5gWChgX9K5fYwhFDj5ofxZXAKVQk+WjmkwMcmCx3dtWSm++Wdksj/ZlA V1cLW3ohWv1/OAZuOlw7sLf98aJpX+UUmIYYQxDubq+4/q7VA7HzEf2k/i/oN1NI JgtrhpPcZ/LMO6k7DYx0qlfYq8pTSfd6MI4LnWKgLc+JSPJJjmvspgio2ZFcnYr7 A264BwLo6v1Mos1o1JUvFFcp4GANlw0XFiWh7JXYRl8WmS5DoouUC+aNJ3lmyF6z LbIjZCSfgZnk/LK1KU1j91FI2bc2ULYZvAC1PAg8/zvIgxn6YM2Q7ZsdEgWw0FpS zMBX1/lk4wkFckeUIlkD55Y= -----END CERTIFICATE-----` const excludedNamesRoot = ` -----BEGIN CERTIFICATE----- MIIEGTCCAwGgAwIBAgIHDUSFpInn/zANBgkqhkiG9w0BAQsFADCBozELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5 ICgzNzMxNTA5NDM3NDYyNDg1KTEmMCQGA1UEAwwdTmFtZSBDb25zdHJhaW50cyBU ZXN0IFJvb3QgQ0EwHhcNMTcwMjA4MjExNTA0WhcNMTgwMjA4MjAyNDU4WjCBmTEL MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBH YXRvczEUMBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNl Y3VyaXR5ICgzNzM0NTE1NDc5MDY0NjAyKTEcMBoGA1UEAwwTTG9jYWwgUm9vdCBm b3IgMzM5MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJymcnX29ekc 7+MLyr8QuAzoHWznmGdDd2sITwWRjM89/21cdlHCGKSpULUNdFp9HDLWvYECtxt+ 8TuzKiQz7qAerzGUT1zI5McIjHy0e/i4xIkfiBiNeTCuB/N9QRbZlcfM80ErkaA4 gCAFK8qZAcWkHIl6e+KaQFMPLKk9kckgAnVDHEJe8oLNCogCJ15558b65g05p9eb 5Lg+E98hoPRTQaDwlz3CZPfTTA2EiEZInSi8qzodFCbTpJUVTbiVUH/JtVjlibbb smdcx5PORK+8ZJkhLEh54AjaWOX4tB/7Tkk8stg2VBmrIARt/j4UVj7cTrIWU3bV m8TwHJG+YgsCAwEAAaNaMFgwDwYDVR0TAQH/BAUwAwEB/zBFBgNVHR4EPjA8oBww CocICgEAAP//AAAwDoIMYmVuZGVyLmxvY2FsoRwwCocICgEAAP//AAAwDoIMYmVu ZGVyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQAMjbheffPxtSKSv9NySW+8qmHs n7Mb5GGyCFu+cMZSoSaabstbml+zHEFJvWz6/1E95K4F8jKhAcu/CwDf4IZrSD2+ Hee0DolVSQhZpnHgPyj7ZATz48e3aJaQPUlhCEOh0wwF4Y0N4FV0t7R6woLylYRZ yU1yRHUqUYpN0DWFpsPbBqgM6uUAVO2ayBFhPgWUaqkmSbZ/Nq7isGvknaTmcIwT 6mOAFN0qFb4RGzfGJW7x6z7KCULS7qVDp6fU3tRoScHFEgRubks6jzQ1W5ooSm4o +NQCZDd5eFeU8PpNX7rgaYE4GPq+EEmLVCBYmdctr8QVdqJ//8Xu3+1phjDy -----END CERTIFICATE-----` const invalidCNRoot = ` -----BEGIN CERTIFICATE----- MIIBFjCBvgIJAIsu4r+jb70UMAoGCCqGSM49BAMCMBQxEjAQBgNVBAsMCVRlc3Qg cm9vdDAeFw0xODA3MTExODMyMzVaFw0yODA3MDgxODMyMzVaMBQxEjAQBgNVBAsM CVRlc3Qgcm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABF6oDgMg0LV6YhPj QXaPXYCc2cIyCdqp0ROUksRz0pOLTc5iY2nraUheRUD1vRRneq7GeXOVNn7uXONg oCGMjNwwCgYIKoZIzj0EAwIDRwAwRAIgDSiwgIn8g1lpruYH0QD1GYeoWVunfmrI XzZZl0eW/ugCICgOfXeZ2GGy3wIC0352BaC3a8r5AAb2XSGNe+e9wNN6 -----END CERTIFICATE----- ` const invalidCNWithoutSAN = ` Certificate: Data: Version: 1 (0x0) Serial Number: 07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a2 Signature Algorithm: ecdsa-with-SHA256 Issuer: OU = Test root Validity Not Before: Jul 11 18:35:21 2018 GMT Not After : Jul 8 18:35:21 2028 GMT Subject: CN = "foo,invalid" Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35: 2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5: b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85: 0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9: 26:b3:ca:50:90 ASN1 OID: prime256v1 NIST CURVE: P-256 Signature Algorithm: ecdsa-with-SHA256 30:45:02:21:00:85:96:75:b6:72:3c:67:12:a0:7f:86:04:81: d2:dd:c8:67:50:d7:5f:85:c0:54:54:fc:e6:6b:45:08:93:d3: 2a:02:20:60:86:3e:d6:28:a6:4e:da:dd:6e:95:89:cc:00:76: 78:1c:03:80:85:a6:5a:0b:eb:c5:f3:9c:2e:df:ef:6e:fa -----BEGIN CERTIFICATE----- MIIBJDCBywIUB7q8t9mrDAL+UB1OFaMN5BEWFKIwCgYIKoZIzj0EAwIwFDESMBAG A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4MzUyMVoXDTI4MDcwODE4MzUyMVow FjEUMBIGA1UEAwwLZm9vLGludmFsaWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AASnpnwiM6dHfwiTLV9hNS7aRWd28pdzGLABEkoa1bdvQTy7BWn0Bl3/6yunhQtM 90VOgUB6qcYdu7rZuSazylCQMAoGCCqGSM49BAMCA0gAMEUCIQCFlnW2cjxnEqB/ hgSB0t3IZ1DXX4XAVFT85mtFCJPTKgIgYIY+1iimTtrdbpWJzAB2eBwDgIWmWgvr xfOcLt/vbvo= -----END CERTIFICATE----- ` const validCNWithoutSAN = ` Certificate: Data: Version: 1 (0x0) Serial Number: 07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a4 Signature Algorithm: ecdsa-with-SHA256 Issuer: OU = Test root Validity Not Before: Jul 11 18:47:24 2018 GMT Not After : Jul 8 18:47:24 2028 GMT Subject: CN = foo.example.com Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35: 2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5: b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85: 0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9: 26:b3:ca:50:90 ASN1 OID: prime256v1 NIST CURVE: P-256 Signature Algorithm: ecdsa-with-SHA256 30:44:02:20:53:6c:d7:b7:59:61:51:72:a5:18:a3:4b:0d:52: ea:15:fa:d0:93:30:32:54:4b:ed:0f:58:85:b8:a8:1a:82:3b: 02:20:14:77:4b:0e:7e:4f:0a:4f:64:26:97:dc:d0:ed:aa:67: 1d:37:85:da:b4:87:ba:25:1c:2a:58:f7:23:11:8b:3d -----BEGIN CERTIFICATE----- MIIBJzCBzwIUB7q8t9mrDAL+UB1OFaMN5BEWFKQwCgYIKoZIzj0EAwIwFDESMBAG A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4NDcyNFoXDTI4MDcwODE4NDcyNFow GjEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEp6Z8IjOnR38Iky1fYTUu2kVndvKXcxiwARJKGtW3b0E8uwVp9AZd/+sr p4ULTPdFToFAeqnGHbu62bkms8pQkDAKBggqhkjOPQQDAgNHADBEAiBTbNe3WWFR cqUYo0sNUuoV+tCTMDJUS+0PWIW4qBqCOwIgFHdLDn5PCk9kJpfc0O2qZx03hdq0 h7olHCpY9yMRiz0= -----END CERTIFICATE----- ` const ( rootWithoutSKID = ` Certificate: Data: Version: 3 (0x2) Serial Number: 78:29:2a:dc:2f:12:39:7f:c9:33:93:ea:61:39:7d:70 Signature Algorithm: ecdsa-with-SHA256 Issuer: O = Acme Co Validity Not Before: Feb 4 22:56:34 2019 GMT Not After : Feb 1 22:56:34 2029 GMT Subject: O = Acme Co Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:84:a6:8c:69:53:af:87:4b:39:64:fe:04:24:e6: d8:fc:d6:46:39:35:0e:92:dc:48:08:7e:02:5f:1e: 07:53:5c:d9:e0:56:c5:82:07:f6:a3:e2:ad:f6:ad: be:a0:4e:03:87:39:67:0c:9c:46:91:68:6b:0e:8e: f8:49:97:9d:5b ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment, Certificate Sign X509v3 Extended Key Usage: TLS Web Server Authentication X509v3 Basic Constraints: critical CA:TRUE X509v3 Subject Alternative Name: DNS:example Signature Algorithm: ecdsa-with-SHA256 30:46:02:21:00:c6:81:61:61:42:8d:37:e7:d0:c3:72:43:44: 17:bd:84:ff:88:81:68:9a:99:08:ab:3c:3a:c0:1e:ea:8c:ba: c0:02:21:00:de:c9:fa:e5:5e:c6:e2:db:23:64:43:a9:37:42: 72:92:7f:6e:89:38:ea:9e:2a:a7:fd:2f:ea:9a:ff:20:21:e7 -----BEGIN CERTIFICATE----- MIIBbzCCARSgAwIBAgIQeCkq3C8SOX/JM5PqYTl9cDAKBggqhkjOPQQDAjASMRAw DgYDVQQKEwdBY21lIENvMB4XDTE5MDIwNDIyNTYzNFoXDTI5MDIwMTIyNTYzNFow EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABISm jGlTr4dLOWT+BCTm2PzWRjk1DpLcSAh+Al8eB1Nc2eBWxYIH9qPirfatvqBOA4c5 ZwycRpFoaw6O+EmXnVujTDBKMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBIGA1UdEQQLMAmCB2V4YW1wbGUwCgYI KoZIzj0EAwIDSQAwRgIhAMaBYWFCjTfn0MNyQ0QXvYT/iIFompkIqzw6wB7qjLrA AiEA3sn65V7G4tsjZEOpN0Jykn9uiTjqniqn/S/qmv8gIec= -----END CERTIFICATE----- ` leafWithAKID = ` Certificate: Data: Version: 3 (0x2) Serial Number: f0:8a:62:f0:03:84:a2:cf:69:63:ad:71:3b:b6:5d:8c Signature Algorithm: ecdsa-with-SHA256 Issuer: O = Acme Co Validity Not Before: Feb 4 23:06:52 2019 GMT Not After : Feb 1 23:06:52 2029 GMT Subject: O = Acme LLC Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:5a:4e:4d:fb:ff:17:f7:b6:13:e8:29:45:34:81: 39:ff:8c:9c:d9:8c:0a:9f:dd:b5:97:4c:2b:20:91: 1c:4f:6b:be:53:27:66:ec:4a:ad:08:93:6d:66:36: 0c:02:70:5d:01:ca:7f:c3:29:e9:4f:00:ba:b4:14: ec:c5:c3:34:b3 ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Authority Key Identifier: keyid:C2:2B:5F:91:78:34:26:09:42:8D:6F:51:B2:C5:AF:4C:0B:DE:6A:42 X509v3 Subject Alternative Name: DNS:example Signature Algorithm: ecdsa-with-SHA256 30:44:02:20:64:e0:ba:56:89:63:ce:22:5e:4f:22:15:fd:3c: 35:64:9a:3a:6b:7b:9a:32:a0:7f:f7:69:8c:06:f0:00:58:b8: 02:20:09:e4:9f:6d:8b:9e:38:e1:b6:01:d5:ee:32:a4:94:65: 93:2a:78:94:bb:26:57:4b:c7:dd:6c:3d:40:2b:63:90 -----BEGIN CERTIFICATE----- MIIBjTCCATSgAwIBAgIRAPCKYvADhKLPaWOtcTu2XYwwCgYIKoZIzj0EAwIwEjEQ MA4GA1UEChMHQWNtZSBDbzAeFw0xOTAyMDQyMzA2NTJaFw0yOTAyMDEyMzA2NTJa MBMxETAPBgNVBAoTCEFjbWUgTExDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE Wk5N+/8X97YT6ClFNIE5/4yc2YwKn921l0wrIJEcT2u+Uydm7EqtCJNtZjYMAnBd Acp/wynpTwC6tBTsxcM0s6NqMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUwitfkXg0JglCjW9R ssWvTAveakIwEgYDVR0RBAswCYIHZXhhbXBsZTAKBggqhkjOPQQDAgNHADBEAiBk 4LpWiWPOIl5PIhX9PDVkmjpre5oyoH/3aYwG8ABYuAIgCeSfbYueOOG2AdXuMqSU ZZMqeJS7JldLx91sPUArY5A= -----END CERTIFICATE----- ` ) var unknownAuthorityErrorTests = []struct { cert string expected string }{ {selfSignedWithCommonName, "x509: certificate signed by unknown authority (possibly because of \"empty\" while trying to verify candidate authority certificate \"test\")"}, {selfSignedNoCommonNameWithOrgName, "x509: certificate signed by unknown authority (possibly because of \"empty\" while trying to verify candidate authority certificate \"ca\")"}, {selfSignedNoCommonNameNoOrgName, "x509: certificate signed by unknown authority (possibly because of \"empty\" while trying to verify candidate authority certificate \"serial:0\")"}, } func TestUnknownAuthorityError(t *testing.T) { for i, tt := range unknownAuthorityErrorTests { der, _ := pem.Decode([]byte(tt.cert)) if der == nil { t.Errorf("#%d: Unable to decode PEM block", i) } c, err := ParseCertificate(der.Bytes) if err != nil { t.Errorf("#%d: Unable to parse certificate -> %v", i, err) } uae := &UnknownAuthorityError{ Cert: c, hintErr: fmt.Errorf("empty"), hintCert: c, } actual := uae.Error() if actual != tt.expected { t.Errorf("#%d: UnknownAuthorityError.Error() response invalid actual: %s expected: %s", i, actual, tt.expected) } } } var nameConstraintTests = []struct { constraint, domain string expectError bool shouldMatch bool }{ {"", "anything.com", false, true}, {"example.com", "example.com", false, true}, {"example.com.", "example.com", true, false}, {"example.com", "example.com.", true, false}, {"example.com", "ExAmPle.coM", false, true}, {"example.com", "exampl1.com", false, false}, {"example.com", "www.ExAmPle.coM", false, true}, {"example.com", "sub.www.ExAmPle.coM", false, true}, {"example.com", "notexample.com", false, false}, {".example.com", "example.com", false, false}, {".example.com", "www.example.com", false, true}, {".example.com", "www..example.com", true, false}, } func TestNameConstraints(t *testing.T) { for i, test := range nameConstraintTests { result, err := matchDomainConstraint(test.domain, test.constraint) if err != nil && !test.expectError { t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) continue } if err == nil && test.expectError { t.Errorf("unexpected success for test #%d: domain=%s, constraint=%s", i, test.domain, test.constraint) continue } if result != test.shouldMatch { t.Errorf("unexpected result for test #%d: domain=%s, constraint=%s, result=%t", i, test.domain, test.constraint, result) } } } const selfSignedWithCommonName = `-----BEGIN CERTIFICATE----- MIIDCjCCAfKgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMQswCQYDVQQKEwJjYTEL MAkGA1UEAxMCY2EwHhcNMTYwODI4MTcwOTE4WhcNMjEwODI3MTcwOTE4WjAcMQsw CQYDVQQKEwJjYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAOH55PfRsbvmcabfLLko1w/yuapY/hk13Cgmc3WE/Z1ZStxGiVxY gQVH9n4W/TbUsrep/TmcC4MV7xEm5252ArcgaH6BeQ4QOTFj/6Jx0RT7U/ix+79x 8RRysf7OlzNpGIctwZEM7i/G+0ZfqX9ULxL/EW9tppSxMX1jlXZQarnU7BERL5cH +G2jcbU9H28FXYishqpVYE9L7xrXMm61BAwvGKB0jcVW6JdhoAOSfQbbgp7JjIlq czXqUsv1UdORO/horIoJptynTvuARjZzyWatya6as7wyOgEBllE6BjPK9zpn+lp3 tQ8dwKVqm/qBPhIrVqYG/Ec7pIv8mJfYabMCAwEAAaNZMFcwDgYDVR0PAQH/BAQD AgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAA MAoGA1UdDgQDBAEAMAwGA1UdIwQFMAOAAQAwDQYJKoZIhvcNAQELBQADggEBAAAM XMFphzq4S5FBcRdB2fRrmcoz+jEROBWvIH/1QUJeBEBz3ZqBaJYfBtQTvqCA5Rjw dxyIwVd1W3q3aSulM0tO62UCU6L6YeeY/eq8FmpD7nMJo7kCrXUUAMjxbYvS3zkT v/NErK6SgWnkQiPJBZNX1Q9+aSbLT/sbaCTdbWqcGNRuLGJkmqfIyoxRt0Hhpqsx jP5cBaVl50t4qoCuVIE9cOucnxYXnI7X5HpXWvu8Pfxo4SwVjb1az8Fk5s8ZnxGe fPB6Q3L/pKBe0SEe5GywpwtokPLB3lAygcuHbxp/1FlQ1NQZqq+vgXRIla26bNJf IuYkJwt6w+LH/9HZgf8= -----END CERTIFICATE-----` const selfSignedNoCommonNameWithOrgName = `-----BEGIN CERTIFICATE----- MIIC+zCCAeOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMQswCQYDVQQKEwJjYTEL MAkGA1UEAxMCY2EwHhcNMTYwODI4MTgxMzQ4WhcNMjEwODI3MTgxMzQ4WjANMQsw CQYDVQQKEwJjYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5EjrUa 7EtOMxWiIgTzp2FlQvncPsG329O3l3uNGnbigb8TmNMw2M8UhoDjd84pnU5RAfqd 8t5TJyw/ybnIKBN131Q2xX+gPQ0dFyMvcO+i1CUgCxmYZomKVA2MXO1RD1hLTYGS gOVjc3no3MBwd8uVQp0NStqJ1QvLtNG4Uy+B28qe+ZFGGbjGqx8/CU4A8Szlpf7/ xAZR8w5qFUUlpA2LQYeHHJ5fQVXw7kyL1diNrKNi0G3qcY0IrBh++hT+hnEEXyXu g8a0Ux18hoE8D6rAr34rCZl6AWfqW5wjwm+N5Ns2ugr9U4N8uCKJYMPHb2CtdubU 46IzVucpTfGLdaMCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQG CCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMAoGA1UdDgQDBAEAMAwG A1UdIwQFMAOAAQAwDQYJKoZIhvcNAQELBQADggEBAEn5SgVpJ3zjsdzPqK7Qd/sB bYd1qtPHlrszjhbHBg35C6mDgKhcv4o6N+fuC+FojZb8lIxWzJtvT9pQbfy/V6u3 wOb816Hm71uiP89sioIOKCvSAstj/p9doKDOUaKOcZBTw0PS2m9eja8bnleZzBvK rD8cNkHf74v98KvBhcwBlDifVzmkWzMG6TL1EkRXUyLKiWgoTUFSkCDV927oXXMR DKnszq+AVw+K8hbeV2A7GqT7YfeqOAvSbatTDnDtKOPmlCnQui8A149VgZzXv7eU 29ssJSqjUPyp58dlV6ZuynxPho1QVZUOQgnJToXIQ3/5vIvJRXy52GJCs4/Gh/w= -----END CERTIFICATE-----` const selfSignedNoCommonNameNoOrgName = `-----BEGIN CERTIFICATE----- MIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMQswCQYDVQQKEwJjYTEL MAkGA1UEAxMCY2EwHhcNMTYwODI4MTgxOTQ1WhcNMjEwODI3MTgxOTQ1WjAAMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp3E+Jl6DpgzogHUW/i/AAcCM fnNJLOamNVKFGmmxhb4XTHxRaWoTzrlsyzIMS0WzivvJeZVe6mWbvuP2kZanKgIz 35YXRTR9HbqkNTMuvnpUESzWxbGWE2jmt2+a/Jnz89FS4WIYRhF7nI2z8PvZOfrI 2gETTT2tEpoF2S4soaYfm0DBeT8K0/rogAaf+oeUS6V+v3miRcAooJgpNJGu9kqm S0xKPn1RCFVjpiRd6YNS0xZirjYQIBMFBvoSoHjaOdgJptNRBprYPOxVJ/ItzGf0 kPmzPFCx2tKfxV9HLYBPgxi+fP3IIx8aIYuJn8yReWtYEMYU11hDPeAFN5Gm+wID AQABo1kwVzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG AQUFBwMBMAwGA1UdEwEB/wQCMAAwCgYDVR0OBAMEAQAwDAYDVR0jBAUwA4ABADAN BgkqhkiG9w0BAQsFAAOCAQEATZVOFeiCpPM5QysToLv+8k7Rjoqt6L5IxMUJGEpq 4ENmldmwkhEKr9VnYEJY3njydnnTm97d9vOfnLj9nA9wMBODeOO3KL2uJR2oDnmM 9z1NSe2aQKnyBb++DM3ZdikpHn/xEpGV19pYKFQVn35x3lpPh2XijqRDO/erKemb w67CoNRb81dy+4Q1lGpA8ORoLWh5fIq2t2eNGc4qB8vlTIKiESzAwu7u3sRfuWQi 4R+gnfLd37FWflMHwztFbVTuNtPOljCX0LN7KcuoXYlr05RhQrmoN7fQHsrZMNLs 8FVjHdKKu+uPstwd04Uy4BR/H2y1yerN9j/L6ZkMl98iiA== -----END CERTIFICATE-----` const criticalExtRoot = `-----BEGIN CERTIFICATE----- MIIBqzCCAVGgAwIBAgIJAJ+mI/85cXApMAoGCCqGSM49BAMCMB0xDDAKBgNVBAoT A09yZzENMAsGA1UEAxMEUm9vdDAeFw0xNTAxMDEwMDAwMDBaFw0yNTAxMDEwMDAw MDBaMB0xDDAKBgNVBAoTA09yZzENMAsGA1UEAxMEUm9vdDBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABJGp9joiG2QSQA+1FczEDAsWo84rFiP3GTL+n+ugcS6TyNib gzMsdbJgVi+a33y0SzLZxB+YvU3/4KTk8yKLC+2jejB4MA4GA1UdDwEB/wQEAwIC BDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB /zAZBgNVHQ4EEgQQQDfXAftAL7gcflQEJ4xZATAbBgNVHSMEFDASgBBAN9cB+0Av uBx+VAQnjFkBMAoGCCqGSM49BAMCA0gAMEUCIFeSV00fABFceWR52K+CfIgOHotY FizzGiLB47hGwjMuAiEA8e0um2Kr8FPQ4wmFKaTRKHMaZizCGl3m+RG5QsE1KWo= -----END CERTIFICATE-----` const criticalExtIntermediate = `-----BEGIN CERTIFICATE----- MIIBszCCAVmgAwIBAgIJAL2kcGZKpzVqMAoGCCqGSM49BAMCMB0xDDAKBgNVBAoT A09yZzENMAsGA1UEAxMEUm9vdDAeFw0xNTAxMDEwMDAwMDBaFw0yNTAxMDEwMDAw MDBaMCUxDDAKBgNVBAoTA09yZzEVMBMGA1UEAxMMSW50ZXJtZWRpYXRlMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAESqVq92iPEq01cL4o99WiXDc5GZjpjNlzMS1n rk8oHcVDp4tQRRQG3F4A6dF1rn/L923ha3b0fhDLlAvXZB+7EKN6MHgwDgYDVR0P AQH/BAQDAgIEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMB Af8EBTADAQH/MBkGA1UdDgQSBBCMGmiotXbbXVd7H40UsgajMBsGA1UdIwQUMBKA EEA31wH7QC+4HH5UBCeMWQEwCgYIKoZIzj0EAwIDSAAwRQIhAOhhNRb6KV7h3wbE cdap8bojzvUcPD78fbsQPCNw1jPxAiBOeAJhlTwpKn9KHpeJphYSzydj9NqcS26Y xXbdbm27KQ== -----END CERTIFICATE-----` const criticalExtLeafWithExt = `-----BEGIN CERTIFICATE----- MIIBxTCCAWugAwIBAgIJAJZAUtw5ccb1MAoGCCqGSM49BAMCMCUxDDAKBgNVBAoT A09yZzEVMBMGA1UEAxMMSW50ZXJtZWRpYXRlMB4XDTE1MDEwMTAwMDAwMFoXDTI1 MDEwMTAwMDAwMFowJDEMMAoGA1UEChMDT3JnMRQwEgYDVQQDEwtleGFtcGxlLmNv bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABF3ABa2+B6gUyg6ayCaRQWYY/+No 6PceLqEavZNUeVNuz7bS74Toy8I7R3bGMkMgbKpLSPlPTroAATvebTXoBaijgYQw gYEwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjAMBgNVHRMBAf8EAjAAMBkGA1UdDgQSBBBRNtBL2vq8nCV3qVp7ycxMMBsGA1Ud IwQUMBKAEIwaaKi1dttdV3sfjRSyBqMwCgYDUQMEAQH/BAAwCgYIKoZIzj0EAwID SAAwRQIgVjy8GBgZFiagexEuDLqtGjIRJQtBcf7lYgf6XFPH1h4CIQCT6nHhGo6E I+crEm4P5q72AnA/Iy0m24l7OvLuXObAmg== -----END CERTIFICATE-----` const criticalExtIntermediateWithExt = `-----BEGIN CERTIFICATE----- MIIB2TCCAX6gAwIBAgIIQD3NrSZtcUUwCgYIKoZIzj0EAwIwHTEMMAoGA1UEChMD T3JnMQ0wCwYDVQQDEwRSb290MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAw MFowPTEMMAoGA1UEChMDT3JnMS0wKwYDVQQDEyRJbnRlcm1lZGlhdGUgd2l0aCBD cml0aWNhbCBFeHRlbnNpb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQtnmzH mcRm10bdDBnJE7xQEJ25cLCL5okuEphRR0Zneo6+nQZikoh+UBbtt5GV3Dms7LeP oF5HOplYDCd8wi/wo4GHMIGEMA4GA1UdDwEB/wQEAwICBDAdBgNVHSUEFjAUBggr BgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQKxdv UuQZ6sO3XvBsxgNZ3zAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMAoGA1ED BAEB/wQAMAoGCCqGSM49BAMCA0kAMEYCIQCQzTPd6XKex+OAPsKT/1DsoMsg8vcG c2qZ4Q0apT/kvgIhAKu2TnNQMIUdcO0BYQIl+Uhxc78dc9h4lO+YJB47pHGx -----END CERTIFICATE-----` const criticalExtLeaf = `-----BEGIN CERTIFICATE----- MIIBzzCCAXWgAwIBAgIJANoWFIlhCI9MMAoGCCqGSM49BAMCMD0xDDAKBgNVBAoT A09yZzEtMCsGA1UEAxMkSW50ZXJtZWRpYXRlIHdpdGggQ3JpdGljYWwgRXh0ZW5z aW9uMB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFowJDEMMAoGA1UEChMD T3JnMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEH A0IABG1Lfh8A0Ho2UvZN5H0+ONil9c8jwtC0y0xIZftyQE+Fwr9XwqG3rV2g4M1h GnJa9lV9MPHg8+b85Hixm0ZSw7SjdzB1MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAZBgNVHQ4EEgQQ UNhY4JhezH9gQYqvDMWrWDAbBgNVHSMEFDASgBArF29S5Bnqw7de8GzGA1nfMAoG CCqGSM49BAMCA0gAMEUCIQClA3d4tdrDu9Eb5ZBpgyC+fU1xTZB0dKQHz6M5fPZA 2AIgN96lM+CPGicwhN24uQI6flOsO3H0TJ5lNzBYLtnQtlc= -----END CERTIFICATE-----` func TestValidHostname(t *testing.T) { tests := []struct { host string want bool }{ {"example.com", true}, {"eXample123-.com", true}, {"-eXample123-.com", false}, {"", false}, {".", false}, {"example..com", false}, {".example.com", false}, {"*.example.com", true}, {"*foo.example.com", false}, {"foo.*.example.com", false}, {"exa_mple.com", true}, {"foo,bar", false}, {"project-dev:us-central1:main", true}, } for _, tt := range tests { if got := validHostname(tt.host); got != tt.want { t.Errorf("validHostname(%q) = %v, want %v", tt.host, got, tt.want) } } } func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.PrivateKey) (*Certificate, crypto.PrivateKey, error) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) template := &Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{CommonName: cn}, NotBefore: time.Now().Add(-1 * time.Hour), NotAfter: time.Now().Add(24 * time.Hour), KeyUsage: KeyUsageKeyEncipherment | KeyUsageDigitalSignature | KeyUsageCertSign, ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, BasicConstraintsValid: true, IsCA: isCA, } if issuer == nil { issuer = template issuerKey = priv } derBytes, err := CreateCertificate(rand.Reader, template, issuer, priv.Public(), issuerKey) if err != nil { return nil, nil, err } cert, err := ParseCertificate(derBytes) if err != nil { return nil, nil, err } return cert, priv, nil } func TestPathologicalChain(t *testing.T) { if testing.Short() { t.Skip("skipping generation of a long chain of certificates in short mode") } // Build a chain where all intermediates share the same subject, to hit the // path building worst behavior. roots, intermediates := NewCertPool(), NewCertPool() parent, parentKey, err := generateCert("Root CA", true, nil, nil) if err != nil { t.Fatal(err) } roots.AddCert(parent) for i := 1; i < 100; i++ { parent, parentKey, err = generateCert("Intermediate CA", true, parent, parentKey) if err != nil { t.Fatal(err) } intermediates.AddCert(parent) } leaf, _, err := generateCert("Leaf", false, parent, parentKey) if err != nil { t.Fatal(err) } start := time.Now() _, err = leaf.Verify(VerifyOptions{ Roots: roots, Intermediates: intermediates, }) t.Logf("verification took %v", time.Since(start)) if err == nil || !strings.Contains(err.Error(), "signature check attempts limit") { t.Errorf("expected verification to fail with a signature checks limit error; got %v", err) } } func TestLongChain(t *testing.T) { if testing.Short() { t.Skip("skipping generation of a long chain of certificates in short mode") } roots, intermediates := NewCertPool(), NewCertPool() parent, parentKey, err := generateCert("Root CA", true, nil, nil) if err != nil { t.Fatal(err) } roots.AddCert(parent) for i := 1; i < 15; i++ { name := fmt.Sprintf("Intermediate CA #%d", i) parent, parentKey, err = generateCert(name, true, parent, parentKey) if err != nil { t.Fatal(err) } intermediates.AddCert(parent) } leaf, _, err := generateCert("Leaf", false, parent, parentKey) if err != nil { t.Fatal(err) } start := time.Now() if _, err := leaf.Verify(VerifyOptions{ Roots: roots, Intermediates: intermediates, }); err != nil { t.Error(err) } t.Logf("verification took %v", time.Since(start)) } google-certificate-transparency-go-2308f62/x509/x509.go000066400000000000000000003216561462611535200224170ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package x509 parses X.509-encoded keys and certificates. // // On UNIX systems the environment variables SSL_CERT_FILE and SSL_CERT_DIR // can be used to override the system default locations for the SSL certificate // file and SSL certificate files directory, respectively. // // This is a fork of the Go library crypto/x509 package, primarily adapted for // use with Certificate Transparency. Main areas of difference are: // // - Life as a fork: // - Rename OS-specific cgo code so it doesn't clash with main Go library. // - Use local library imports (asn1, pkix) throughout. // - Add version-specific wrappers for Go version-incompatible code (in // ptr_*_windows.go). // - Laxer certificate parsing: // - Add options to disable various validation checks (times, EKUs etc). // - Use NonFatalErrors type for some errors and continue parsing; this // can be checked with IsFatal(err). // - Support for short bitlength ECDSA curves (in curves.go). // - Certificate Transparency specific function: // - Parsing and marshaling of SCTList extension. // - RemoveSCTList() function for rebuilding CT leaf entry. // - Pre-certificate processing (RemoveCTPoison(), BuildPrecertTBS(), // ParseTBSCertificate(), IsPrecertificate()). // - Revocation list processing: // - Detailed CRL parsing (in revoked.go) // - Detailed error recording mechanism (in error.go, errors.go) // - Factor out parseDistributionPoints() for reuse. // - Factor out and generalize GeneralNames parsing (in names.go) // - Fix CRL commenting. // - RPKI support: // - Support for SubjectInfoAccess extension // - Support for RFC3779 extensions (in rpki.go) // - RSAES-OAEP support: // - Support for parsing RSASES-OAEP public keys from certificates // - Ed25519 support: // - Support for parsing and marshaling Ed25519 keys // - General improvements: // - Export and use OID values throughout. // - Export OIDFromNamedCurve(). // - Export SignatureAlgorithmFromAI(). // - Add OID value to UnhandledCriticalExtension error. // - Minor typo/lint fixes. package x509 import ( "bytes" "crypto" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" _ "crypto/sha1" _ "crypto/sha256" _ "crypto/sha512" "encoding/pem" "errors" "fmt" "io" "math/big" "net" "net/url" "strconv" "strings" "time" "unicode/utf8" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/crypto/ed25519" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509/pkix" ) // pkixPublicKey reflects a PKIX public key structure. See SubjectPublicKeyInfo // in RFC 3280. type pkixPublicKey struct { Algo pkix.AlgorithmIdentifier BitString asn1.BitString } // ParsePKIXPublicKey parses a public key in PKIX, ASN.1 DER form. // // It returns a *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, or // ed25519.PublicKey. More types might be supported in the future. // // This kind of key is commonly encoded in PEM blocks of type "PUBLIC KEY". func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) { var pki publicKeyInfo if rest, err := asn1.Unmarshal(derBytes, &pki); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after ASN.1 of public-key") } algo := getPublicKeyAlgorithmFromOID(pki.Algorithm.Algorithm) if algo == UnknownPublicKeyAlgorithm { return nil, errors.New("x509: unknown public key algorithm") } var nfe NonFatalErrors pub, err = parsePublicKey(algo, &pki, &nfe) if err != nil { return pub, err } // Treat non-fatal errors as fatal for this entrypoint. if len(nfe.Errors) > 0 { return nil, nfe.Errors[0] } return pub, nil } func marshalPublicKey(pub interface{}) (publicKeyBytes []byte, publicKeyAlgorithm pkix.AlgorithmIdentifier, err error) { switch pub := pub.(type) { case *rsa.PublicKey: publicKeyBytes, err = asn1.Marshal(pkcs1PublicKey{ N: pub.N, E: pub.E, }) if err != nil { return nil, pkix.AlgorithmIdentifier{}, err } publicKeyAlgorithm.Algorithm = OIDPublicKeyRSA // This is a NULL parameters value which is required by // RFC 3279, Section 2.3.1. publicKeyAlgorithm.Parameters = asn1.NullRawValue case *ecdsa.PublicKey: publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y) oid, ok := OIDFromNamedCurve(pub.Curve) if !ok { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") } publicKeyAlgorithm.Algorithm = OIDPublicKeyECDSA var paramBytes []byte paramBytes, err = asn1.Marshal(oid) if err != nil { return } publicKeyAlgorithm.Parameters.FullBytes = paramBytes case ed25519.PublicKey: publicKeyBytes = pub publicKeyAlgorithm.Algorithm = OIDPublicKeyEd25519 default: return nil, pkix.AlgorithmIdentifier{}, fmt.Errorf("x509: unsupported public key type: %T", pub) } return publicKeyBytes, publicKeyAlgorithm, nil } // MarshalPKIXPublicKey converts a public key to PKIX, ASN.1 DER form. // // The following key types are currently supported: *rsa.PublicKey, *ecdsa.PublicKey // and ed25519.PublicKey. Unsupported key types result in an error. // // This kind of key is commonly encoded in PEM blocks of type "PUBLIC KEY". func MarshalPKIXPublicKey(pub interface{}) ([]byte, error) { var publicKeyBytes []byte var publicKeyAlgorithm pkix.AlgorithmIdentifier var err error if publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(pub); err != nil { return nil, err } pkix := pkixPublicKey{ Algo: publicKeyAlgorithm, BitString: asn1.BitString{ Bytes: publicKeyBytes, BitLength: 8 * len(publicKeyBytes), }, } ret, _ := asn1.Marshal(pkix) return ret, nil } // These structures reflect the ASN.1 structure of X.509 certificates.: type certificate struct { Raw asn1.RawContent TBSCertificate tbsCertificate SignatureAlgorithm pkix.AlgorithmIdentifier SignatureValue asn1.BitString } type tbsCertificate struct { Raw asn1.RawContent Version int `asn1:"optional,explicit,default:0,tag:0"` SerialNumber *big.Int SignatureAlgorithm pkix.AlgorithmIdentifier Issuer asn1.RawValue Validity validity Subject asn1.RawValue PublicKey publicKeyInfo UniqueId asn1.BitString `asn1:"optional,tag:1"` SubjectUniqueId asn1.BitString `asn1:"optional,tag:2"` Extensions []pkix.Extension `asn1:"optional,explicit,tag:3"` } // RFC 4055, 4.1 // The current ASN.1 parser does not support non-integer defaults so // the 'default:' tags here do nothing. type rsaesoaepAlgorithmParameters struct { HashFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:0,default:sha1Identifier"` MaskgenFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:1,default:mgf1SHA1Identifier"` PSourceFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:2,default:pSpecifiedEmptyIdentifier"` } type dsaAlgorithmParameters struct { P, Q, G *big.Int } type dsaSignature struct { R, S *big.Int } type ecdsaSignature dsaSignature type validity struct { NotBefore, NotAfter time.Time } type publicKeyInfo struct { Raw asn1.RawContent Algorithm pkix.AlgorithmIdentifier PublicKey asn1.BitString } // RFC 5280, 4.2.1.1 type authKeyId struct { Id []byte `asn1:"optional,tag:0"` } // SignatureAlgorithm indicates the algorithm used to sign a certificate. type SignatureAlgorithm int // SignatureAlgorithm values: const ( UnknownSignatureAlgorithm SignatureAlgorithm = iota MD2WithRSA MD5WithRSA SHA1WithRSA SHA256WithRSA SHA384WithRSA SHA512WithRSA DSAWithSHA1 DSAWithSHA256 ECDSAWithSHA1 ECDSAWithSHA256 ECDSAWithSHA384 ECDSAWithSHA512 SHA256WithRSAPSS SHA384WithRSAPSS SHA512WithRSAPSS PureEd25519 ) // RFC 4055, 6. Basic object identifiers var oidpSpecified = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 9} // These are the default parameters for an RSAES-OAEP pubkey. // The current ASN.1 parser does not support non-integer defaults so // these currently do nothing. var ( sha1Identifier = pkix.AlgorithmIdentifier{ Algorithm: oidSHA1, Parameters: asn1.NullRawValue, } mgf1SHA1Identifier = pkix.AlgorithmIdentifier{ Algorithm: oidMGF1, // RFC 4055, 2.1 sha1Identifier Parameters: asn1.RawValue{ Class: asn1.ClassUniversal, Tag: asn1.TagSequence, IsCompound: false, Bytes: []byte{6, 5, 43, 14, 3, 2, 26, 5, 0}, FullBytes: []byte{16, 9, 6, 5, 43, 14, 3, 2, 26, 5, 0}}, } pSpecifiedEmptyIdentifier = pkix.AlgorithmIdentifier{ Algorithm: oidpSpecified, // RFC 4055, 4.1 nullOctetString Parameters: asn1.RawValue{ Class: asn1.ClassUniversal, Tag: asn1.TagOctetString, IsCompound: false, Bytes: []byte{}, FullBytes: []byte{4, 0}}, } ) func (algo SignatureAlgorithm) isRSAPSS() bool { switch algo { case SHA256WithRSAPSS, SHA384WithRSAPSS, SHA512WithRSAPSS: return true default: return false } } func (algo SignatureAlgorithm) String() string { for _, details := range signatureAlgorithmDetails { if details.algo == algo { return details.name } } return strconv.Itoa(int(algo)) } // PublicKeyAlgorithm indicates the algorithm used for a certificate's public key. type PublicKeyAlgorithm int // PublicKeyAlgorithm values: const ( UnknownPublicKeyAlgorithm PublicKeyAlgorithm = iota RSA DSA ECDSA Ed25519 RSAESOAEP ) var publicKeyAlgoName = [...]string{ RSA: "RSA", DSA: "DSA", ECDSA: "ECDSA", Ed25519: "Ed25519", RSAESOAEP: "RSAESOAEP", } func (algo PublicKeyAlgorithm) String() string { if 0 < algo && int(algo) < len(publicKeyAlgoName) { return publicKeyAlgoName[algo] } return strconv.Itoa(int(algo)) } // OIDs for signature algorithms // // pkcs-1 OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } // // // RFC 3279 2.2.1 RSA Signature Algorithms // // md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } // // md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } // // sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } // // dsaWithSha1 OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) x9-57(10040) x9cm(4) 3 } // // RFC 3279 2.2.3 ECDSA Signature Algorithm // // ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) ansi-x962(10045) // signatures(4) ecdsa-with-SHA1(1)} // // // RFC 4055 5 PKCS #1 Version 1.5 // // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } // // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } // // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } // // // RFC 5758 3.1 DSA Signature Algorithms // // dsaWithSha256 OBJECT IDENTIFIER ::= { // joint-iso-ccitt(2) country(16) us(840) organization(1) gov(101) // csor(3) algorithms(4) id-dsa-with-sha2(3) 2} // // RFC 5758 3.2 ECDSA Signature Algorithm // // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2) // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 } // // ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { iso(1) member-body(2) // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 3 } // // ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { iso(1) member-body(2) // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 4 } // // // RFC 8410 3 Curve25519 and Curve448 Algorithm Identifiers // // id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } var ( oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} // oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA // but it's specified by ISO. Microsoft's makecert.exe has been known // to produce certificates with this OID. oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} ) var signatureAlgorithmDetails = []struct { algo SignatureAlgorithm name string oid asn1.ObjectIdentifier pubKeyAlgo PublicKeyAlgorithm hash crypto.Hash }{ {MD2WithRSA, "MD2-RSA", oidSignatureMD2WithRSA, RSA, crypto.Hash(0) /* no value for MD2 */}, {MD5WithRSA, "MD5-RSA", oidSignatureMD5WithRSA, RSA, crypto.MD5}, {SHA1WithRSA, "SHA1-RSA", oidSignatureSHA1WithRSA, RSA, crypto.SHA1}, {SHA1WithRSA, "SHA1-RSA", oidISOSignatureSHA1WithRSA, RSA, crypto.SHA1}, {SHA256WithRSA, "SHA256-RSA", oidSignatureSHA256WithRSA, RSA, crypto.SHA256}, {SHA384WithRSA, "SHA384-RSA", oidSignatureSHA384WithRSA, RSA, crypto.SHA384}, {SHA512WithRSA, "SHA512-RSA", oidSignatureSHA512WithRSA, RSA, crypto.SHA512}, {SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, RSA, crypto.SHA256}, {SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, RSA, crypto.SHA384}, {SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, RSA, crypto.SHA512}, {DSAWithSHA1, "DSA-SHA1", oidSignatureDSAWithSHA1, DSA, crypto.SHA1}, {DSAWithSHA256, "DSA-SHA256", oidSignatureDSAWithSHA256, DSA, crypto.SHA256}, {ECDSAWithSHA1, "ECDSA-SHA1", oidSignatureECDSAWithSHA1, ECDSA, crypto.SHA1}, {ECDSAWithSHA256, "ECDSA-SHA256", oidSignatureECDSAWithSHA256, ECDSA, crypto.SHA256}, {ECDSAWithSHA384, "ECDSA-SHA384", oidSignatureECDSAWithSHA384, ECDSA, crypto.SHA384}, {ECDSAWithSHA512, "ECDSA-SHA512", oidSignatureECDSAWithSHA512, ECDSA, crypto.SHA512}, {PureEd25519, "Ed25519", oidSignatureEd25519, Ed25519, crypto.Hash(0) /* no pre-hashing */}, } // pssParameters reflects the parameters in an AlgorithmIdentifier that // specifies RSA PSS. See RFC 3447, Appendix A.2.3. type pssParameters struct { // The following three fields are not marked as // optional because the default values specify SHA-1, // which is no longer suitable for use in signatures. Hash pkix.AlgorithmIdentifier `asn1:"explicit,tag:0"` MGF pkix.AlgorithmIdentifier `asn1:"explicit,tag:1"` SaltLength int `asn1:"explicit,tag:2"` TrailerField int `asn1:"optional,explicit,tag:3,default:1"` } // rsaPSSParameters returns an asn1.RawValue suitable for use as the Parameters // in an AlgorithmIdentifier that specifies RSA PSS. func rsaPSSParameters(hashFunc crypto.Hash) asn1.RawValue { var hashOID asn1.ObjectIdentifier switch hashFunc { case crypto.SHA256: hashOID = oidSHA256 case crypto.SHA384: hashOID = oidSHA384 case crypto.SHA512: hashOID = oidSHA512 } params := pssParameters{ Hash: pkix.AlgorithmIdentifier{ Algorithm: hashOID, Parameters: asn1.NullRawValue, }, MGF: pkix.AlgorithmIdentifier{ Algorithm: oidMGF1, }, SaltLength: hashFunc.Size(), TrailerField: 1, } mgf1Params := pkix.AlgorithmIdentifier{ Algorithm: hashOID, Parameters: asn1.NullRawValue, } var err error params.MGF.Parameters.FullBytes, err = asn1.Marshal(mgf1Params) if err != nil { panic(err) } serialized, err := asn1.Marshal(params) if err != nil { panic(err) } return asn1.RawValue{FullBytes: serialized} } // SignatureAlgorithmFromAI converts an PKIX algorithm identifier to the // equivalent local constant. func SignatureAlgorithmFromAI(ai pkix.AlgorithmIdentifier) SignatureAlgorithm { if ai.Algorithm.Equal(oidSignatureEd25519) { // RFC 8410, Section 3 // > For all of the OIDs, the parameters MUST be absent. if len(ai.Parameters.FullBytes) != 0 { return UnknownSignatureAlgorithm } } if !ai.Algorithm.Equal(oidSignatureRSAPSS) { for _, details := range signatureAlgorithmDetails { if ai.Algorithm.Equal(details.oid) { return details.algo } } return UnknownSignatureAlgorithm } // RSA PSS is special because it encodes important parameters // in the Parameters. var params pssParameters if _, err := asn1.Unmarshal(ai.Parameters.FullBytes, ¶ms); err != nil { return UnknownSignatureAlgorithm } var mgf1HashFunc pkix.AlgorithmIdentifier if _, err := asn1.Unmarshal(params.MGF.Parameters.FullBytes, &mgf1HashFunc); err != nil { return UnknownSignatureAlgorithm } // PSS is greatly overburdened with options. This code forces them into // three buckets by requiring that the MGF1 hash function always match the // message hash function (as recommended in RFC 3447, Section 8.1), that the // salt length matches the hash length, and that the trailer field has the // default value. if (len(params.Hash.Parameters.FullBytes) != 0 && !bytes.Equal(params.Hash.Parameters.FullBytes, asn1.NullBytes)) || !params.MGF.Algorithm.Equal(oidMGF1) || !mgf1HashFunc.Algorithm.Equal(params.Hash.Algorithm) || (len(mgf1HashFunc.Parameters.FullBytes) != 0 && !bytes.Equal(mgf1HashFunc.Parameters.FullBytes, asn1.NullBytes)) || params.TrailerField != 1 { return UnknownSignatureAlgorithm } switch { case params.Hash.Algorithm.Equal(oidSHA256) && params.SaltLength == 32: return SHA256WithRSAPSS case params.Hash.Algorithm.Equal(oidSHA384) && params.SaltLength == 48: return SHA384WithRSAPSS case params.Hash.Algorithm.Equal(oidSHA512) && params.SaltLength == 64: return SHA512WithRSAPSS } return UnknownSignatureAlgorithm } // RFC 3279, 2.3 Public Key Algorithms // // pkcs-1 OBJECT IDENTIFIER ::== { iso(1) member-body(2) us(840) // // rsadsi(113549) pkcs(1) 1 } // // rsaEncryption OBJECT IDENTIFIER ::== { pkcs1-1 1 } // // id-dsa OBJECT IDENTIFIER ::== { iso(1) member-body(2) us(840) // // x9-57(10040) x9cm(4) 1 } // // # RFC 5480, 2.1.1 Unrestricted Algorithm Identifier and Parameters // // id-ecPublicKey OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } var ( OIDPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} OIDPublicKeyRSAESOAEP = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 7} OIDPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} OIDPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} OIDPublicKeyRSAObsolete = asn1.ObjectIdentifier{2, 5, 8, 1, 1} OIDPublicKeyEd25519 = oidSignatureEd25519 ) func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) PublicKeyAlgorithm { switch { case oid.Equal(OIDPublicKeyRSA): return RSA case oid.Equal(OIDPublicKeyDSA): return DSA case oid.Equal(OIDPublicKeyECDSA): return ECDSA case oid.Equal(OIDPublicKeyRSAESOAEP): return RSAESOAEP case oid.Equal(OIDPublicKeyEd25519): return Ed25519 } return UnknownPublicKeyAlgorithm } // RFC 5480, 2.1.1.1. Named Curve // // secp224r1 OBJECT IDENTIFIER ::= { // iso(1) identified-organization(3) certicom(132) curve(0) 33 } // // secp256r1 OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) // prime(1) 7 } // // secp384r1 OBJECT IDENTIFIER ::= { // iso(1) identified-organization(3) certicom(132) curve(0) 34 } // // secp521r1 OBJECT IDENTIFIER ::= { // iso(1) identified-organization(3) certicom(132) curve(0) 35 } // // secp192r1 OBJECT IDENTIFIER ::= { // iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) // prime(1) 1 } // // NB: secp256r1 is equivalent to prime256v1, // secp192r1 is equivalent to ansix9p192r and prime192v1 var ( OIDNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} OIDNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} OIDNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} OIDNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} OIDNamedCurveP192 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 1} ) func namedCurveFromOID(oid asn1.ObjectIdentifier, nfe *NonFatalErrors) elliptic.Curve { switch { case oid.Equal(OIDNamedCurveP224): return elliptic.P224() case oid.Equal(OIDNamedCurveP256): return elliptic.P256() case oid.Equal(OIDNamedCurveP384): return elliptic.P384() case oid.Equal(OIDNamedCurveP521): return elliptic.P521() case oid.Equal(OIDNamedCurveP192): nfe.AddError(errors.New("insecure curve (secp192r1) specified")) return secp192r1() } return nil } // OIDFromNamedCurve returns the OID used to specify the use of the given // elliptic curve. func OIDFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) { switch curve { case elliptic.P224(): return OIDNamedCurveP224, true case elliptic.P256(): return OIDNamedCurveP256, true case elliptic.P384(): return OIDNamedCurveP384, true case elliptic.P521(): return OIDNamedCurveP521, true case secp192r1(): return OIDNamedCurveP192, true } return nil, false } // KeyUsage represents the set of actions that are valid for a given key. It's // a bitmap of the KeyUsage* constants. type KeyUsage int // KeyUsage values: const ( KeyUsageDigitalSignature KeyUsage = 1 << iota KeyUsageContentCommitment KeyUsageKeyEncipherment KeyUsageDataEncipherment KeyUsageKeyAgreement KeyUsageCertSign KeyUsageCRLSign KeyUsageEncipherOnly KeyUsageDecipherOnly ) // RFC 5280, 4.2.1.12 Extended Key Usage // // anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 } // // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } // // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } // id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 } // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } // id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 } // id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 } var ( oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0} oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1} oidExtKeyUsageClientAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2} oidExtKeyUsageCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3} oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4} oidExtKeyUsageIPSECEndSystem = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5} oidExtKeyUsageIPSECTunnel = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6} oidExtKeyUsageIPSECUser = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7} oidExtKeyUsageTimeStamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8} oidExtKeyUsageOCSPSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9} oidExtKeyUsageMicrosoftServerGatedCrypto = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3} oidExtKeyUsageNetscapeServerGatedCrypto = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1} oidExtKeyUsageMicrosoftCommercialCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 1, 22} oidExtKeyUsageMicrosoftKernelCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 61, 1, 1} // RFC 6962 s3.1 oidExtKeyUsageCertificateTransparency = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 4} ) // ExtKeyUsage represents an extended set of actions that are valid for a given key. // Each of the ExtKeyUsage* constants define a unique action. type ExtKeyUsage int // ExtKeyUsage values: const ( ExtKeyUsageAny ExtKeyUsage = iota ExtKeyUsageServerAuth ExtKeyUsageClientAuth ExtKeyUsageCodeSigning ExtKeyUsageEmailProtection ExtKeyUsageIPSECEndSystem ExtKeyUsageIPSECTunnel ExtKeyUsageIPSECUser ExtKeyUsageTimeStamping ExtKeyUsageOCSPSigning ExtKeyUsageMicrosoftServerGatedCrypto ExtKeyUsageNetscapeServerGatedCrypto ExtKeyUsageMicrosoftCommercialCodeSigning ExtKeyUsageMicrosoftKernelCodeSigning ExtKeyUsageCertificateTransparency ) // extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID. var extKeyUsageOIDs = []struct { extKeyUsage ExtKeyUsage oid asn1.ObjectIdentifier }{ {ExtKeyUsageAny, oidExtKeyUsageAny}, {ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth}, {ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth}, {ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning}, {ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection}, {ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem}, {ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel}, {ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser}, {ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping}, {ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning}, {ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto}, {ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto}, {ExtKeyUsageMicrosoftCommercialCodeSigning, oidExtKeyUsageMicrosoftCommercialCodeSigning}, {ExtKeyUsageMicrosoftKernelCodeSigning, oidExtKeyUsageMicrosoftKernelCodeSigning}, {ExtKeyUsageCertificateTransparency, oidExtKeyUsageCertificateTransparency}, } func extKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku ExtKeyUsage, ok bool) { for _, pair := range extKeyUsageOIDs { if oid.Equal(pair.oid) { return pair.extKeyUsage, true } } return } func oidFromExtKeyUsage(eku ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) { for _, pair := range extKeyUsageOIDs { if eku == pair.extKeyUsage { return pair.oid, true } } return } // SerializedSCT represents a single TLS-encoded signed certificate timestamp, from RFC6962 s3.3. type SerializedSCT struct { Val []byte `tls:"minlen:1,maxlen:65535"` } // SignedCertificateTimestampList is a list of signed certificate timestamps, from RFC6962 s3.3. type SignedCertificateTimestampList struct { SCTList []SerializedSCT `tls:"minlen:1,maxlen:65335"` } // A Certificate represents an X.509 certificate. type Certificate struct { Raw []byte // Complete ASN.1 DER content (certificate, signature algorithm and signature). RawTBSCertificate []byte // Certificate part of raw ASN.1 DER content. RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo. RawSubject []byte // DER encoded Subject RawIssuer []byte // DER encoded Issuer Signature []byte SignatureAlgorithm SignatureAlgorithm PublicKeyAlgorithm PublicKeyAlgorithm PublicKey interface{} Version int SerialNumber *big.Int Issuer pkix.Name Subject pkix.Name NotBefore, NotAfter time.Time // Validity bounds. KeyUsage KeyUsage // Extensions contains raw X.509 extensions. When parsing certificates, // this can be used to extract non-critical extensions that are not // parsed by this package. When marshaling certificates, the Extensions // field is ignored, see ExtraExtensions. Extensions []pkix.Extension // ExtraExtensions contains extensions to be copied, raw, into any // marshaled certificates. Values override any extensions that would // otherwise be produced based on the other fields. The ExtraExtensions // field is not populated when parsing certificates, see Extensions. ExtraExtensions []pkix.Extension // UnhandledCriticalExtensions contains a list of extension IDs that // were not (fully) processed when parsing. Verify will fail if this // slice is non-empty, unless verification is delegated to an OS // library which understands all the critical extensions. // // Users can access these extensions using Extensions and can remove // elements from this slice if they believe that they have been // handled. UnhandledCriticalExtensions []asn1.ObjectIdentifier ExtKeyUsage []ExtKeyUsage // Sequence of extended key usages. UnknownExtKeyUsage []asn1.ObjectIdentifier // Encountered extended key usages unknown to this package. // BasicConstraintsValid indicates whether IsCA, MaxPathLen, // and MaxPathLenZero are valid. BasicConstraintsValid bool IsCA bool // MaxPathLen and MaxPathLenZero indicate the presence and // value of the BasicConstraints' "pathLenConstraint". // // When parsing a certificate, a positive non-zero MaxPathLen // means that the field was specified, -1 means it was unset, // and MaxPathLenZero being true mean that the field was // explicitly set to zero. The case of MaxPathLen==0 with MaxPathLenZero==false // should be treated equivalent to -1 (unset). // // When generating a certificate, an unset pathLenConstraint // can be requested with either MaxPathLen == -1 or using the // zero value for both MaxPathLen and MaxPathLenZero. MaxPathLen int // MaxPathLenZero indicates that BasicConstraintsValid==true // and MaxPathLen==0 should be interpreted as an actual // maximum path length of zero. Otherwise, that combination is // interpreted as MaxPathLen not being set. MaxPathLenZero bool SubjectKeyId []byte AuthorityKeyId []byte // RFC 5280, 4.2.2.1 (Authority Information Access) OCSPServer []string IssuingCertificateURL []string // Subject Information Access SubjectTimestamps []string SubjectCARepositories []string // Subject Alternate Name values. (Note that these values may not be valid // if invalid values were contained within a parsed certificate. For // example, an element of DNSNames may not be a valid DNS domain name.) DNSNames []string EmailAddresses []string IPAddresses []net.IP URIs []*url.URL // Name constraints PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical. PermittedDNSDomains []string ExcludedDNSDomains []string PermittedIPRanges []*net.IPNet ExcludedIPRanges []*net.IPNet PermittedEmailAddresses []string ExcludedEmailAddresses []string PermittedURIDomains []string ExcludedURIDomains []string // CRL Distribution Points CRLDistributionPoints []string PolicyIdentifiers []asn1.ObjectIdentifier RPKIAddressRanges []*IPAddressFamilyBlocks RPKIASNumbers, RPKIRoutingDomainIDs *ASIdentifiers // Certificate Transparency SCT extension contents; this is a TLS-encoded // SignedCertificateTimestampList (RFC 6962 s3.3). RawSCT []byte SCTList SignedCertificateTimestampList } // ErrUnsupportedAlgorithm results from attempting to perform an operation that // involves algorithms that are not currently implemented. var ErrUnsupportedAlgorithm = errors.New("x509: cannot verify signature: algorithm unimplemented") // InsecureAlgorithmError results when the signature algorithm for a certificate // is known to be insecure. type InsecureAlgorithmError SignatureAlgorithm func (e InsecureAlgorithmError) Error() string { return fmt.Sprintf("x509: cannot verify signature: insecure algorithm %v", SignatureAlgorithm(e)) } // ConstraintViolationError results when a requested usage is not permitted by // a certificate. For example: checking a signature when the public key isn't a // certificate signing key. type ConstraintViolationError struct{} func (ConstraintViolationError) Error() string { return "x509: invalid signature: parent certificate cannot sign this kind of certificate" } // Equal indicates whether two Certificate objects are equal (by comparing their // DER-encoded values). func (c *Certificate) Equal(other *Certificate) bool { if c == nil || other == nil { return c == other } return bytes.Equal(c.Raw, other.Raw) } // IsPrecertificate checks whether the certificate is a precertificate, by // checking for the presence of the CT Poison extension. func (c *Certificate) IsPrecertificate() bool { if c == nil { return false } for _, ext := range c.Extensions { if ext.Id.Equal(OIDExtensionCTPoison) { return true } } return false } func (c *Certificate) hasSANExtension() bool { return oidInExtensions(OIDExtensionSubjectAltName, c.Extensions) } // Entrust have a broken root certificate (CN=Entrust.net Certification // Authority (2048)) which isn't marked as a CA certificate and is thus invalid // according to PKIX. // We recognise this certificate by its SubjectPublicKeyInfo and exempt it // from the Basic Constraints requirement. // See http://www.entrust.net/knowledge-base/technote.cfm?tn=7869 // // TODO(agl): remove this hack once their reissued root is sufficiently // widespread. var entrustBrokenSPKI = []byte{ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde, 0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b, 0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba, 0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0, 0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb, 0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc, 0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd, 0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1, 0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28, 0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77, 0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb, 0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49, 0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9, 0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a, 0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68, 0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d, 0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f, 0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90, 0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75, 0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15, 0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8, 0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03, 0x01, 0x00, 0x01, } // CheckSignatureFrom verifies that the signature on c is a valid signature // from parent. func (c *Certificate) CheckSignatureFrom(parent *Certificate) error { // RFC 5280, 4.2.1.9: // "If the basic constraints extension is not present in a version 3 // certificate, or the extension is present but the cA boolean is not // asserted, then the certified public key MUST NOT be used to verify // certificate signatures." // (except for Entrust, see comment above entrustBrokenSPKI) if (parent.Version == 3 && !parent.BasicConstraintsValid || parent.BasicConstraintsValid && !parent.IsCA) && !bytes.Equal(c.RawSubjectPublicKeyInfo, entrustBrokenSPKI) { return ConstraintViolationError{} } if parent.KeyUsage != 0 && parent.KeyUsage&KeyUsageCertSign == 0 { return ConstraintViolationError{} } if parent.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm { return ErrUnsupportedAlgorithm } // TODO(agl): don't ignore the path length constraint. return parent.CheckSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature) } // CheckSignature verifies that signature is a valid signature over signed from // c's public key. func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature []byte) error { return checkSignature(algo, signed, signature, c.PublicKey) } func (c *Certificate) hasNameConstraints() bool { return oidInExtensions(OIDExtensionNameConstraints, c.Extensions) } func (c *Certificate) getSANExtension() []byte { for _, e := range c.Extensions { if e.Id.Equal(OIDExtensionSubjectAltName) { return e.Value } } return nil } func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm, pubKey interface{}) error { return fmt.Errorf("x509: signature algorithm specifies an %s public key, but have public key of type %T", expectedPubKeyAlgo.String(), pubKey) } // CheckSignature verifies that signature is a valid signature over signed from // a crypto.PublicKey. func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey) (err error) { var hashType crypto.Hash var pubKeyAlgo PublicKeyAlgorithm for _, details := range signatureAlgorithmDetails { if details.algo == algo { hashType = details.hash pubKeyAlgo = details.pubKeyAlgo } } switch hashType { case crypto.Hash(0): if pubKeyAlgo != Ed25519 { return ErrUnsupportedAlgorithm } case crypto.MD5: return InsecureAlgorithmError(algo) default: if !hashType.Available() { return ErrUnsupportedAlgorithm } h := hashType.New() h.Write(signed) signed = h.Sum(nil) } switch pub := publicKey.(type) { case *rsa.PublicKey: if pubKeyAlgo != RSA { return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub) } if algo.isRSAPSS() { return rsa.VerifyPSS(pub, hashType, signed, signature, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) } else { return rsa.VerifyPKCS1v15(pub, hashType, signed, signature) } case *dsa.PublicKey: if pubKeyAlgo != DSA { return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub) } dsaSig := new(dsaSignature) if rest, err := asn1.Unmarshal(signature, dsaSig); err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after DSA signature") } if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 { return errors.New("x509: DSA signature contained zero or negative values") } // According to FIPS 186-3, section 4.6, the hash must be truncated if it is longer // than the key length, but crypto/dsa doesn't do it automatically. if maxHashLen := pub.Q.BitLen() / 8; maxHashLen < len(signed) { signed = signed[:maxHashLen] } if !dsa.Verify(pub, signed, dsaSig.R, dsaSig.S) { return errors.New("x509: DSA verification failure") } return case *ecdsa.PublicKey: if pubKeyAlgo != ECDSA { return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub) } ecdsaSig := new(ecdsaSignature) if rest, err := asn1.Unmarshal(signature, ecdsaSig); err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after ECDSA signature") } if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 { return errors.New("x509: ECDSA signature contained zero or negative values") } if !ecdsa.Verify(pub, signed, ecdsaSig.R, ecdsaSig.S) { return errors.New("x509: ECDSA verification failure") } return case ed25519.PublicKey: if pubKeyAlgo != Ed25519 { return signaturePublicKeyAlgoMismatchError(pubKeyAlgo, pub) } if !ed25519.Verify(pub, signed, signature) { return errors.New("x509: Ed25519 verification failure") } return } return ErrUnsupportedAlgorithm } // CheckCRLSignature checks that the signature in crl is from c. func (c *Certificate) CheckCRLSignature(crl *pkix.CertificateList) error { algo := SignatureAlgorithmFromAI(crl.SignatureAlgorithm) return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) } // UnhandledCriticalExtension results when the certificate contains an extension // that is marked as critical but which is not handled by this library. type UnhandledCriticalExtension struct { ID asn1.ObjectIdentifier } func (h UnhandledCriticalExtension) Error() string { return fmt.Sprintf("x509: unhandled critical extension (%v)", h.ID) } // removeExtension takes a DER-encoded TBSCertificate, removes the extension // specified by oid (preserving the order of other extensions), and returns the // result still as a DER-encoded TBSCertificate. This function will fail if // there is not exactly 1 extension of the type specified by the oid present. func removeExtension(tbsData []byte, oid asn1.ObjectIdentifier) ([]byte, error) { var tbs tbsCertificate rest, err := asn1.Unmarshal(tbsData, &tbs) if err != nil { return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err) } else if rLen := len(rest); rLen > 0 { return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen) } extAt := -1 for i, ext := range tbs.Extensions { if ext.Id.Equal(oid) { if extAt != -1 { return nil, errors.New("multiple extensions of specified type present") } extAt = i } } if extAt == -1 { return nil, errors.New("no extension of specified type present") } tbs.Extensions = append(tbs.Extensions[:extAt], tbs.Extensions[extAt+1:]...) // Clear out the asn1.RawContent so the re-marshal operation sees the // updated structure (rather than just copying the out-of-date DER data). tbs.Raw = nil data, err := asn1.Marshal(tbs) if err != nil { return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err) } return data, nil } // RemoveSCTList takes a DER-encoded TBSCertificate and removes the CT SCT // extension that contains the SCT list (preserving the order of other // extensions), and returns the result still as a DER-encoded TBSCertificate. // This function will fail if there is not exactly 1 CT SCT extension present. func RemoveSCTList(tbsData []byte) ([]byte, error) { return removeExtension(tbsData, OIDExtensionCTSCT) } // RemoveCTPoison takes a DER-encoded TBSCertificate and removes the CT poison // extension (preserving the order of other extensions), and returns the result // still as a DER-encoded TBSCertificate. This function will fail if there is // not exactly 1 CT poison extension present. func RemoveCTPoison(tbsData []byte) ([]byte, error) { return BuildPrecertTBS(tbsData, nil) } // BuildPrecertTBS builds a Certificate Transparency pre-certificate (RFC 6962 // s3.1) from the given DER-encoded TBSCertificate, returning a DER-encoded // TBSCertificate. // // This function removes the CT poison extension (there must be exactly 1 of // these), preserving the order of other extensions. // // If preIssuer is provided, this should be a special intermediate certificate // that was used to sign the precert (indicated by having the special // CertificateTransparency extended key usage). In this case, the issuance // information of the pre-cert is updated to reflect the next issuer in the // chain, i.e. the issuer of this special intermediate: // - The precert's Issuer is changed to the Issuer of the intermediate // - The precert's AuthorityKeyId is changed to the AuthorityKeyId of the // intermediate. func BuildPrecertTBS(tbsData []byte, preIssuer *Certificate) ([]byte, error) { data, err := removeExtension(tbsData, OIDExtensionCTPoison) if err != nil { return nil, err } var tbs tbsCertificate rest, err := asn1.Unmarshal(data, &tbs) if err != nil { return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err) } else if rLen := len(rest); rLen > 0 { return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen) } if preIssuer != nil { // Update the precert's Issuer field. Use the RawIssuer rather than the // parsed Issuer to avoid any chance of ASN.1 differences (e.g. switching // from UTF8String to PrintableString). tbs.Issuer.FullBytes = preIssuer.RawIssuer // Also need to update the cert's AuthorityKeyID extension // to that of the preIssuer. var issuerKeyID []byte for _, ext := range preIssuer.Extensions { if ext.Id.Equal(OIDExtensionAuthorityKeyId) { issuerKeyID = ext.Value break } } // Check the preIssuer has the CT EKU. seenCTEKU := false for _, eku := range preIssuer.ExtKeyUsage { if eku == ExtKeyUsageCertificateTransparency { seenCTEKU = true break } } if !seenCTEKU { return nil, fmt.Errorf("issuer does not have CertificateTransparency extended key usage") } keyAt := -1 for i, ext := range tbs.Extensions { if ext.Id.Equal(OIDExtensionAuthorityKeyId) { keyAt = i break } } if keyAt >= 0 { // PreCert has an auth-key-id; replace it with the value from the preIssuer if issuerKeyID != nil { tbs.Extensions[keyAt].Value = issuerKeyID } else { tbs.Extensions = append(tbs.Extensions[:keyAt], tbs.Extensions[keyAt+1:]...) } } else if issuerKeyID != nil { // PreCert did not have an auth-key-id, but the preIssuer does, so add it at the end. authKeyIDExt := pkix.Extension{ Id: OIDExtensionAuthorityKeyId, Critical: false, Value: issuerKeyID, } tbs.Extensions = append(tbs.Extensions, authKeyIDExt) } // Clear out the asn1.RawContent so the re-marshal operation sees the // updated structure (rather than just copying the out-of-date DER data). tbs.Raw = nil } data, err = asn1.Marshal(tbs) if err != nil { return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err) } return data, nil } type basicConstraints struct { IsCA bool `asn1:"optional"` MaxPathLen int `asn1:"optional,default:-1"` } // RFC 5280, 4.2.1.4 type policyInformation struct { Policy asn1.ObjectIdentifier // policyQualifiers omitted } const ( nameTypeEmail = 1 nameTypeDNS = 2 nameTypeURI = 6 nameTypeIP = 7 ) // RFC 5280, 4.2.2.1 type accessDescription struct { Method asn1.ObjectIdentifier Location asn1.RawValue } // RFC 5280, 4.2.1.14 type distributionPoint struct { DistributionPoint distributionPointName `asn1:"optional,tag:0"` Reason asn1.BitString `asn1:"optional,tag:1"` CRLIssuer asn1.RawValue `asn1:"optional,tag:2"` } type distributionPointName struct { FullName []asn1.RawValue `asn1:"optional,tag:0"` RelativeName pkix.RDNSequence `asn1:"optional,tag:1"` } func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo, nfe *NonFatalErrors) (interface{}, error) { asn1Data := keyData.PublicKey.RightAlign() switch algo { case RSA, RSAESOAEP: // RSA public keys must have a NULL in the parameters. // See RFC 3279, Section 2.3.1. if algo == RSA && !bytes.Equal(keyData.Algorithm.Parameters.FullBytes, asn1.NullBytes) { nfe.AddError(errors.New("x509: RSA key missing NULL parameters")) } if algo == RSAESOAEP { // We only parse the parameters to ensure it is a valid encoding, we throw out the actual values paramsData := keyData.Algorithm.Parameters.FullBytes params := new(rsaesoaepAlgorithmParameters) params.HashFunc = sha1Identifier params.MaskgenFunc = mgf1SHA1Identifier params.PSourceFunc = pSpecifiedEmptyIdentifier rest, err := asn1.Unmarshal(paramsData, params) if err != nil { return nil, err } if len(rest) != 0 { return nil, errors.New("x509: trailing data after RSAES-OAEP parameters") } } p := new(pkcs1PublicKey) rest, err := asn1.Unmarshal(asn1Data, p) if err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(asn1Data, p, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } if len(rest) != 0 { return nil, errors.New("x509: trailing data after RSA public key") } if p.N.Sign() <= 0 { nfe.AddError(errors.New("x509: RSA modulus is not a positive number")) } if p.E <= 0 { return nil, errors.New("x509: RSA public exponent is not a positive number") } // TODO(dkarch): Update to return the parameters once crypto/x509 has come up with permanent solution (https://github.com/golang/go/issues/30416) pub := &rsa.PublicKey{ E: p.E, N: p.N, } return pub, nil case DSA: var p *big.Int rest, err := asn1.Unmarshal(asn1Data, &p) if err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(asn1Data, &p, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } if len(rest) != 0 { return nil, errors.New("x509: trailing data after DSA public key") } paramsData := keyData.Algorithm.Parameters.FullBytes params := new(dsaAlgorithmParameters) rest, err = asn1.Unmarshal(paramsData, params) if err != nil { return nil, err } if len(rest) != 0 { return nil, errors.New("x509: trailing data after DSA parameters") } if p.Sign() <= 0 || params.P.Sign() <= 0 || params.Q.Sign() <= 0 || params.G.Sign() <= 0 { return nil, errors.New("x509: zero or negative DSA parameter") } pub := &dsa.PublicKey{ Parameters: dsa.Parameters{ P: params.P, Q: params.Q, G: params.G, }, Y: p, } return pub, nil case ECDSA: paramsData := keyData.Algorithm.Parameters.FullBytes namedCurveOID := new(asn1.ObjectIdentifier) rest, err := asn1.Unmarshal(paramsData, namedCurveOID) if err != nil { return nil, errors.New("x509: failed to parse ECDSA parameters as named curve") } if len(rest) != 0 { return nil, errors.New("x509: trailing data after ECDSA parameters") } namedCurve := namedCurveFromOID(*namedCurveOID, nfe) if namedCurve == nil { return nil, fmt.Errorf("x509: unsupported elliptic curve %v", namedCurveOID) } x, y := elliptic.Unmarshal(namedCurve, asn1Data) if x == nil { return nil, errors.New("x509: failed to unmarshal elliptic curve point") } pub := &ecdsa.PublicKey{ Curve: namedCurve, X: x, Y: y, } return pub, nil case Ed25519: return ed25519.PublicKey(asn1Data), nil default: return nil, nil } } // NonFatalErrors is an error type which can hold a number of other errors. // It's used to collect a range of non-fatal errors which occur while parsing // a certificate, that way we can still match on certs which technically are // invalid. type NonFatalErrors struct { Errors []error } // AddError adds an error to the list of errors contained by NonFatalErrors. func (e *NonFatalErrors) AddError(err error) { e.Errors = append(e.Errors, err) } // Returns a string consisting of the values of Error() from all of the errors // contained in |e| func (e NonFatalErrors) Error() string { r := "NonFatalErrors: " for _, err := range e.Errors { r += err.Error() + "; " } return r } // HasError returns true if |e| contains at least one error func (e *NonFatalErrors) HasError() bool { if e == nil { return false } return len(e.Errors) > 0 } // Append combines the contents of two NonFatalErrors instances. func (e *NonFatalErrors) Append(more *NonFatalErrors) *NonFatalErrors { if e == nil { return more } if more == nil { return e } combined := NonFatalErrors{Errors: make([]error, 0, len(e.Errors)+len(more.Errors))} combined.Errors = append(combined.Errors, e.Errors...) combined.Errors = append(combined.Errors, more.Errors...) return &combined } // IsFatal indicates whether an error is fatal. func IsFatal(err error) bool { if err == nil { return false } if _, ok := err.(NonFatalErrors); ok { return false } if errs, ok := err.(*Errors); ok { return errs.Fatal() } return true } func parseDistributionPoints(data []byte, crldp *[]string) error { // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint // // DistributionPoint ::= SEQUENCE { // distributionPoint [0] DistributionPointName OPTIONAL, // reasons [1] ReasonFlags OPTIONAL, // cRLIssuer [2] GeneralNames OPTIONAL } // // DistributionPointName ::= CHOICE { // fullName [0] GeneralNames, // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } var cdp []distributionPoint if rest, err := asn1.Unmarshal(data, &cdp); err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after X.509 CRL distribution point") } for _, dp := range cdp { // Per RFC 5280, 4.2.1.13, one of distributionPoint or cRLIssuer may be empty. if len(dp.DistributionPoint.FullName) == 0 { continue } for _, fullName := range dp.DistributionPoint.FullName { if fullName.Tag == 6 { *crldp = append(*crldp, string(fullName.Bytes)) } } } return nil } func forEachSAN(extension []byte, callback func(tag int, data []byte) error) error { // RFC 5280, 4.2.1.6 // SubjectAltName ::= GeneralNames // // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER } var seq asn1.RawValue rest, err := asn1.Unmarshal(extension, &seq) if err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after X.509 extension") } if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal { return asn1.StructuralError{Msg: "bad SAN sequence"} } rest = seq.Bytes for len(rest) > 0 { var v asn1.RawValue rest, err = asn1.Unmarshal(rest, &v) if err != nil { return err } if err := callback(v.Tag, v.Bytes); err != nil { return err } } return nil } func parseSANExtension(value []byte, nfe *NonFatalErrors) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) { err = forEachSAN(value, func(tag int, data []byte) error { switch tag { case nameTypeEmail: emailAddresses = append(emailAddresses, string(data)) case nameTypeDNS: dnsNames = append(dnsNames, string(data)) case nameTypeURI: uri, err := url.Parse(string(data)) if err != nil { return fmt.Errorf("x509: cannot parse URI %q: %s", string(data), err) } if len(uri.Host) > 0 { if _, ok := domainToReverseLabels(uri.Host); !ok { return fmt.Errorf("x509: cannot parse URI %q: invalid domain", string(data)) } } uris = append(uris, uri) case nameTypeIP: switch len(data) { case net.IPv4len, net.IPv6len: ipAddresses = append(ipAddresses, data) default: nfe.AddError(errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data)))) } } return nil }) return } // isValidIPMask reports whether mask consists of zero or more 1 bits, followed by zero bits. func isValidIPMask(mask []byte) bool { seenZero := false for _, b := range mask { if seenZero { if b != 0 { return false } continue } switch b { case 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe: seenZero = true case 0xff: default: return false } } return true } func parseNameConstraintsExtension(out *Certificate, e pkix.Extension, nfe *NonFatalErrors) (unhandled bool, err error) { // RFC 5280, 4.2.1.10 // NameConstraints ::= SEQUENCE { // permittedSubtrees [0] GeneralSubtrees OPTIONAL, // excludedSubtrees [1] GeneralSubtrees OPTIONAL } // // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree // // GeneralSubtree ::= SEQUENCE { // base GeneralName, // minimum [0] BaseDistance DEFAULT 0, // maximum [1] BaseDistance OPTIONAL } // // BaseDistance ::= INTEGER (0..MAX) outer := cryptobyte.String(e.Value) var toplevel, permitted, excluded cryptobyte.String var havePermitted, haveExcluded bool if !outer.ReadASN1(&toplevel, cryptobyte_asn1.SEQUENCE) || !outer.Empty() || !toplevel.ReadOptionalASN1(&permitted, &havePermitted, cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()) || !toplevel.ReadOptionalASN1(&excluded, &haveExcluded, cryptobyte_asn1.Tag(1).ContextSpecific().Constructed()) || !toplevel.Empty() { return false, errors.New("x509: invalid NameConstraints extension") } if !havePermitted && !haveExcluded || len(permitted) == 0 && len(excluded) == 0 { // From RFC 5280, Section 4.2.1.10: // “either the permittedSubtrees field // or the excludedSubtrees MUST be // present” return false, errors.New("x509: empty name constraints extension") } getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) { for !subtrees.Empty() { var seq, value cryptobyte.String var tag cryptobyte_asn1.Tag if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) || !seq.ReadAnyASN1(&value, &tag) { return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension") } var ( dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific() emailTag = cryptobyte_asn1.Tag(1).ContextSpecific() ipTag = cryptobyte_asn1.Tag(7).ContextSpecific() uriTag = cryptobyte_asn1.Tag(6).ContextSpecific() ) switch tag { case dnsTag: domain := string(value) if err := isIA5String(domain); err != nil { return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) } trimmedDomain := domain if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { // constraints can have a leading // period to exclude the domain // itself, but that's not valid in a // normal domain name. trimmedDomain = trimmedDomain[1:] } if _, ok := domainToReverseLabels(trimmedDomain); !ok { nfe.AddError(fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)) } dnsNames = append(dnsNames, domain) case ipTag: l := len(value) var ip, mask []byte switch l { case 8: ip = value[:4] mask = value[4:] case 32: ip = value[:16] mask = value[16:] default: return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l) } if !isValidIPMask(mask) { return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask) } ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)}) case emailTag: constraint := string(value) if err := isIA5String(constraint); err != nil { return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) } // If the constraint contains an @ then // it specifies an exact mailbox name. if strings.Contains(constraint, "@") { if _, ok := parseRFC2821Mailbox(constraint); !ok { nfe.AddError(fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)) } } else { // Otherwise it's a domain name. domain := constraint if len(domain) > 0 && domain[0] == '.' { domain = domain[1:] } if _, ok := domainToReverseLabels(domain); !ok { nfe.AddError(fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)) } } emails = append(emails, constraint) case uriTag: domain := string(value) if err := isIA5String(domain); err != nil { return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) } if net.ParseIP(domain) != nil { return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain) } trimmedDomain := domain if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { // constraints can have a leading // period to exclude the domain itself, // but that's not valid in a normal // domain name. trimmedDomain = trimmedDomain[1:] } if _, ok := domainToReverseLabels(trimmedDomain); !ok { nfe.AddError(fmt.Errorf("x509: failed to parse URI constraint %q", domain)) } uriDomains = append(uriDomains, domain) default: unhandled = true } } return dnsNames, ips, emails, uriDomains, nil } if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil { return false, err } if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil { return false, err } out.PermittedDNSDomainsCritical = e.Critical return unhandled, nil } func parseCertificate(in *certificate) (*Certificate, error) { var nfe NonFatalErrors out := new(Certificate) out.Raw = in.Raw out.RawTBSCertificate = in.TBSCertificate.Raw out.RawSubjectPublicKeyInfo = in.TBSCertificate.PublicKey.Raw out.RawSubject = in.TBSCertificate.Subject.FullBytes out.RawIssuer = in.TBSCertificate.Issuer.FullBytes out.Signature = in.SignatureValue.RightAlign() out.SignatureAlgorithm = SignatureAlgorithmFromAI(in.TBSCertificate.SignatureAlgorithm) out.PublicKeyAlgorithm = getPublicKeyAlgorithmFromOID(in.TBSCertificate.PublicKey.Algorithm.Algorithm) var err error out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, &in.TBSCertificate.PublicKey, &nfe) if err != nil { return nil, err } out.Version = in.TBSCertificate.Version + 1 out.SerialNumber = in.TBSCertificate.SerialNumber var issuer, subject pkix.RDNSequence if rest, err := asn1.Unmarshal(in.TBSCertificate.Subject.FullBytes, &subject); err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(in.TBSCertificate.Subject.FullBytes, &subject, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 subject") } if rest, err := asn1.Unmarshal(in.TBSCertificate.Issuer.FullBytes, &issuer); err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(in.TBSCertificate.Issuer.FullBytes, &issuer, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 subject") } out.Issuer.FillFromRDNSequence(&issuer) out.Subject.FillFromRDNSequence(&subject) out.NotBefore = in.TBSCertificate.Validity.NotBefore out.NotAfter = in.TBSCertificate.Validity.NotAfter for _, e := range in.TBSCertificate.Extensions { out.Extensions = append(out.Extensions, e) unhandled := false if len(e.Id) == 4 && e.Id[0] == OIDExtensionArc[0] && e.Id[1] == OIDExtensionArc[1] && e.Id[2] == OIDExtensionArc[2] { switch e.Id[3] { case OIDExtensionKeyUsage[3]: // RFC 5280, 4.2.1.3 var usageBits asn1.BitString if rest, err := asn1.Unmarshal(e.Value, &usageBits); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 KeyUsage") } var usage int for i := 0; i < 9; i++ { if usageBits.At(i) != 0 { usage |= 1 << uint(i) } } out.KeyUsage = KeyUsage(usage) case OIDExtensionBasicConstraints[3]: // RFC 5280, 4.2.1.9 var constraints basicConstraints if rest, err := asn1.Unmarshal(e.Value, &constraints); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 BasicConstraints") } out.BasicConstraintsValid = true out.IsCA = constraints.IsCA out.MaxPathLen = constraints.MaxPathLen out.MaxPathLenZero = out.MaxPathLen == 0 // TODO: map out.MaxPathLen to 0 if it has the -1 default value? (Issue 19285) case OIDExtensionSubjectAltName[3]: out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(e.Value, &nfe) if err != nil { return nil, err } if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 { // If we didn't parse anything then we do the critical check, below. unhandled = true } case OIDExtensionNameConstraints[3]: unhandled, err = parseNameConstraintsExtension(out, e, &nfe) if err != nil { return nil, err } case OIDExtensionCRLDistributionPoints[3]: // RFC 5280, 4.2.1.13 if err := parseDistributionPoints(e.Value, &out.CRLDistributionPoints); err != nil { return nil, err } case OIDExtensionAuthorityKeyId[3]: // RFC 5280, 4.2.1.1 var a authKeyId if rest, err := asn1.Unmarshal(e.Value, &a); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 authority key-id") } out.AuthorityKeyId = a.Id case OIDExtensionExtendedKeyUsage[3]: // RFC 5280, 4.2.1.12. Extended Key Usage // id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 } // // ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId // // KeyPurposeId ::= OBJECT IDENTIFIER var keyUsage []asn1.ObjectIdentifier if len(e.Value) == 0 { nfe.AddError(errors.New("x509: empty ExtendedKeyUsage")) } else { rest, err := asn1.Unmarshal(e.Value, &keyUsage) if err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(e.Value, &keyUsage, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 ExtendedKeyUsage") } } for _, u := range keyUsage { if extKeyUsage, ok := extKeyUsageFromOID(u); ok { out.ExtKeyUsage = append(out.ExtKeyUsage, extKeyUsage) } else { out.UnknownExtKeyUsage = append(out.UnknownExtKeyUsage, u) } } case OIDExtensionSubjectKeyId[3]: // RFC 5280, 4.2.1.2 var keyid []byte if rest, err := asn1.Unmarshal(e.Value, &keyid); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 key-id") } out.SubjectKeyId = keyid case OIDExtensionCertificatePolicies[3]: // RFC 5280 4.2.1.4: Certificate Policies var policies []policyInformation if rest, err := asn1.Unmarshal(e.Value, &policies); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 certificate policies") } out.PolicyIdentifiers = make([]asn1.ObjectIdentifier, len(policies)) for i, policy := range policies { out.PolicyIdentifiers[i] = policy.Policy } default: // Unknown extensions are recorded if critical. unhandled = true } } else if e.Id.Equal(OIDExtensionAuthorityInfoAccess) { // RFC 5280 4.2.2.1: Authority Information Access var aia []accessDescription if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 authority information") } if len(aia) == 0 { nfe.AddError(errors.New("x509: empty AuthorityInfoAccess extension")) } for _, v := range aia { // GeneralName: uniformResourceIdentifier [6] IA5String if v.Location.Tag != 6 { continue } if v.Method.Equal(OIDAuthorityInfoAccessOCSP) { out.OCSPServer = append(out.OCSPServer, string(v.Location.Bytes)) } else if v.Method.Equal(OIDAuthorityInfoAccessIssuers) { out.IssuingCertificateURL = append(out.IssuingCertificateURL, string(v.Location.Bytes)) } } } else if e.Id.Equal(OIDExtensionSubjectInfoAccess) { // RFC 5280 4.2.2.2: Subject Information Access var sia []accessDescription if rest, err := asn1.Unmarshal(e.Value, &sia); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 subject information") } if len(sia) == 0 { nfe.AddError(errors.New("x509: empty SubjectInfoAccess extension")) } for _, v := range sia { // TODO(drysdale): cope with non-URI types of GeneralName // GeneralName: uniformResourceIdentifier [6] IA5String if v.Location.Tag != 6 { continue } if v.Method.Equal(OIDSubjectInfoAccessTimestamp) { out.SubjectTimestamps = append(out.SubjectTimestamps, string(v.Location.Bytes)) } else if v.Method.Equal(OIDSubjectInfoAccessCARepo) { out.SubjectCARepositories = append(out.SubjectCARepositories, string(v.Location.Bytes)) } } } else if e.Id.Equal(OIDExtensionIPPrefixList) { out.RPKIAddressRanges = parseRPKIAddrBlocks(e.Value, &nfe) } else if e.Id.Equal(OIDExtensionASList) { out.RPKIASNumbers, out.RPKIRoutingDomainIDs = parseRPKIASIdentifiers(e.Value, &nfe) } else if e.Id.Equal(OIDExtensionCTSCT) { if rest, err := asn1.Unmarshal(e.Value, &out.RawSCT); err != nil { nfe.AddError(fmt.Errorf("failed to asn1.Unmarshal SCT list extension: %v", err)) } else if len(rest) != 0 { nfe.AddError(errors.New("trailing data after ASN1-encoded SCT list")) } else { if rest, err := tls.Unmarshal(out.RawSCT, &out.SCTList); err != nil { nfe.AddError(fmt.Errorf("failed to tls.Unmarshal SCT list: %v", err)) } else if len(rest) != 0 { nfe.AddError(errors.New("trailing data after TLS-encoded SCT list")) } } } else { // Unknown extensions are recorded if critical. unhandled = true } if e.Critical && unhandled { out.UnhandledCriticalExtensions = append(out.UnhandledCriticalExtensions, e.Id) } } if nfe.HasError() { return out, nfe } return out, nil } // ParseTBSCertificate parses a single TBSCertificate from the given ASN.1 DER data. // The parsed data is returned in a Certificate struct for ease of access. func ParseTBSCertificate(asn1Data []byte) (*Certificate, error) { var tbsCert tbsCertificate var nfe NonFatalErrors rest, err := asn1.Unmarshal(asn1Data, &tbsCert) if err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(asn1Data, &tbsCert, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } if len(rest) > 0 { return nil, asn1.SyntaxError{Msg: "trailing data"} } ret, err := parseCertificate(&certificate{ Raw: tbsCert.Raw, TBSCertificate: tbsCert}) if err != nil { errs, ok := err.(NonFatalErrors) if !ok { return nil, err } nfe.Errors = append(nfe.Errors, errs.Errors...) } if nfe.HasError() { return ret, nfe } return ret, nil } // ParseCertificate parses a single certificate from the given ASN.1 DER data. // This function can return both a Certificate and an error (in which case the // error will be of type NonFatalErrors). func ParseCertificate(asn1Data []byte) (*Certificate, error) { var cert certificate var nfe NonFatalErrors rest, err := asn1.Unmarshal(asn1Data, &cert) if err != nil { var laxErr error rest, laxErr = asn1.UnmarshalWithParams(asn1Data, &cert, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } if len(rest) > 0 { return nil, asn1.SyntaxError{Msg: "trailing data"} } ret, err := parseCertificate(&cert) if err != nil { errs, ok := err.(NonFatalErrors) if !ok { return nil, err } nfe.Errors = append(nfe.Errors, errs.Errors...) } if nfe.HasError() { return ret, nfe } return ret, nil } // ParseCertificates parses one or more certificates from the given ASN.1 DER // data. The certificates must be concatenated with no intermediate padding. // This function can return both a slice of Certificate and an error (in which // case the error will be of type NonFatalErrors). func ParseCertificates(asn1Data []byte) ([]*Certificate, error) { var v []*certificate var nfe NonFatalErrors for len(asn1Data) > 0 { cert := new(certificate) var err error asn1Data, err = asn1.Unmarshal(asn1Data, cert) if err != nil { var laxErr error asn1Data, laxErr = asn1.UnmarshalWithParams(asn1Data, &cert, "lax") if laxErr != nil { return nil, laxErr } nfe.AddError(err) } v = append(v, cert) } ret := make([]*Certificate, len(v)) for i, ci := range v { cert, err := parseCertificate(ci) if err != nil { errs, ok := err.(NonFatalErrors) if !ok { return nil, err } nfe.Errors = append(nfe.Errors, errs.Errors...) } ret[i] = cert } if nfe.HasError() { return ret, nfe } return ret, nil } func reverseBitsInAByte(in byte) byte { b1 := in>>4 | in<<4 b2 := b1>>2&0x33 | b1<<2&0xcc b3 := b2>>1&0x55 | b2<<1&0xaa return b3 } // asn1BitLength returns the bit-length of bitString by considering the // most-significant bit in a byte to be the "first" bit. This convention // matches ASN.1, but differs from almost everything else. func asn1BitLength(bitString []byte) int { bitLen := len(bitString) * 8 for i := range bitString { b := bitString[len(bitString)-i-1] for bit := uint(0); bit < 8; bit++ { if (b>>bit)&1 == 1 { return bitLen } bitLen-- } } return 0 } // OID values for standard extensions from RFC 5280. var ( OIDExtensionArc = asn1.ObjectIdentifier{2, 5, 29} // id-ce RFC5280 s4.2.1 OIDExtensionSubjectKeyId = asn1.ObjectIdentifier{2, 5, 29, 14} OIDExtensionKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15} OIDExtensionExtendedKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37} OIDExtensionAuthorityKeyId = asn1.ObjectIdentifier{2, 5, 29, 35} OIDExtensionBasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19} OIDExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17} OIDExtensionCertificatePolicies = asn1.ObjectIdentifier{2, 5, 29, 32} OIDExtensionNameConstraints = asn1.ObjectIdentifier{2, 5, 29, 30} OIDExtensionCRLDistributionPoints = asn1.ObjectIdentifier{2, 5, 29, 31} OIDExtensionIssuerAltName = asn1.ObjectIdentifier{2, 5, 29, 18} OIDExtensionSubjectDirectoryAttributes = asn1.ObjectIdentifier{2, 5, 29, 9} OIDExtensionInhibitAnyPolicy = asn1.ObjectIdentifier{2, 5, 29, 54} OIDExtensionPolicyConstraints = asn1.ObjectIdentifier{2, 5, 29, 36} OIDExtensionPolicyMappings = asn1.ObjectIdentifier{2, 5, 29, 33} OIDExtensionFreshestCRL = asn1.ObjectIdentifier{2, 5, 29, 46} OIDExtensionAuthorityInfoAccess = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1} OIDExtensionSubjectInfoAccess = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 11} // OIDExtensionCTPoison is defined in RFC 6962 s3.1. OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3} // OIDExtensionCTSCT is defined in RFC 6962 s3.3. OIDExtensionCTSCT = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2} // OIDExtensionIPPrefixList is defined in RFC 3779 s2. OIDExtensionIPPrefixList = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 7} // OIDExtensionASList is defined in RFC 3779 s3. OIDExtensionASList = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 8} ) var ( OIDAuthorityInfoAccessOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1} OIDAuthorityInfoAccessIssuers = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 2} OIDSubjectInfoAccessTimestamp = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 3} OIDSubjectInfoAccessCARepo = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 5} OIDAnyPolicy = asn1.ObjectIdentifier{2, 5, 29, 32, 0} ) // oidInExtensions reports whether an extension with the given oid exists in // extensions. func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) bool { for _, e := range extensions { if e.Id.Equal(oid) { return true } } return false } // marshalSANs marshals a list of addresses into a the contents of an X.509 // SubjectAlternativeName extension. func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) (derBytes []byte, err error) { var rawValues []asn1.RawValue for _, name := range dnsNames { rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: asn1.ClassContextSpecific, Bytes: []byte(name)}) } for _, email := range emailAddresses { rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: asn1.ClassContextSpecific, Bytes: []byte(email)}) } for _, rawIP := range ipAddresses { // If possible, we always want to encode IPv4 addresses in 4 bytes. ip := rawIP.To4() if ip == nil { ip = rawIP } rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: asn1.ClassContextSpecific, Bytes: ip}) } for _, uri := range uris { rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: asn1.ClassContextSpecific, Bytes: []byte(uri.String())}) } return asn1.Marshal(rawValues) } func isIA5String(s string) error { for _, r := range s { if r >= utf8.RuneSelf { return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s) } } return nil } func buildExtensions(template *Certificate, subjectIsEmpty bool, authorityKeyId []byte) (ret []pkix.Extension, err error) { ret = make([]pkix.Extension, 12 /* maximum number of elements. */) n := 0 if template.KeyUsage != 0 && !oidInExtensions(OIDExtensionKeyUsage, template.ExtraExtensions) { ret[n].Id = OIDExtensionKeyUsage ret[n].Critical = true var a [2]byte a[0] = reverseBitsInAByte(byte(template.KeyUsage)) a[1] = reverseBitsInAByte(byte(template.KeyUsage >> 8)) l := 1 if a[1] != 0 { l = 2 } bitString := a[:l] ret[n].Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)}) if err != nil { return } n++ } if (len(template.ExtKeyUsage) > 0 || len(template.UnknownExtKeyUsage) > 0) && !oidInExtensions(OIDExtensionExtendedKeyUsage, template.ExtraExtensions) { ret[n].Id = OIDExtensionExtendedKeyUsage var oids []asn1.ObjectIdentifier for _, u := range template.ExtKeyUsage { if oid, ok := oidFromExtKeyUsage(u); ok { oids = append(oids, oid) } else { panic("internal error") } } oids = append(oids, template.UnknownExtKeyUsage...) ret[n].Value, err = asn1.Marshal(oids) if err != nil { return } n++ } if template.BasicConstraintsValid && !oidInExtensions(OIDExtensionBasicConstraints, template.ExtraExtensions) { // Leaving MaxPathLen as zero indicates that no maximum path // length is desired, unless MaxPathLenZero is set. A value of // -1 causes encoding/asn1 to omit the value as desired. maxPathLen := template.MaxPathLen if maxPathLen == 0 && !template.MaxPathLenZero { maxPathLen = -1 } ret[n].Id = OIDExtensionBasicConstraints ret[n].Value, err = asn1.Marshal(basicConstraints{template.IsCA, maxPathLen}) ret[n].Critical = true if err != nil { return } n++ } if len(template.SubjectKeyId) > 0 && !oidInExtensions(OIDExtensionSubjectKeyId, template.ExtraExtensions) { ret[n].Id = OIDExtensionSubjectKeyId ret[n].Value, err = asn1.Marshal(template.SubjectKeyId) if err != nil { return } n++ } if len(authorityKeyId) > 0 && !oidInExtensions(OIDExtensionAuthorityKeyId, template.ExtraExtensions) { ret[n].Id = OIDExtensionAuthorityKeyId ret[n].Value, err = asn1.Marshal(authKeyId{authorityKeyId}) if err != nil { return } n++ } if (len(template.OCSPServer) > 0 || len(template.IssuingCertificateURL) > 0) && !oidInExtensions(OIDExtensionAuthorityInfoAccess, template.ExtraExtensions) { ret[n].Id = OIDExtensionAuthorityInfoAccess var aiaValues []accessDescription for _, name := range template.OCSPServer { aiaValues = append(aiaValues, accessDescription{ Method: OIDAuthorityInfoAccessOCSP, Location: asn1.RawValue{Tag: 6, Class: asn1.ClassContextSpecific, Bytes: []byte(name)}, }) } for _, name := range template.IssuingCertificateURL { aiaValues = append(aiaValues, accessDescription{ Method: OIDAuthorityInfoAccessIssuers, Location: asn1.RawValue{Tag: 6, Class: asn1.ClassContextSpecific, Bytes: []byte(name)}, }) } ret[n].Value, err = asn1.Marshal(aiaValues) if err != nil { return } n++ } if len(template.SubjectTimestamps) > 0 || len(template.SubjectCARepositories) > 0 && !oidInExtensions(OIDExtensionSubjectInfoAccess, template.ExtraExtensions) { ret[n].Id = OIDExtensionSubjectInfoAccess var siaValues []accessDescription for _, ts := range template.SubjectTimestamps { siaValues = append(siaValues, accessDescription{ Method: OIDSubjectInfoAccessTimestamp, Location: asn1.RawValue{Tag: 6, Class: asn1.ClassContextSpecific, Bytes: []byte(ts)}, }) } for _, repo := range template.SubjectCARepositories { siaValues = append(siaValues, accessDescription{ Method: OIDSubjectInfoAccessCARepo, Location: asn1.RawValue{Tag: 6, Class: asn1.ClassContextSpecific, Bytes: []byte(repo)}, }) } ret[n].Value, err = asn1.Marshal(siaValues) if err != nil { return } n++ } if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 || len(template.URIs) > 0) && !oidInExtensions(OIDExtensionSubjectAltName, template.ExtraExtensions) { ret[n].Id = OIDExtensionSubjectAltName // From RFC 5280, Section 4.2.1.6: // “If the subject field contains an empty sequence ... then // subjectAltName extension ... is marked as critical” ret[n].Critical = subjectIsEmpty ret[n].Value, err = marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses, template.URIs) if err != nil { return } n++ } if len(template.PolicyIdentifiers) > 0 && !oidInExtensions(OIDExtensionCertificatePolicies, template.ExtraExtensions) { ret[n].Id = OIDExtensionCertificatePolicies policies := make([]policyInformation, len(template.PolicyIdentifiers)) for i, policy := range template.PolicyIdentifiers { policies[i].Policy = policy } ret[n].Value, err = asn1.Marshal(policies) if err != nil { return } n++ } if (len(template.PermittedDNSDomains) > 0 || len(template.ExcludedDNSDomains) > 0 || len(template.PermittedIPRanges) > 0 || len(template.ExcludedIPRanges) > 0 || len(template.PermittedEmailAddresses) > 0 || len(template.ExcludedEmailAddresses) > 0 || len(template.PermittedURIDomains) > 0 || len(template.ExcludedURIDomains) > 0) && !oidInExtensions(OIDExtensionNameConstraints, template.ExtraExtensions) { ret[n].Id = OIDExtensionNameConstraints ret[n].Critical = template.PermittedDNSDomainsCritical ipAndMask := func(ipNet *net.IPNet) []byte { maskedIP := ipNet.IP.Mask(ipNet.Mask) ipAndMask := make([]byte, 0, len(maskedIP)+len(ipNet.Mask)) ipAndMask = append(ipAndMask, maskedIP...) ipAndMask = append(ipAndMask, ipNet.Mask...) return ipAndMask } serialiseConstraints := func(dns []string, ips []*net.IPNet, emails []string, uriDomains []string) (der []byte, err error) { var b cryptobyte.Builder for _, name := range dns { if err = isIA5String(name); err != nil { return nil, err } b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1(cryptobyte_asn1.Tag(2).ContextSpecific(), func(b *cryptobyte.Builder) { b.AddBytes([]byte(name)) }) }) } for _, ipNet := range ips { b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1(cryptobyte_asn1.Tag(7).ContextSpecific(), func(b *cryptobyte.Builder) { b.AddBytes(ipAndMask(ipNet)) }) }) } for _, email := range emails { if err = isIA5String(email); err != nil { return nil, err } b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific(), func(b *cryptobyte.Builder) { b.AddBytes([]byte(email)) }) }) } for _, uriDomain := range uriDomains { if err = isIA5String(uriDomain); err != nil { return nil, err } b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1(cryptobyte_asn1.Tag(6).ContextSpecific(), func(b *cryptobyte.Builder) { b.AddBytes([]byte(uriDomain)) }) }) } return b.Bytes() } permitted, err := serialiseConstraints(template.PermittedDNSDomains, template.PermittedIPRanges, template.PermittedEmailAddresses, template.PermittedURIDomains) if err != nil { return nil, err } excluded, err := serialiseConstraints(template.ExcludedDNSDomains, template.ExcludedIPRanges, template.ExcludedEmailAddresses, template.ExcludedURIDomains) if err != nil { return nil, err } var b cryptobyte.Builder b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { if len(permitted) > 0 { b.AddASN1(cryptobyte_asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { b.AddBytes(permitted) }) } if len(excluded) > 0 { b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { b.AddBytes(excluded) }) } }) ret[n].Value, err = b.Bytes() if err != nil { return nil, err } n++ } if len(template.CRLDistributionPoints) > 0 && !oidInExtensions(OIDExtensionCRLDistributionPoints, template.ExtraExtensions) { ret[n].Id = OIDExtensionCRLDistributionPoints var crlDp []distributionPoint for _, name := range template.CRLDistributionPoints { dp := distributionPoint{ DistributionPoint: distributionPointName{ FullName: []asn1.RawValue{ {Tag: 6, Class: asn1.ClassContextSpecific, Bytes: []byte(name)}, }, }, } crlDp = append(crlDp, dp) } ret[n].Value, err = asn1.Marshal(crlDp) if err != nil { return } n++ } if (len(template.RawSCT) > 0 || len(template.SCTList.SCTList) > 0) && !oidInExtensions(OIDExtensionCTSCT, template.ExtraExtensions) { rawSCT := template.RawSCT if len(template.SCTList.SCTList) > 0 { rawSCT, err = tls.Marshal(template.SCTList) if err != nil { return } } ret[n].Id = OIDExtensionCTSCT ret[n].Value, err = asn1.Marshal(rawSCT) if err != nil { return } n++ } // Adding another extension here? Remember to update the maximum number // of elements in the make() at the top of the function and the list of // template fields used in CreateCertificate documentation. return append(ret[:n], template.ExtraExtensions...), nil } func subjectBytes(cert *Certificate) ([]byte, error) { if len(cert.RawSubject) > 0 { return cert.RawSubject, nil } return asn1.Marshal(cert.Subject.ToRDNSequence()) } // signingParamsForPublicKey returns the parameters to use for signing with // priv. If requestedSigAlgo is not zero then it overrides the default // signature algorithm. func signingParamsForPublicKey(pub interface{}, requestedSigAlgo SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) { var pubType PublicKeyAlgorithm switch pub := pub.(type) { case *rsa.PublicKey: pubType = RSA hashFunc = crypto.SHA256 sigAlgo.Algorithm = oidSignatureSHA256WithRSA sigAlgo.Parameters = asn1.NullRawValue case *ecdsa.PublicKey: pubType = ECDSA switch pub.Curve { case elliptic.P224(), elliptic.P256(): hashFunc = crypto.SHA256 sigAlgo.Algorithm = oidSignatureECDSAWithSHA256 case elliptic.P384(): hashFunc = crypto.SHA384 sigAlgo.Algorithm = oidSignatureECDSAWithSHA384 case elliptic.P521(): hashFunc = crypto.SHA512 sigAlgo.Algorithm = oidSignatureECDSAWithSHA512 default: err = errors.New("x509: unknown elliptic curve") } case ed25519.PublicKey: pubType = Ed25519 sigAlgo.Algorithm = oidSignatureEd25519 default: err = errors.New("x509: only RSA, ECDSA and Ed25519 keys supported") } if err != nil { return } if requestedSigAlgo == 0 { return } found := false for _, details := range signatureAlgorithmDetails { if details.algo == requestedSigAlgo { if details.pubKeyAlgo != pubType { err = errors.New("x509: requested SignatureAlgorithm does not match private key type") return } sigAlgo.Algorithm, hashFunc = details.oid, details.hash if hashFunc == 0 && pubType != Ed25519 { err = errors.New("x509: cannot sign with hash function requested") return } if requestedSigAlgo.isRSAPSS() { sigAlgo.Parameters = rsaPSSParameters(hashFunc) } found = true break } } if !found { err = errors.New("x509: unknown SignatureAlgorithm") } return } // emptyASN1Subject is the ASN.1 DER encoding of an empty Subject, which is // just an empty SEQUENCE. var emptyASN1Subject = []byte{0x30, 0} // CreateCertificate creates a new X.509v3 certificate based on a template. // The following members of template are used: // - SerialNumber // - Subject // - NotBefore, NotAfter // - SignatureAlgorithm // - For extensions: // - KeyUsage // - ExtKeyUsage, UnknownExtKeyUsage // - BasicConstraintsValid, IsCA, MaxPathLen, MaxPathLenZero // - SubjectKeyId // - AuthorityKeyId // - OCSPServer, IssuingCertificateURL // - SubjectTimestamps, SubjectCARepositories // - DNSNames, EmailAddresses, IPAddresses, URIs // - PolicyIdentifiers // - ExcludedDNSDomains, ExcludedIPRanges, ExcludedEmailAddresses, ExcludedURIDomains, PermittedDNSDomainsCritical, // PermittedDNSDomains, PermittedIPRanges, PermittedEmailAddresses, PermittedURIDomains // - CRLDistributionPoints // - RawSCT, SCTList // - ExtraExtensions // // The certificate is signed by parent. If parent is equal to template then the // certificate is self-signed. The parameter pub is the public key of the // signee and priv is the private key of the signer. // // The returned slice is the certificate in DER encoding. // // The currently supported key types are *rsa.PublicKey, *ecdsa.PublicKey and // ed25519.PublicKey. pub must be a supported key type, and priv must be a // crypto.Signer with a supported public key. // // The AuthorityKeyId will be taken from the SubjectKeyId of parent, if any, // unless the resulting certificate is self-signed. Otherwise the value from // template will be used. func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv interface{}) (cert []byte, err error) { key, ok := priv.(crypto.Signer) if !ok { return nil, errors.New("x509: certificate private key does not implement crypto.Signer") } if template.SerialNumber == nil { return nil, errors.New("x509: no SerialNumber given") } hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(key.Public(), template.SignatureAlgorithm) if err != nil { return nil, err } publicKeyBytes, publicKeyAlgorithm, err := marshalPublicKey(pub) if err != nil { return nil, err } asn1Issuer, err := subjectBytes(parent) if err != nil { return } asn1Subject, err := subjectBytes(template) if err != nil { return } authorityKeyId := template.AuthorityKeyId if !bytes.Equal(asn1Issuer, asn1Subject) && len(parent.SubjectKeyId) > 0 { authorityKeyId = parent.SubjectKeyId } extensions, err := buildExtensions(template, bytes.Equal(asn1Subject, emptyASN1Subject), authorityKeyId) if err != nil { return } encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes} c := tbsCertificate{ Version: 2, SerialNumber: template.SerialNumber, SignatureAlgorithm: signatureAlgorithm, Issuer: asn1.RawValue{FullBytes: asn1Issuer}, Validity: validity{template.NotBefore.UTC(), template.NotAfter.UTC()}, Subject: asn1.RawValue{FullBytes: asn1Subject}, PublicKey: publicKeyInfo{nil, publicKeyAlgorithm, encodedPublicKey}, Extensions: extensions, } tbsCertContents, err := asn1.Marshal(c) if err != nil { return } c.Raw = tbsCertContents signed := tbsCertContents if hashFunc != 0 { h := hashFunc.New() h.Write(signed) signed = h.Sum(nil) } var signerOpts crypto.SignerOpts = hashFunc if template.SignatureAlgorithm != 0 && template.SignatureAlgorithm.isRSAPSS() { signerOpts = &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: hashFunc, } } var signature []byte signature, err = key.Sign(rand, signed, signerOpts) if err != nil { return } return asn1.Marshal(certificate{ nil, c, signatureAlgorithm, asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}, }) } // pemCRLPrefix is the magic string that indicates that we have a PEM encoded // CRL. var pemCRLPrefix = []byte("-----BEGIN X509 CRL") // pemType is the type of a PEM encoded CRL. var pemType = "X509 CRL" // ParseCRL parses a CRL from the given bytes. It's often the case that PEM // encoded CRLs will appear where they should be DER encoded, so this function // will transparently handle PEM encoding as long as there isn't any leading // garbage. func ParseCRL(crlBytes []byte) (*pkix.CertificateList, error) { if bytes.HasPrefix(crlBytes, pemCRLPrefix) { block, _ := pem.Decode(crlBytes) if block != nil && block.Type == pemType { crlBytes = block.Bytes } } return ParseDERCRL(crlBytes) } // ParseDERCRL parses a DER encoded CRL from the given bytes. func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) { certList := new(pkix.CertificateList) if rest, err := asn1.Unmarshal(derBytes, certList); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after CRL") } return certList, nil } // CreateCRL returns a DER encoded CRL, signed by this Certificate, that // contains the given list of revoked certificates. func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) { key, ok := priv.(crypto.Signer) if !ok { return nil, errors.New("x509: certificate private key does not implement crypto.Signer") } hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(key.Public(), 0) if err != nil { return nil, err } // Force revocation times to UTC per RFC 5280. revokedCertsUTC := make([]pkix.RevokedCertificate, len(revokedCerts)) for i, rc := range revokedCerts { rc.RevocationTime = rc.RevocationTime.UTC() revokedCertsUTC[i] = rc } tbsCertList := pkix.TBSCertificateList{ Version: 1, Signature: signatureAlgorithm, Issuer: c.Subject.ToRDNSequence(), ThisUpdate: now.UTC(), NextUpdate: expiry.UTC(), RevokedCertificates: revokedCertsUTC, } // Authority Key Id if len(c.SubjectKeyId) > 0 { var aki pkix.Extension aki.Id = OIDExtensionAuthorityKeyId aki.Value, err = asn1.Marshal(authKeyId{Id: c.SubjectKeyId}) if err != nil { return } tbsCertList.Extensions = append(tbsCertList.Extensions, aki) } tbsCertListContents, err := asn1.Marshal(tbsCertList) if err != nil { return } signed := tbsCertListContents if hashFunc != 0 { h := hashFunc.New() h.Write(signed) signed = h.Sum(nil) } var signature []byte signature, err = key.Sign(rand, signed, hashFunc) if err != nil { return } return asn1.Marshal(pkix.CertificateList{ TBSCertList: tbsCertList, SignatureAlgorithm: signatureAlgorithm, SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}, }) } // CertificateRequest represents a PKCS #10, certificate signature request. type CertificateRequest struct { Raw []byte // Complete ASN.1 DER content (CSR, signature algorithm and signature). RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content. RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo. RawSubject []byte // DER encoded Subject. Version int Signature []byte SignatureAlgorithm SignatureAlgorithm PublicKeyAlgorithm PublicKeyAlgorithm PublicKey interface{} Subject pkix.Name // Attributes contains the CSR attributes that can parse as // pkix.AttributeTypeAndValueSET. // // Deprecated: Use Extensions and ExtraExtensions instead for parsing and // generating the requestedExtensions attribute. Attributes []pkix.AttributeTypeAndValueSET // Extensions contains all requested extensions, in raw form. When parsing // CSRs, this can be used to extract extensions that are not parsed by this // package. Extensions []pkix.Extension // ExtraExtensions contains extensions to be copied, raw, into any CSR // marshaled by CreateCertificateRequest. Values override any extensions // that would otherwise be produced based on the other fields but are // overridden by any extensions specified in Attributes. // // The ExtraExtensions field is not populated by ParseCertificateRequest, // see Extensions instead. ExtraExtensions []pkix.Extension // Subject Alternate Name values. DNSNames []string EmailAddresses []string IPAddresses []net.IP URIs []*url.URL } // These structures reflect the ASN.1 structure of X.509 certificate // signature requests (see RFC 2986): type tbsCertificateRequest struct { Raw asn1.RawContent Version int Subject asn1.RawValue PublicKey publicKeyInfo RawAttributes []asn1.RawValue `asn1:"tag:0"` } type certificateRequest struct { Raw asn1.RawContent TBSCSR tbsCertificateRequest SignatureAlgorithm pkix.AlgorithmIdentifier SignatureValue asn1.BitString } // oidExtensionRequest is a PKCS#9 OBJECT IDENTIFIER that indicates requested // extensions in a CSR. var oidExtensionRequest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 14} // newRawAttributes converts AttributeTypeAndValueSETs from a template // CertificateRequest's Attributes into tbsCertificateRequest RawAttributes. func newRawAttributes(attributes []pkix.AttributeTypeAndValueSET) ([]asn1.RawValue, error) { var rawAttributes []asn1.RawValue b, err := asn1.Marshal(attributes) if err != nil { return nil, err } rest, err := asn1.Unmarshal(b, &rawAttributes) if err != nil { return nil, err } if len(rest) != 0 { return nil, errors.New("x509: failed to unmarshal raw CSR Attributes") } return rawAttributes, nil } // parseRawAttributes Unmarshals RawAttributes into AttributeTypeAndValueSETs. func parseRawAttributes(rawAttributes []asn1.RawValue) []pkix.AttributeTypeAndValueSET { var attributes []pkix.AttributeTypeAndValueSET for _, rawAttr := range rawAttributes { var attr pkix.AttributeTypeAndValueSET rest, err := asn1.Unmarshal(rawAttr.FullBytes, &attr) // Ignore attributes that don't parse into pkix.AttributeTypeAndValueSET // (i.e.: challengePassword or unstructuredName). if err == nil && len(rest) == 0 { attributes = append(attributes, attr) } } return attributes } // parseCSRExtensions parses the attributes from a CSR and extracts any // requested extensions. func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error) { // pkcs10Attribute reflects the Attribute structure from RFC 2986, Section 4.1. type pkcs10Attribute struct { Id asn1.ObjectIdentifier Values []asn1.RawValue `asn1:"set"` } var ret []pkix.Extension for _, rawAttr := range rawAttributes { var attr pkcs10Attribute if rest, err := asn1.Unmarshal(rawAttr.FullBytes, &attr); err != nil || len(rest) != 0 || len(attr.Values) == 0 { // Ignore attributes that don't parse. continue } if !attr.Id.Equal(oidExtensionRequest) { continue } var extensions []pkix.Extension if _, err := asn1.Unmarshal(attr.Values[0].FullBytes, &extensions); err != nil { return nil, err } ret = append(ret, extensions...) } return ret, nil } // CreateCertificateRequest creates a new certificate request based on a // template. The following members of template are used: // // - SignatureAlgorithm // - Subject // - DNSNames // - EmailAddresses // - IPAddresses // - URIs // - ExtraExtensions // - Attributes (deprecated) // // priv is the private key to sign the CSR with, and the corresponding public // key will be included in the CSR. It must implement crypto.Signer and its // Public() method must return a *rsa.PublicKey or a *ecdsa.PublicKey or a // ed25519.PublicKey. (A *rsa.PrivateKey, *ecdsa.PrivateKey or // ed25519.PrivateKey satisfies this.) // // The returned slice is the certificate request in DER encoding. func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error) { key, ok := priv.(crypto.Signer) if !ok { return nil, errors.New("x509: certificate private key does not implement crypto.Signer") } var hashFunc crypto.Hash var sigAlgo pkix.AlgorithmIdentifier hashFunc, sigAlgo, err = signingParamsForPublicKey(key.Public(), template.SignatureAlgorithm) if err != nil { return nil, err } var publicKeyBytes []byte var publicKeyAlgorithm pkix.AlgorithmIdentifier publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(key.Public()) if err != nil { return nil, err } var extensions []pkix.Extension if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 || len(template.URIs) > 0) && !oidInExtensions(OIDExtensionSubjectAltName, template.ExtraExtensions) { sanBytes, err := marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses, template.URIs) if err != nil { return nil, err } extensions = append(extensions, pkix.Extension{ Id: OIDExtensionSubjectAltName, Value: sanBytes, }) } extensions = append(extensions, template.ExtraExtensions...) // Make a copy of template.Attributes because we may alter it below. attributes := make([]pkix.AttributeTypeAndValueSET, 0, len(template.Attributes)) for _, attr := range template.Attributes { values := make([][]pkix.AttributeTypeAndValue, len(attr.Value)) copy(values, attr.Value) attributes = append(attributes, pkix.AttributeTypeAndValueSET{ Type: attr.Type, Value: values, }) } extensionsAppended := false if len(extensions) > 0 { // Append the extensions to an existing attribute if possible. for _, atvSet := range attributes { if !atvSet.Type.Equal(oidExtensionRequest) || len(atvSet.Value) == 0 { continue } // specifiedExtensions contains all the extensions that we // found specified via template.Attributes. specifiedExtensions := make(map[string]bool) for _, atvs := range atvSet.Value { for _, atv := range atvs { specifiedExtensions[atv.Type.String()] = true } } newValue := make([]pkix.AttributeTypeAndValue, 0, len(atvSet.Value[0])+len(extensions)) newValue = append(newValue, atvSet.Value[0]...) for _, e := range extensions { if specifiedExtensions[e.Id.String()] { // Attributes already contained a value for // this extension and it takes priority. continue } newValue = append(newValue, pkix.AttributeTypeAndValue{ // There is no place for the critical // flag in an AttributeTypeAndValue. Type: e.Id, Value: e.Value, }) } atvSet.Value[0] = newValue extensionsAppended = true break } } rawAttributes, err := newRawAttributes(attributes) if err != nil { return } // If not included in attributes, add a new attribute for the // extensions. if len(extensions) > 0 && !extensionsAppended { attr := struct { Type asn1.ObjectIdentifier Value [][]pkix.Extension `asn1:"set"` }{ Type: oidExtensionRequest, Value: [][]pkix.Extension{extensions}, } b, err := asn1.Marshal(attr) if err != nil { return nil, errors.New("x509: failed to serialise extensions attribute: " + err.Error()) } var rawValue asn1.RawValue if _, err := asn1.Unmarshal(b, &rawValue); err != nil { return nil, err } rawAttributes = append(rawAttributes, rawValue) } asn1Subject := template.RawSubject if len(asn1Subject) == 0 { asn1Subject, err = asn1.Marshal(template.Subject.ToRDNSequence()) if err != nil { return nil, err } } tbsCSR := tbsCertificateRequest{ Version: 0, // PKCS #10, RFC 2986 Subject: asn1.RawValue{FullBytes: asn1Subject}, PublicKey: publicKeyInfo{ Algorithm: publicKeyAlgorithm, PublicKey: asn1.BitString{ Bytes: publicKeyBytes, BitLength: len(publicKeyBytes) * 8, }, }, RawAttributes: rawAttributes, } tbsCSRContents, err := asn1.Marshal(tbsCSR) if err != nil { return } tbsCSR.Raw = tbsCSRContents signed := tbsCSRContents if hashFunc != 0 { h := hashFunc.New() h.Write(signed) signed = h.Sum(nil) } var signature []byte signature, err = key.Sign(rand, signed, hashFunc) if err != nil { return } return asn1.Marshal(certificateRequest{ TBSCSR: tbsCSR, SignatureAlgorithm: sigAlgo, SignatureValue: asn1.BitString{ Bytes: signature, BitLength: len(signature) * 8, }, }) } // ParseCertificateRequest parses a single certificate request from the // given ASN.1 DER data. func ParseCertificateRequest(asn1Data []byte) (*CertificateRequest, error) { var csr certificateRequest rest, err := asn1.Unmarshal(asn1Data, &csr) if err != nil { return nil, err } else if len(rest) != 0 { return nil, asn1.SyntaxError{Msg: "trailing data"} } return parseCertificateRequest(&csr) } func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error) { out := &CertificateRequest{ Raw: in.Raw, RawTBSCertificateRequest: in.TBSCSR.Raw, RawSubjectPublicKeyInfo: in.TBSCSR.PublicKey.Raw, RawSubject: in.TBSCSR.Subject.FullBytes, Signature: in.SignatureValue.RightAlign(), SignatureAlgorithm: SignatureAlgorithmFromAI(in.SignatureAlgorithm), PublicKeyAlgorithm: getPublicKeyAlgorithmFromOID(in.TBSCSR.PublicKey.Algorithm.Algorithm), Version: in.TBSCSR.Version, Attributes: parseRawAttributes(in.TBSCSR.RawAttributes), } var err error var nfe NonFatalErrors out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, &in.TBSCSR.PublicKey, &nfe) if err != nil { return nil, err } // Treat non-fatal errors as fatal here. if len(nfe.Errors) > 0 { return nil, nfe.Errors[0] } var subject pkix.RDNSequence if rest, err := asn1.Unmarshal(in.TBSCSR.Subject.FullBytes, &subject); err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after X.509 Subject") } out.Subject.FillFromRDNSequence(&subject) if out.Extensions, err = parseCSRExtensions(in.TBSCSR.RawAttributes); err != nil { return nil, err } for _, extension := range out.Extensions { if extension.Id.Equal(OIDExtensionSubjectAltName) { out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(extension.Value, &nfe) if err != nil { return nil, err } } } return out, nil } // CheckSignature reports whether the signature on c is valid. func (c *CertificateRequest) CheckSignature() error { return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey) } google-certificate-transparency-go-2308f62/x509/x509_test.go000066400000000000000000003610731462611535200234530ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package x509 import ( "bytes" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" _ "crypto/sha256" _ "crypto/sha512" "encoding/base64" "encoding/hex" "encoding/pem" "errors" "fmt" "math/big" "net" "net/url" "os" "os/exec" "reflect" "runtime" "strings" "testing" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" "golang.org/x/crypto/ed25519" ) func TestParsePKCS1PrivateKey(t *testing.T) { block, _ := pem.Decode([]byte(pemPrivateKey)) priv, err := ParsePKCS1PrivateKey(block.Bytes) if err != nil { t.Errorf("Failed to parse private key: %s", err) return } if priv.PublicKey.N.Cmp(rsaPrivateKey.PublicKey.N) != 0 || priv.PublicKey.E != rsaPrivateKey.PublicKey.E || priv.D.Cmp(rsaPrivateKey.D) != 0 || priv.Primes[0].Cmp(rsaPrivateKey.Primes[0]) != 0 || priv.Primes[1].Cmp(rsaPrivateKey.Primes[1]) != 0 { t.Errorf("got:%+v want:%+v", priv, rsaPrivateKey) } // This private key includes an invalid prime that // rsa.PrivateKey.Validate should reject. data := []byte("0\x16\x02\x00\x02\x02\u007f\x00\x02\x0200\x02\x0200\x02\x02\x00\x01\x02\x02\u007f\x00") if _, err := ParsePKCS1PrivateKey(data); err == nil { t.Errorf("parsing invalid private key did not result in an error") } } func testParsePKIXPublicKey(t *testing.T, pemBytes string) (pub interface{}) { block, _ := pem.Decode([]byte(pemBytes)) pub, err := ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatalf("Failed to parse public key: %s", err) } pubBytes2, err := MarshalPKIXPublicKey(pub) if err != nil { t.Errorf("Failed to marshal public key for the second time: %s", err) return } if !bytes.Equal(pubBytes2, block.Bytes) { t.Errorf("Reserialization of public key didn't match. got %x, want %x", pubBytes2, block.Bytes) } return } func TestParsePKIXPublicKey(t *testing.T) { t.Run("RSA", func(t *testing.T) { pub := testParsePKIXPublicKey(t, pemPublicKey) _, ok := pub.(*rsa.PublicKey) if !ok { t.Errorf("Value returned from ParsePKIXPublicKey was not an RSA public key") } }) t.Run("Ed25519", func(t *testing.T) { pub := testParsePKIXPublicKey(t, pemEd25519Key) _, ok := pub.(ed25519.PublicKey) if !ok { t.Errorf("Value returned from ParsePKIXPublicKey was not an Ed25519 public key") } }) } func TestParsePKIXPublicKeyEd25519(t *testing.T) { block, _ := pem.Decode([]byte(pemPublicKeyEd25519)) pub, err := ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatalf("ParsePKIXPublicKey(Ed25519)=nil,%v; want _,nil", err) } edPub, ok := pub.(ed25519.PublicKey) if !ok { t.Fatalf("ParsePKIXPublicKey.(ed25519.PublicKey)=nil,%v; want _,true", ok) } pubBytes2, err := MarshalPKIXPublicKey(edPub) if err != nil { t.Fatalf("MarshalPKIXPublicKey(Ed25519)=nil,%v; want _,nil", err) } if !bytes.Equal(pubBytes2, block.Bytes) { t.Errorf("MarshalPKIXPublicKey(Ed25519)=%x; want %x", pubBytes2, block.Bytes) } } // From RFC 8410 section 10.1. var pemPublicKeyEd25519 = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -----END PUBLIC KEY-----` var pemPublicKey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3VoPN9PKUjKFLMwOge6+ wnDi8sbETGIx2FKXGgqtAKpzmem53kRGEQg8WeqRmp12wgp74TGpkEXsGae7RS1k enJCnma4fii+noGH7R0qKgHvPrI2Bwa9hzsH8tHxpyM3qrXslOmD45EH9SxIDUBJ FehNdaPbLP1gFyahKMsdfxFJLUvbUycuZSJ2ZnIgeVxwm4qbSvZInL9Iu4FzuPtg fINKcbbovy1qq4KvPIrXzhbY3PWDc6btxCf3SE0JdE1MCPThntB62/bLMSQ7xdDR FF53oIpvxe/SCOymfWq/LW849Ytv3Xwod0+wzAP8STXG4HSELS4UedPYeHJJJYcZ +QIDAQAB -----END PUBLIC KEY----- ` var pemPrivateKey = testingKey(` -----BEGIN RSA TESTING KEY----- MIICXAIBAAKBgQCxoeCUW5KJxNPxMp+KmCxKLc1Zv9Ny+4CFqcUXVUYH69L3mQ7v IWrJ9GBfcaA7BPQqUlWxWM+OCEQZH1EZNIuqRMNQVuIGCbz5UQ8w6tS0gcgdeGX7 J7jgCQ4RK3F/PuCM38QBLaHx988qG8NMc6VKErBjctCXFHQt14lerd5KpQIDAQAB AoGAYrf6Hbk+mT5AI33k2Jt1kcweodBP7UkExkPxeuQzRVe0KVJw0EkcFhywKpr1 V5eLMrILWcJnpyHE5slWwtFHBG6a5fLaNtsBBtcAIfqTQ0Vfj5c6SzVaJv0Z5rOd 7gQF6isy3t3w9IF3We9wXQKzT6q5ypPGdm6fciKQ8RnzREkCQQDZwppKATqQ41/R vhSj90fFifrGE6aVKC1hgSpxGQa4oIdsYYHwMzyhBmWW9Xv/R+fPyr8ZwPxp2c12 33QwOLPLAkEA0NNUb+z4ebVVHyvSwF5jhfJxigim+s49KuzJ1+A2RaSApGyBZiwS rWvWkB471POAKUYt5ykIWVZ83zcceQiNTwJBAMJUFQZX5GDqWFc/zwGoKkeR49Yi MTXIvf7Wmv6E++eFcnT461FlGAUHRV+bQQXGsItR/opIG7mGogIkVXa3E1MCQARX AAA7eoZ9AEHflUeuLn9QJI/r0hyQQLEtrpwv6rDT1GCWaLII5HJ6NUFVf4TTcqxo 6vdM4QGKTJoO+SaCyP0CQFdpcxSAuzpFcKv0IlJ8XzS/cy+mweCMwyJ1PFEc4FX6 wg/HcAJWY60xZTJDFN+Qfx8ZQvBEin6c2/h+zZi5IVY= -----END RSA TESTING KEY----- `) // pemEd25519Key is the example from RFC 8410, Secrion 4. var pemEd25519Key = ` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -----END PUBLIC KEY----- ` var testPrivateKey *rsa.PrivateKey func init() { block, _ := pem.Decode([]byte(pemPrivateKey)) var err error if testPrivateKey, err = ParsePKCS1PrivateKey(block.Bytes); err != nil { panic("Failed to parse private key: " + err.Error()) } } func bigFromString(s string) *big.Int { ret := new(big.Int) ret.SetString(s, 10) return ret } func fromBase10(base10 string) *big.Int { i := new(big.Int) i.SetString(base10, 10) return i } func bigFromHexString(s string) *big.Int { ret := new(big.Int) ret.SetString(s, 16) return ret } var rsaPrivateKey = &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: bigFromString("124737666279038955318614287965056875799409043964547386061640914307192830334599556034328900586693254156136128122194531292927142396093148164407300419162827624945636708870992355233833321488652786796134504707628792159725681555822420087112284637501705261187690946267527866880072856272532711620639179596808018872997"), E: 65537, }, D: bigFromString("69322600686866301945688231018559005300304807960033948687567105312977055197015197977971637657636780793670599180105424702854759606794705928621125408040473426339714144598640466128488132656829419518221592374964225347786430566310906679585739468938549035854760501049443920822523780156843263434219450229353270690889"), Primes: []*big.Int{ bigFromString("11405025354575369741595561190164746858706645478381139288033759331174478411254205003127028642766986913445391069745480057674348716675323735886284176682955723"), bigFromString("10937079261204603443118731009201819560867324167189758120988909645641782263430128449826989846631183550578761324239709121189827307416350485191350050332642639"), }, } func TestMarshalRSAPrivateKey(t *testing.T) { priv := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: fromBase10("16346378922382193400538269749936049106320265317511766357599732575277382844051791096569333808598921852351577762718529818072849191122419410612033592401403764925096136759934497687765453905884149505175426053037420486697072448609022753683683718057795566811401938833367954642951433473337066311978821180526439641496973296037000052546108507805269279414789035461158073156772151892452251106173507240488993608650881929629163465099476849643165682709047462010581308719577053905787496296934240246311806555924593059995202856826239801816771116902778517096212527979497399966526283516447337775509777558018145573127308919204297111496233"), E: 3, }, D: fromBase10("10897585948254795600358846499957366070880176878341177571733155050184921896034527397712889205732614568234385175145686545381899460748279607074689061600935843283397424506622998458510302603922766336783617368686090042765718290914099334449154829375179958369993407724946186243249568928237086215759259909861748642124071874879861299389874230489928271621259294894142840428407196932444474088857746123104978617098858619445675532587787023228852383149557470077802718705420275739737958953794088728369933811184572620857678792001136676902250566845618813972833750098806496641114644760255910789397593428910198080271317419213080834885003"), Primes: []*big.Int{ fromBase10("1025363189502892836833747188838978207017355117492483312747347695538428729137306368764177201532277413433182799108299960196606011786562992097313508180436744488171474690412562218914213688661311117337381958560443"), fromBase10("3467903426626310123395340254094941045497208049900750380025518552334536945536837294961497712862519984786362199788654739924501424784631315081391467293694361474867825728031147665777546570788493758372218019373"), fromBase10("4597024781409332673052708605078359346966325141767460991205742124888960305710298765592730135879076084498363772408626791576005136245060321874472727132746643162385746062759369754202494417496879741537284589047"), }, } derBytes := MarshalPKCS1PrivateKey(priv) priv2, err := ParsePKCS1PrivateKey(derBytes) if err != nil { t.Errorf("error parsing serialized key: %s", err) return } if priv.PublicKey.N.Cmp(priv2.PublicKey.N) != 0 || priv.PublicKey.E != priv2.PublicKey.E || priv.D.Cmp(priv2.D) != 0 || len(priv2.Primes) != 3 || priv.Primes[0].Cmp(priv2.Primes[0]) != 0 || priv.Primes[1].Cmp(priv2.Primes[1]) != 0 || priv.Primes[2].Cmp(priv2.Primes[2]) != 0 { t.Errorf("got:%+v want:%+v", priv, priv2) } } func TestMarshalRSAPublicKey(t *testing.T) { pub := &rsa.PublicKey{ N: fromBase10("16346378922382193400538269749936049106320265317511766357599732575277382844051791096569333808598921852351577762718529818072849191122419410612033592401403764925096136759934497687765453905884149505175426053037420486697072448609022753683683718057795566811401938833367954642951433473337066311978821180526439641496973296037000052546108507805269279414789035461158073156772151892452251106173507240488993608650881929629163465099476849643165682709047462010581308719577053905787496296934240246311806555924593059995202856826239801816771116902778517096212527979497399966526283516447337775509777558018145573127308919204297111496233"), E: 3, } derBytes := MarshalPKCS1PublicKey(pub) pub2, err := ParsePKCS1PublicKey(derBytes) if err != nil { t.Errorf("ParsePKCS1PublicKey: %s", err) } if pub.N.Cmp(pub2.N) != 0 || pub.E != pub2.E { t.Errorf("ParsePKCS1PublicKey = %+v, want %+v", pub, pub2) } // It's never been documented that asn1.Marshal/Unmarshal on rsa.PublicKey works, // but it does, and we know of code that depends on it. // Lock that in, even though we'd prefer that people use MarshalPKCS1PublicKey and ParsePKCS1PublicKey. derBytes2, err := asn1.Marshal(*pub) if err != nil { t.Errorf("Marshal(rsa.PublicKey): %v", err) } else if !bytes.Equal(derBytes, derBytes2) { t.Errorf("Marshal(rsa.PublicKey) = %x, want %x", derBytes2, derBytes) } pub3 := new(rsa.PublicKey) rest, err := asn1.Unmarshal(derBytes, pub3) if err != nil { t.Errorf("Unmarshal(rsa.PublicKey): %v", err) } if len(rest) != 0 || pub.N.Cmp(pub3.N) != 0 || pub.E != pub3.E { t.Errorf("Unmarshal(rsa.PublicKey) = %+v, %q want %+v, %q", pub, rest, pub2, []byte(nil)) } publicKeys := []struct { derBytes []byte expectedErrSubstr string }{ { derBytes: []byte{ 0x30, 6, // SEQUENCE, 6 bytes 0x02, 1, // INTEGER, 1 byte 17, 0x02, 1, // INTEGER, 1 byte 3, // 3 }, }, { derBytes: []byte{ 0x30, 6, // SEQUENCE 0x02, 1, // INTEGER, 1 byte 0xff, // -1 0x02, 1, // INTEGER, 1 byte 3, }, expectedErrSubstr: "zero or negative", }, { derBytes: []byte{ 0x30, 6, // SEQUENCE 0x02, 1, // INTEGER, 1 byte 17, 0x02, 1, // INTEGER, 1 byte 0xff, // -1 }, expectedErrSubstr: "zero or negative", }, { derBytes: []byte{ 0x30, 6, // SEQUENCE 0x02, 1, // INTEGER, 1 byte 17, 0x02, 1, // INTEGER, 1 byte 3, 1, }, expectedErrSubstr: "trailing data", }, { derBytes: []byte{ 0x30, 9, // SEQUENCE 0x02, 1, // INTEGER, 1 byte 17, 0x02, 4, // INTEGER, 4 bytes 0x7f, 0xff, 0xff, 0xff, }, }, { derBytes: []byte{ 0x30, 10, // SEQUENCE 0x02, 1, // INTEGER, 1 byte 17, 0x02, 5, // INTEGER, 5 bytes 0x00, 0x80, 0x00, 0x00, 0x00, }, // On 64-bit systems, encoding/asn1 will accept the // public exponent, but ParsePKCS1PublicKey will return // an error. On 32-bit systems, encoding/asn1 will // return the error. The common substring of both error // is the word “large”. expectedErrSubstr: "large", }, } for i, test := range publicKeys { shouldFail := len(test.expectedErrSubstr) > 0 pub, err := ParsePKCS1PublicKey(test.derBytes) if shouldFail { if err == nil { t.Errorf("#%d: unexpected success, got %#v", i, pub) } else if !strings.Contains(err.Error(), test.expectedErrSubstr) { t.Errorf("#%d: expected error containing %q, got %s", i, test.expectedErrSubstr, err) } } else { if err != nil { t.Errorf("#%d: unexpected failure: %s", i, err) continue } reserialized := MarshalPKCS1PublicKey(pub) if !bytes.Equal(reserialized, test.derBytes) { t.Errorf("#%d: failed to reserialize: got %x, expected %x", i, reserialized, test.derBytes) } } } } type matchHostnamesTest struct { pattern, host string ok bool } var matchHostnamesTests = []matchHostnamesTest{ {"a.b.c", "a.b.c", true}, {"a.b.c", "b.b.c", false}, {"", "b.b.c", false}, {"a.b.c", "", false}, {"example.com", "example.com", true}, {"example.com", "www.example.com", false}, {"*.example.com", "example.com", false}, {"*.example.com", "www.example.com", true}, {"*.example.com", "www.example.com.", true}, {"*.example.com", "xyz.www.example.com", false}, {"*.*.example.com", "xyz.www.example.com", false}, {"*.www.*.com", "xyz.www.example.com", false}, {"*bar.example.com", "foobar.example.com", false}, {"f*.example.com", "foobar.example.com", false}, {"", ".", false}, {".", "", false}, {".", ".", false}, {"example.com", "example.com.", true}, {"example.com.", "example.com", true}, {"example.com.", "example.com.", true}, {"*.com.", "example.com.", true}, {"*.com.", "example.com", true}, {"*.com", "example.com", true}, {"*.com", "example.com.", true}, } func TestMatchHostnames(t *testing.T) { for i, test := range matchHostnamesTests { r := matchHostnames(test.pattern, test.host) if r != test.ok { t.Errorf("#%d mismatch got: %t want: %t when matching '%s' against '%s'", i, r, test.ok, test.host, test.pattern) } } } func TestMatchIP(t *testing.T) { // Check that pattern matching is working. c := &Certificate{ DNSNames: []string{"*.foo.bar.baz"}, Subject: pkix.Name{ CommonName: "*.foo.bar.baz", }, } err := c.VerifyHostname("quux.foo.bar.baz") if err != nil { t.Fatalf("VerifyHostname(quux.foo.bar.baz): %v", err) } // But check that if we change it to be matching against an IP address, // it is rejected. c = &Certificate{ DNSNames: []string{"*.2.3.4"}, Subject: pkix.Name{ CommonName: "*.2.3.4", }, } err = c.VerifyHostname("1.2.3.4") if err == nil { t.Fatalf("VerifyHostname(1.2.3.4) should have failed, did not") } c = &Certificate{ IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, } err = c.VerifyHostname("127.0.0.1") if err != nil { t.Fatalf("VerifyHostname(127.0.0.1): %v", err) } err = c.VerifyHostname("::1") if err != nil { t.Fatalf("VerifyHostname(::1): %v", err) } err = c.VerifyHostname("[::1]") if err != nil { t.Fatalf("VerifyHostname([::1]): %v", err) } } func TestCertificateParse(t *testing.T) { s, _ := hex.DecodeString(certBytes) certs, err := ParseCertificates(s) if IsFatal(err) { t.Error(err) } if len(certs) != 2 { t.Errorf("Wrong number of certs: got %d want 2", len(certs)) return } err = certs[0].CheckSignatureFrom(certs[1]) if err != nil { t.Error(err) } if err := certs[0].VerifyHostname("mail.google.com"); err != nil { t.Error(err) } const expectedExtensions = 4 if n := len(certs[0].Extensions); n != expectedExtensions { t.Errorf("want %d extensions, got %d", expectedExtensions, n) } } func TestCertificateEqualOnNil(t *testing.T) { cNonNil := new(Certificate) var cNil1, cNil2 *Certificate if !cNil1.Equal(cNil2) { t.Error("Nil certificates: cNil1 is not equal to cNil2") } if !cNil2.Equal(cNil1) { t.Error("Nil certificates: cNil2 is not equal to cNil1") } if cNil1.Equal(cNonNil) { t.Error("Unexpectedly cNil1 is equal to cNonNil") } if cNonNil.Equal(cNil1) { t.Error("Unexpectedly cNonNil is equal to cNil1") } } func TestMismatchedSignatureAlgorithm(t *testing.T) { der, _ := pem.Decode([]byte(rsaPSSSelfSignedPEM)) if der == nil { t.Fatal("Failed to find PEM block") } cert, err := ParseCertificate(der.Bytes) if err != nil { t.Fatal(err) } if err = cert.CheckSignature(ECDSAWithSHA256, nil, nil); err == nil { t.Fatal("CheckSignature unexpectedly return no error") } const expectedSubstring = " but have public key of type " if !strings.Contains(err.Error(), expectedSubstring) { t.Errorf("Expected error containing %q, but got %q", expectedSubstring, err) } } var certBytes = "308203223082028ba00302010202106edf0d9499fd4533dd1297fc42a93be1300d06092a864886" + "f70d0101050500304c310b3009060355040613025a4131253023060355040a131c546861777465" + "20436f6e73756c74696e67202850747929204c74642e311630140603550403130d546861777465" + "20534743204341301e170d3039303332353136343932395a170d3130303332353136343932395a" + "3069310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630" + "140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c6520" + "496e63311830160603550403130f6d61696c2e676f6f676c652e636f6d30819f300d06092a8648" + "86f70d010101050003818d0030818902818100c5d6f892fccaf5614b064149e80a2c9581a218ef" + "41ec35bd7a58125ae76f9ea54ddc893abbeb029f6b73616bf0ffd868791fba7af9c4aebf3706ba" + "3eeaeed27435b4ddcfb157c05f351d66aa87fee0de072d66d773affbd36ab78bef090e0cc861a9" + "03ac90dd98b51c9c41566c017f0beec3bff391051ffba0f5cc6850ad2a590203010001a381e730" + "81e430280603551d250421301f06082b0601050507030106082b06010505070302060960864801" + "86f842040130360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e74686177" + "74652e636f6d2f54686177746553474343412e63726c307206082b060105050701010466306430" + "2206082b060105050730018616687474703a2f2f6f6373702e7468617774652e636f6d303e0608" + "2b060105050730028632687474703a2f2f7777772e7468617774652e636f6d2f7265706f736974" + "6f72792f5468617774655f5347435f43412e637274300c0603551d130101ff04023000300d0609" + "2a864886f70d01010505000381810062f1f3050ebc105e497c7aedf87e24d2f4a986bb3b837bd1" + "9b91ebcad98b065992f6bd2b49b7d6d3cb2e427a99d606c7b1d46352527fac39e6a8b6726de5bf" + "70212a52cba07634a5e332011bd1868e78eb5e3c93cf03072276786f207494feaa0ed9d53b2110" + "a76571f90209cdae884385c882587030ee15f33d761e2e45a6bc308203233082028ca003020102" + "020430000002300d06092a864886f70d0101050500305f310b3009060355040613025553311730" + "15060355040a130e566572695369676e2c20496e632e31373035060355040b132e436c61737320" + "33205075626c6963205072696d6172792043657274696669636174696f6e20417574686f726974" + "79301e170d3034303531333030303030305a170d3134303531323233353935395a304c310b3009" + "060355040613025a4131253023060355040a131c54686177746520436f6e73756c74696e672028" + "50747929204c74642e311630140603550403130d5468617774652053474320434130819f300d06" + "092a864886f70d010101050003818d0030818902818100d4d367d08d157faecd31fe7d1d91a13f" + "0b713cacccc864fb63fc324b0794bd6f80ba2fe10493c033fc093323e90b742b71c403c6d2cde2" + "2ff50963cdff48a500bfe0e7f388b72d32de9836e60aad007bc4644a3b847503f270927d0e62f5" + "21ab693684317590f8bfc76c881b06957cc9e5a8de75a12c7a68dfd5ca1c875860190203010001" + "a381fe3081fb30120603551d130101ff040830060101ff020100300b0603551d0f040403020106" + "301106096086480186f842010104040302010630280603551d110421301fa41d301b3119301706" + "035504031310507269766174654c6162656c332d313530310603551d1f042a30283026a024a022" + "8620687474703a2f2f63726c2e766572697369676e2e636f6d2f706361332e63726c303206082b" + "0601050507010104263024302206082b060105050730018616687474703a2f2f6f6373702e7468" + "617774652e636f6d30340603551d25042d302b06082b0601050507030106082b06010505070302" + "06096086480186f8420401060a6086480186f845010801300d06092a864886f70d010105050003" + "81810055ac63eadea1ddd2905f9f0bce76be13518f93d9052bc81b774bad6950a1eededcfddb07" + "e9e83994dcab72792f06bfab8170c4a8edea5334edef1e53d906c7562bd15cf4d18a8eb42bb137" + "9048084225c53e8acb7feb6f04d16dc574a2f7a27c7b603c77cd0ece48027f012fb69b37e02a2a" + "36dcd585d6ace53f546f961e05af" var certWithSCTListPEM = ` -----BEGIN CERTIFICATE----- MIIHkzCCBnugAwIBAgIUHz6ZOwEjk6zhU9v3n2Jo3qeucqYwDQYJKoZIhvcNAQEL BQAwSTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHzAd BgNVBAMTFlF1b1ZhZGlzIEVWIFNTTCBJQ0EgRzEwHhcNMTcwMjA4MTQxNTU3WhcN MTgwMjA4MTQyNTAwWjCByzETMBEGCysGAQQBgjc8AgEDEwJHQjEYMBYGA1UEDwwP QnVzaW5lc3MgRW50aXR5MREwDwYDVQQFEwhTQzA5NTAwMDELMAkGA1UEBhMCR0Ix EjAQBgNVBAgMCUVkaW5idXJnaDESMBAGA1UEBwwJRWRpbmJ1cmdoMSEwHwYDVQQK DBhMbG95ZHMgQmFua2luZyBHcm91cCBQTEMxEjAQBgNVBAsMCUdST1VQIElUMjEb MBkGA1UEAwwSd3d3Lmxsb3lkc2JhbmsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAyRkzN3UmnYbuIW7V4P5qyF/3iCdJwaw/avb0tpOJTa/svtM2 9KxtVtgwqAPCSuWgjHh6lx+OzXBOh0cM1+gOvjscIJ7k6J0UKouhxrZ02G6CHiNS 0P3ztsW1CVYAnEQqnhC+hBSl+Ut7DdcsReOUaUrIbO8T0psfsBnez6VtcLB74Hi0 y6s2AOwPKG7zRjMcMEylOYyGMrUI4ooGsf7IBzMOdMZpkAMUEe6KZ/8AssZOH7F9 OacCBcGHwN3qp/AG02+tXGaS9DWCS9/seMWqyhE8YPk+iGV3sFZEueBMxixVObFZ 0Ezwv3cCel6v2mlA5OweteDI57VG4/7OI45CawIDAQABo4ID7jCCA+owdwYIKwYB BQUHAQEEazBpMDgGCCsGAQUFBzAChixodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9i YWwuY29tL3F2ZXZzc2wxLmNydDAtBggrBgEFBQcwAYYhaHR0cDovL2V2Lm9jc3Au cXVvdmFkaXNnbG9iYWwuY29tMB0GA1UdDgQWBBSIASoK0Cmqs5B06CJUUzq5nQ6c IDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFVYhs66fHZOmROpD9Nsn8L10zzj MFEGA1UdIARKMEgwRgYMKwYBBAG+WAACZAECMDYwNAYIKwYBBQUHAgEWKGh0dHA6 Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL3JlcG9zaXRvcnkwOwYDVR0fBDQwMjAw oC6gLIYqaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5jb20vcXZldnNzbDEuY3Js MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw gd8GA1UdEQSB1zCB1IISd3d3Lmxsb3lkc2JhbmsuY29tghRzdGF0aWMuaGFsaWZh eC5jby51a4IUd3d3Lmxsb3lkc2JhbmsuY28udWuCFGltYWdlcy5oYWxpZmF4LmNv LnVrghh3d3cuYmFua29mc2NvdGxhbmQuY28udWuCD3d3dy5oYWxpZmF4LmNvbYIT d3d3Lmxsb3lkc3RzYi5jby51a4IRd3d3Lmxsb3lkc3RzYi5jb22CEXd3dy5oYWxp ZmF4LmNvLnVrghZ3d3cuYmFua29mc2NvdGxhbmQuY29tMIIBfgYKKwYBBAHWeQIE AgSCAW4EggFqAWgAdgC72d+8H4pxtZOUI5eqkntHOFeVCqtS6BqQlmQ2jh7RhQAA AVoeHdwIAAAEAwBHMEUCIQD3fh/6Cp3I44poKfhA7IkhjInvl8qC9JkooycB7NfE lgIgMihc8F0Ap1gIU7WUCxWgV0nUxeWp34mp+g0IPnNJ1KcAdgCkuQmQtBhYFIe7 E6LMZ3AKPDWYBPkb37jjd80OyA3cEAAAAVoeHdwaAAAEAwBHMEUCIA45u9pfeirN Hn8K0xHDCUfCihQSFJo0YYmFxYEgO8GEAiEAwDXdmIkkv3lJ1td+RjTzXMBIjp9R 3Ii1GumOGKe8IqcAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAA AVoeHdxAAAAEAwBHMEUCIEfe5grRiTvaHZJ4e4glbGftZ87n1pQf2lVeyBMUq0Xa AiEA/+W+qCcVd3hHVaYJryO67SYCbUBinU5/6je8eDLOEM4wDQYJKoZIhvcNAQEL BQADggEBADGdRf8XpQRPzxv+NeUr5i4q49JI8M/umbwSEXkHJmaDD4CDiUGkFUNR Tte0HDRvjbu5y9xub09MgdMl84qvfjsph54rUVK7LWVphi6CC21XbRP5gEwKBeA2 wAZU1IhhWBy6DaW4CjpTu1Ji3FJqHqKklFrZTMSS+xoqfYbbN96q1mJHQhyDdRlC JvNuS1lDM0u3+g/MHNpQM1aGZVKtwpzRABIYhydSJrO14TzWJjbKm1UTnKiwvHbt p1ATnYNDZKj3Ec8EASXGTHHoorX0jZpMiNxHv4p4BUinsNFttkhudMGbCfbjjqef UfUJESblkeQVpcnndMfnsHSahCVd96k= -----END CERTIFICATE----- ` // The cert above contains the following SCTs, each of which is a TLS-encoded // SignedCertificateTimestamp structure from RFC6962 s3.2. /* struct { Version sct_version; LogID id; uint64 timestamp; CtExtensions extensions; digitally-signed struct { Version sct_version; SignatureType signature_type = certificate_timestamp; uint64 timestamp; LogEntryType entry_type; select(entry_type) { case x509_entry: ASN.1Cert; case precert_entry: PreCert; } signed_entry; CtExtensions extensions; }; } SignedCertificateTimestamp; */ var wantSCTs = []string{ ("00" + // Version: v1(0) "bbd9dfbc1f8a71b593942397aa927b473857950aab52e81a909664368e1ed185" + // LogID: Google Skydiver "0000015a1e1ddc08" + // Timestamp "0000" + // No Extensions ("04" + "03" + // SHA-256, ECDSA ("0047" + "3045022100f77e1ffa0a9dc8e38a6829f840ec89218c89ef97ca82f49928a32701ecd7c496022032285cf05d00a7580853b5940b15a05749d4c5e5a9df89a9fa0d083e7349d4a7"))), ("00" + // Version: v1(0) "a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10" + // LogID: Google Pilot "0000015a1e1ddc1a" + "0000" + // No Extensions ("04" + "03" + // SHA-256, ECDSA ("0047" + "304502200e39bbda5f7a2acd1e7f0ad311c30947c28a1412149a34618985c581203bc184022100c035dd988924bf7949d6d77e4634f35cc0488e9f51dc88b51ae98e18a7bc22a7"))), ("00" + // Version: v1(0) "5614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd" + // LogID: Digicert Log Server "0000015a1e1ddc40" + // Timestamp "0000" + // No Extensions ("04" + "03" + // SHA-256, ECDSA ("0047" + "3045022047dee60ad1893bda1d92787b88256c67ed67cee7d6941fda555ec81314ab45da022100ffe5bea8271577784755a609af23baed26026d40629d4e7fea37bc7832ce10ce"))), } func TestParseCertificateSCTs(t *testing.T) { pemBlock, _ := pem.Decode([]byte(certWithSCTListPEM)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("ParseCertificate()=_,%v; want _, nil", err) } if len(cert.RawSCT) == 0 { t.Errorf("len(cert.RawSCT)=0, want >0") } for i, got := range cert.SCTList.SCTList { want, _ := hex.DecodeString(wantSCTs[i]) if !bytes.Equal(got.Val, want) { t.Errorf("SCT[%d]=%x; want %x", i, got.Val, want) } } } func parseCIDR(s string) *net.IPNet { _, net, err := net.ParseCIDR(s) if err != nil { panic(err) } return net } func parseURI(s string) *url.URL { uri, err := url.Parse(s) if err != nil { panic(err) } return uri } func TestCreateSelfSignedCertificate(t *testing.T) { random := rand.Reader ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("Failed to generate ECDSA key: %s", err) } ed25519Pub, ed25519Priv, err := ed25519.GenerateKey(random) if err != nil { t.Fatalf("Failed to generate Ed25519 key: %s", err) } tests := []struct { name string pub, priv interface{} checkSig bool sigAlgo SignatureAlgorithm }{ {"RSA/RSA", &testPrivateKey.PublicKey, testPrivateKey, true, SHA1WithRSA}, {"RSA/ECDSA", &testPrivateKey.PublicKey, ecdsaPriv, false, ECDSAWithSHA384}, {"ECDSA/RSA", &ecdsaPriv.PublicKey, testPrivateKey, false, SHA256WithRSA}, {"ECDSA/ECDSA", &ecdsaPriv.PublicKey, ecdsaPriv, true, ECDSAWithSHA1}, {"RSAPSS/RSAPSS", &testPrivateKey.PublicKey, testPrivateKey, true, SHA256WithRSAPSS}, {"ECDSA/RSAPSS", &ecdsaPriv.PublicKey, testPrivateKey, false, SHA256WithRSAPSS}, {"RSAPSS/ECDSA", &testPrivateKey.PublicKey, ecdsaPriv, false, ECDSAWithSHA384}, {"Ed25519", ed25519Pub, ed25519Priv, true, PureEd25519}, } testExtKeyUsage := []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageServerAuth} testUnknownExtKeyUsage := []asn1.ObjectIdentifier{[]int{1, 2, 3}, []int{2, 59, 1}} extraExtensionData := []byte("extra extension") for _, test := range tests { commonName := "test.example.com" template := Certificate{ // SerialNumber is negative to ensure that negative // values are parsed. This is due to the prevalence of // buggy code that produces certificates with negative // serial numbers. SerialNumber: big.NewInt(-1), Subject: pkix.Name{ CommonName: commonName, Organization: []string{"Σ Acme Co"}, Country: []string{"US"}, ExtraNames: []pkix.AttributeTypeAndValue{ { Type: []int{2, 5, 4, 42}, Value: "Gopher", }, // This should override the Country, above. { Type: []int{2, 5, 4, 6}, Value: "NL", }, }, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(100000, 0), SignatureAlgorithm: test.sigAlgo, SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: KeyUsageCertSign, ExtKeyUsage: testExtKeyUsage, UnknownExtKeyUsage: testUnknownExtKeyUsage, BasicConstraintsValid: true, IsCA: true, OCSPServer: []string{"http://ocsp.example.com"}, IssuingCertificateURL: []string{"http://crt.example.com/ca1.crt"}, DNSNames: []string{"test.example.com"}, EmailAddresses: []string{"gopher@golang.org"}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")}, URIs: []*url.URL{parseURI("https://foo.com/wibble#foo")}, PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}}, PermittedDNSDomains: []string{".example.com", "example.com"}, ExcludedDNSDomains: []string{"bar.example.com"}, PermittedIPRanges: []*net.IPNet{parseCIDR("192.168.1.1/16"), parseCIDR("1.2.3.4/8")}, ExcludedIPRanges: []*net.IPNet{parseCIDR("2001:db8::/48")}, PermittedEmailAddresses: []string{"foo@example.com"}, ExcludedEmailAddresses: []string{".example.com", "example.com"}, PermittedURIDomains: []string{".bar.com", "bar.com"}, ExcludedURIDomains: []string{".bar2.com", "bar2.com"}, CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"}, RawSCT: []byte{0xde, 0xad}, // Ignored because SCTList provided. SCTList: SignedCertificateTimestampList{ SCTList: []SerializedSCT{ {Val: []byte{0x01, 0x02, 0x03}}, {Val: []byte{0x04, 0x05, 0x06}}, }, }, ExtraExtensions: []pkix.Extension{ { Id: []int{1, 2, 3, 4}, Value: extraExtensionData, }, // This extension should override the SubjectKeyId, above. { Id: OIDExtensionSubjectKeyId, Critical: false, Value: []byte{0x04, 0x04, 4, 3, 2, 1}, }, }, } derBytes, err := CreateCertificate(random, &template, &template, test.pub, test.priv) if err != nil { t.Errorf("%s: failed to create certificate: %s", test.name, err) continue } cert, err := ParseCertificate(derBytes) if err != nil { t.Errorf("%s: failed to parse certificate: %s", test.name, err) continue } if len(cert.PolicyIdentifiers) != 1 || !cert.PolicyIdentifiers[0].Equal(template.PolicyIdentifiers[0]) { t.Errorf("%s: failed to parse policy identifiers: got:%#v want:%#v", test.name, cert.PolicyIdentifiers, template.PolicyIdentifiers) } if len(cert.PermittedDNSDomains) != 2 || cert.PermittedDNSDomains[0] != ".example.com" || cert.PermittedDNSDomains[1] != "example.com" { t.Errorf("%s: failed to parse name constraints: %#v", test.name, cert.PermittedDNSDomains) } if len(cert.ExcludedDNSDomains) != 1 || cert.ExcludedDNSDomains[0] != "bar.example.com" { t.Errorf("%s: failed to parse name constraint exclusions: %#v", test.name, cert.ExcludedDNSDomains) } if len(cert.PermittedIPRanges) != 2 || cert.PermittedIPRanges[0].String() != "192.168.0.0/16" || cert.PermittedIPRanges[1].String() != "1.0.0.0/8" { t.Errorf("%s: failed to parse IP constraints: %#v", test.name, cert.PermittedIPRanges) } if len(cert.ExcludedIPRanges) != 1 || cert.ExcludedIPRanges[0].String() != "2001:db8::/48" { t.Errorf("%s: failed to parse IP constraint exclusions: %#v", test.name, cert.ExcludedIPRanges) } if len(cert.PermittedEmailAddresses) != 1 || cert.PermittedEmailAddresses[0] != "foo@example.com" { t.Errorf("%s: failed to parse permitted email addreses: %#v", test.name, cert.PermittedEmailAddresses) } if len(cert.ExcludedEmailAddresses) != 2 || cert.ExcludedEmailAddresses[0] != ".example.com" || cert.ExcludedEmailAddresses[1] != "example.com" { t.Errorf("%s: failed to parse excluded email addreses: %#v", test.name, cert.ExcludedEmailAddresses) } if len(cert.PermittedURIDomains) != 2 || cert.PermittedURIDomains[0] != ".bar.com" || cert.PermittedURIDomains[1] != "bar.com" { t.Errorf("%s: failed to parse permitted URIs: %#v", test.name, cert.PermittedURIDomains) } if len(cert.ExcludedURIDomains) != 2 || cert.ExcludedURIDomains[0] != ".bar2.com" || cert.ExcludedURIDomains[1] != "bar2.com" { t.Errorf("%s: failed to parse excluded URIs: %#v", test.name, cert.ExcludedURIDomains) } if cert.Subject.CommonName != commonName { t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName) } if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "NL" { t.Errorf("%s: ExtraNames didn't override Country", test.name) } for _, ext := range cert.Extensions { if ext.Id.Equal(OIDExtensionSubjectAltName) { if ext.Critical { t.Fatal("SAN extension is marked critical") } } } found := false for _, atv := range cert.Subject.Names { if atv.Type.Equal([]int{2, 5, 4, 42}) { found = true break } } if !found { t.Errorf("%s: Names didn't contain oid 2.5.4.42 from ExtraNames", test.name) } if cert.Issuer.CommonName != commonName { t.Errorf("%s: issuer wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Issuer.CommonName, commonName) } if cert.SignatureAlgorithm != test.sigAlgo { t.Errorf("%s: SignatureAlgorithm wasn't copied from template. Got %v, want %v", test.name, cert.SignatureAlgorithm, test.sigAlgo) } if !reflect.DeepEqual(cert.ExtKeyUsage, testExtKeyUsage) { t.Errorf("%s: extkeyusage wasn't correctly copied from the template. Got %v, want %v", test.name, cert.ExtKeyUsage, testExtKeyUsage) } if !reflect.DeepEqual(cert.UnknownExtKeyUsage, testUnknownExtKeyUsage) { t.Errorf("%s: unknown extkeyusage wasn't correctly copied from the template. Got %v, want %v", test.name, cert.UnknownExtKeyUsage, testUnknownExtKeyUsage) } if !reflect.DeepEqual(cert.OCSPServer, template.OCSPServer) { t.Errorf("%s: OCSP servers differ from template. Got %v, want %v", test.name, cert.OCSPServer, template.OCSPServer) } if !reflect.DeepEqual(cert.IssuingCertificateURL, template.IssuingCertificateURL) { t.Errorf("%s: Issuing certificate URLs differ from template. Got %v, want %v", test.name, cert.IssuingCertificateURL, template.IssuingCertificateURL) } if !reflect.DeepEqual(cert.DNSNames, template.DNSNames) { t.Errorf("%s: SAN DNS names differ from template. Got %v, want %v", test.name, cert.DNSNames, template.DNSNames) } if !reflect.DeepEqual(cert.EmailAddresses, template.EmailAddresses) { t.Errorf("%s: SAN emails differ from template. Got %v, want %v", test.name, cert.EmailAddresses, template.EmailAddresses) } if len(cert.URIs) != 1 || cert.URIs[0].String() != "https://foo.com/wibble#foo" { t.Errorf("%s: URIs differ from template. Got %v, want %v", test.name, cert.URIs, template.URIs) } if !reflect.DeepEqual(cert.IPAddresses, template.IPAddresses) { t.Errorf("%s: SAN IPs differ from template. Got %v, want %v", test.name, cert.IPAddresses, template.IPAddresses) } if !reflect.DeepEqual(cert.CRLDistributionPoints, template.CRLDistributionPoints) { t.Errorf("%s: CRL distribution points differ from template. Got %v, want %v", test.name, cert.CRLDistributionPoints, template.CRLDistributionPoints) } if !reflect.DeepEqual(cert.SCTList, template.SCTList) { t.Errorf("%s: SCTList differs from template. Got %v, want %v", test.name, cert.SCTList, template.SCTList) } if !bytes.Equal(cert.SubjectKeyId, []byte{4, 3, 2, 1}) { t.Errorf("%s: ExtraExtensions didn't override SubjectKeyId", test.name) } if !bytes.Contains(derBytes, extraExtensionData) { t.Errorf("%s: didn't find extra extension in DER output", test.name) } if test.checkSig { err = cert.CheckSignatureFrom(cert) if err != nil { t.Errorf("%s: signature verification failed: %s", test.name, err) } } } } // Self-signed certificate using ECDSA with SHA1 & secp256r1 var ecdsaSHA1CertPem = ` -----BEGIN CERTIFICATE----- MIICDjCCAbUCCQDF6SfN0nsnrjAJBgcqhkjOPQQBMIGPMQswCQYDVQQGEwJVUzET MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMG A1UECgwMR29vZ2xlLCBJbmMuMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIwMjAyMDUw WhcNMjIwNTE4MjAyMDUwWjCBjzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTATBgNVBAoMDEdvb2dsZSwg SW5jLjEXMBUGA1UEAwwOd3d3Lmdvb2dsZS5jb20xIzAhBgkqhkiG9w0BCQEWFGdv bGFuZy1kZXZAZ21haWwuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/Wgn WQDo5+bz71T0327ERgd5SDDXFbXLpzIZDXTkjpe8QTEbsF+ezsQfrekrpDPC4Cd3 P9LY0tG+aI8IyVKdUjAJBgcqhkjOPQQBA0gAMEUCIGlsqMcRqWVIWTD6wXwe6Jk2 DKxL46r/FLgJYnzBEH99AiEA3fBouObsvV1R3oVkb4BQYnD4/4LeId6lAT43YvyV a/A= -----END CERTIFICATE----- ` // Self-signed certificate using ECDSA with SHA256 & secp256r1 var ecdsaSHA256p256CertPem = ` -----BEGIN CERTIFICATE----- MIICDzCCAbYCCQDlsuMWvgQzhTAKBggqhkjOPQQDAjCBjzELMAkGA1UEBhMCVVMx EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT BgNVBAoMDEdvb2dsZSwgSW5jLjEXMBUGA1UEAwwOd3d3Lmdvb2dsZS5jb20xIzAh BgkqhkiG9w0BCQEWFGdvbGFuZy1kZXZAZ21haWwuY29tMB4XDTEyMDUyMTAwMTkx NloXDTIyMDUxOTAwMTkxNlowgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYDVQQKDAxHb29nbGUs IEluYy4xFzAVBgNVBAMMDnd3dy5nb29nbGUuY29tMSMwIQYJKoZIhvcNAQkBFhRn b2xhbmctZGV2QGdtYWlsLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPMt 2ErhxAty5EJRu9yM+MTy+hUXm3pdW1ensAv382KoGExSXAFWP7pjJnNtHO+XSwVm YNtqjcAGFKpweoN//kQwCgYIKoZIzj0EAwIDRwAwRAIgIYSaUA/IB81gjbIw/hUV 70twxJr5EcgOo0hLp3Jm+EYCIFDO3NNcgmURbJ1kfoS3N/0O+irUtoPw38YoNkqJ h5wi -----END CERTIFICATE----- ` // Self-signed certificate using ECDSA with SHA256 & secp384r1 var ecdsaSHA256p384CertPem = ` -----BEGIN CERTIFICATE----- MIICSjCCAdECCQDje/no7mXkVzAKBggqhkjOPQQDAjCBjjELMAkGA1UEBhMCVVMx EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDAS BgNVBAoMC0dvb2dsZSwgSW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIxMDYxMDM0 WhcNMjIwNTE5MDYxMDM0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSwg SW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEGCSqGSIb3DQEJARYUZ29s YW5nLWRldkBnbWFpbC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARRuzRNIKRK jIktEmXanNmrTR/q/FaHXLhWRZ6nHWe26Fw7Rsrbk+VjGy4vfWtNn7xSFKrOu5ze qxKnmE0h5E480MNgrUiRkaGO2GMJJVmxx20aqkXOk59U8yGA4CghE6MwCgYIKoZI zj0EAwIDZwAwZAIwBZEN8gvmRmfeP/9C1PRLzODIY4JqWub2PLRT4mv9GU+yw3Gr PU9A3CHMdEcdw/MEAjBBO1lId8KOCh9UZunsSMfqXiVurpzmhWd6VYZ/32G+M+Mh 3yILeYQzllt/g0rKVRk= -----END CERTIFICATE----- ` // Self-signed certificate using ECDSA with SHA384 & secp521r1 var ecdsaSHA384p521CertPem = ` -----BEGIN CERTIFICATE----- MIICljCCAfcCCQDhp1AFD/ahKjAKBggqhkjOPQQDAzCBjjELMAkGA1UEBhMCVVMx EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDAS BgNVBAoMC0dvb2dsZSwgSW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIxMTUwNDI5 WhcNMjIwNTE5MTUwNDI5WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSwg SW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEGCSqGSIb3DQEJARYUZ29s YW5nLWRldkBnbWFpbC5jb20wgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABACqx9Rv IssRs1LWYcNN+WffwlHw4Tv3y8/LIAA9MF1ZScIonU9nRMxt4a2uGJVCPDw6JHpz PaYc0E9puLoE9AfKpwFr59Jkot7dBg55SKPEFkddoip/rvmN7NPAWjMBirOwjOkm 8FPthvPhGPqsu9AvgVuHu3PosWiHGNrhh379pva8MzAKBggqhkjOPQQDAwOBjAAw gYgCQgEHNmswkUdPpHqrVxp9PvLVl+xxPuHBkT+75z9JizyxtqykHQo9Uh6SWCYH BF9KLolo01wMt8DjoYP5Fb3j5MH7xwJCAbWZzTOp4l4DPkIvAh4LeC4VWbwPPyqh kBg71w/iEcSY3wUKgHGcJJrObZw7wys91I5kENljqw/Samdr3ka+jBJa -----END CERTIFICATE----- ` var ecdsaTests = []struct { sigAlgo SignatureAlgorithm pemCert string }{ {ECDSAWithSHA1, ecdsaSHA1CertPem}, {ECDSAWithSHA256, ecdsaSHA256p256CertPem}, {ECDSAWithSHA256, ecdsaSHA256p384CertPem}, {ECDSAWithSHA384, ecdsaSHA384p521CertPem}, } func TestECDSA(t *testing.T) { for i, test := range ecdsaTests { pemBlock, _ := pem.Decode([]byte(test.pemCert)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Errorf("%d: failed to parse certificate: %s", i, err) continue } if sa := cert.SignatureAlgorithm; sa != test.sigAlgo { t.Errorf("%d: signature algorithm is %v, want %v", i, sa, test.sigAlgo) } if parsedKey, ok := cert.PublicKey.(*ecdsa.PublicKey); !ok { t.Errorf("%d: wanted an ECDSA public key but found: %#v", i, parsedKey) } if pka := cert.PublicKeyAlgorithm; pka != ECDSA { t.Errorf("%d: public key algorithm is %v, want ECDSA", i, pka) } if err = cert.CheckSignatureFrom(cert); err != nil { t.Errorf("%d: certificate verification failed: %s", i, err) } } } // Self-signed certificate using DSA with SHA1 var dsaCertPem = `-----BEGIN CERTIFICATE----- MIIEDTCCA82gAwIBAgIJALHPghaoxeDhMAkGByqGSM44BAMweTELMAkGA1UEBhMC VVMxCzAJBgNVBAgTAk5DMQ8wDQYDVQQHEwZOZXd0b24xFDASBgNVBAoTC0dvb2ds ZSwgSW5jMRIwEAYDVQQDEwlKb24gQWxsaWUxIjAgBgkqhkiG9w0BCQEWE2pvbmFs bGllQGdvb2dsZS5jb20wHhcNMTEwNTE0MDMwMTQ1WhcNMTEwNjEzMDMwMTQ1WjB5 MQswCQYDVQQGEwJVUzELMAkGA1UECBMCTkMxDzANBgNVBAcTBk5ld3RvbjEUMBIG A1UEChMLR29vZ2xlLCBJbmMxEjAQBgNVBAMTCUpvbiBBbGxpZTEiMCAGCSqGSIb3 DQEJARYTam9uYWxsaWVAZ29vZ2xlLmNvbTCCAbcwggEsBgcqhkjOOAQBMIIBHwKB gQC8hLUnQ7FpFYu4WXTj6DKvXvz8QrJkNJCVMTpKAT7uBpobk32S5RrPKXocd4gN 8lyGB9ggS03EVlEwXvSmO0DH2MQtke2jl9j1HLydClMf4sbx5V6TV9IFw505U1iW jL7awRMgxge+FsudtJK254FjMFo03ZnOQ8ZJJ9E6AEDrlwIVAJpnBn9moyP11Ox5 Asc/5dnjb6dPAoGBAJFHd4KVv1iTVCvEG6gGiYop5DJh28hUQcN9kul+2A0yPUSC X93oN00P8Vh3eYgSaCWZsha7zDG53MrVJ0Zf6v/X/CoZNhLldeNOepivTRAzn+Rz kKUYy5l1sxYLHQKF0UGNCXfFKZT0PCmgU+PWhYNBBMn6/cIh44vp85ideo5CA4GE AAKBgFmifCafzeRaohYKXJgMGSEaggCVCRq5xdyDCat+wbOkjC4mfG01/um3G8u5 LxasjlWRKTR/tcAL7t0QuokVyQaYdVypZXNaMtx1db7YBuHjj3aP+8JOQRI9xz8c bp5NDJ5pISiFOv4p3GZfqZPcqckDt78AtkQrmnal2txhhjF6o4HeMIHbMB0GA1Ud DgQWBBQVyyr7hO11ZFFpWX50298Sa3V+rzCBqwYDVR0jBIGjMIGggBQVyyr7hO11 ZFFpWX50298Sa3V+r6F9pHsweTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5DMQ8w DQYDVQQHEwZOZXd0b24xFDASBgNVBAoTC0dvb2dsZSwgSW5jMRIwEAYDVQQDEwlK b24gQWxsaWUxIjAgBgkqhkiG9w0BCQEWE2pvbmFsbGllQGdvb2dsZS5jb22CCQCx z4IWqMXg4TAMBgNVHRMEBTADAQH/MAkGByqGSM44BAMDLwAwLAIUPtn/5j8Q1jJI 7ggOIsgrhgUdjGQCFCsmDq1H11q9+9Wp9IMeGrTSKHIM -----END CERTIFICATE----- ` func TestParseCertificateWithDsaPublicKey(t *testing.T) { expectedKey := &dsa.PublicKey{ Parameters: dsa.Parameters{ P: bigFromHexString("00BC84B52743B169158BB85974E3E832AF5EFCFC42B264349095313A4A013EEE069A1B937D92E51ACF297A1C77880DF25C8607D8204B4DC45651305EF4A63B40C7D8C42D91EDA397D8F51CBC9D0A531FE2C6F1E55E9357D205C39D395358968CBEDAC11320C607BE16CB9DB492B6E78163305A34DD99CE43C64927D13A0040EB97"), Q: bigFromHexString("009A67067F66A323F5D4EC7902C73FE5D9E36FA74F"), G: bigFromHexString("009147778295BF5893542BC41BA806898A29E43261DBC85441C37D92E97ED80D323D44825FDDE8374D0FF15877798812682599B216BBCC31B9DCCAD527465FEAFFD7FC2A193612E575E34E7A98AF4D10339FE47390A518CB9975B3160B1D0285D1418D0977C52994F43C29A053E3D685834104C9FAFDC221E38BE9F3989D7A8E42"), }, Y: bigFromHexString("59A27C269FCDE45AA2160A5C980C19211A820095091AB9C5DC8309AB7EC1B3A48C2E267C6D35FEE9B71BCBB92F16AC8E559129347FB5C00BEEDD10BA8915C90698755CA965735A32DC7575BED806E1E38F768FFBC24E41123DC73F1C6E9E4D0C9E692128853AFE29DC665FA993DCA9C903B7BF00B6442B9A76A5DADC6186317A"), } pemBlock, _ := pem.Decode([]byte(dsaCertPem)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("Failed to parse certificate: %s", err) } if cert.PublicKeyAlgorithm != DSA { t.Errorf("Parsed key algorithm was not DSA") } parsedKey, ok := cert.PublicKey.(*dsa.PublicKey) if !ok { t.Fatalf("Parsed key was not a DSA key: %s", err) } if expectedKey.Y.Cmp(parsedKey.Y) != 0 || expectedKey.P.Cmp(parsedKey.P) != 0 || expectedKey.Q.Cmp(parsedKey.Q) != 0 || expectedKey.G.Cmp(parsedKey.G) != 0 { t.Fatal("Parsed key differs from expected key") } } func TestParseCertificateWithDSASignatureAlgorithm(t *testing.T) { pemBlock, _ := pem.Decode([]byte(dsaCertPem)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("Failed to parse certificate: %s", err) } if cert.SignatureAlgorithm != DSAWithSHA1 { t.Errorf("Parsed signature algorithm was not DSAWithSHA1") } } func TestVerifyCertificateWithDSASignature(t *testing.T) { pemBlock, _ := pem.Decode([]byte(dsaCertPem)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("Failed to parse certificate: %s", err) } // test cert is self-signed if err = cert.CheckSignatureFrom(cert); err != nil { t.Fatalf("DSA Certificate verification failed: %s", err) } } const dsaCert1024WithSha256 = `-----BEGIN CERTIFICATE----- MIIDKzCCAumgAwIBAgIUOXWPK4gTRZVVY7OSXTU00QEWQU8wCwYJYIZIAWUDBAMC MEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMTkxMDAxMDYxODUyWhgPMzAxOTAy MDEwNjE4NTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggG4MIIBLAYHKoZIzjgE ATCCAR8CgYEAr79m/1ypU1aUbbLX1jikTyX7w2QYP+EkxNtXUiiTuxkC1KBqqxT3 0Aht2vxFR47ODEK4B79rHO+UevhaqDaAHSH7Z/9umS0h0aS32KLDLb+LI5AneCrn eW5YbVhfD03N7uR4kKUCKOnWj5hAk9xiE3y7oFR0bBXzqrrHJF9LMd0CFQCB6lSj HSW0rGmNxIZsBl72u7JFLQKBgQCOFd1PGEQmddn0cdFgby5QQfjrqmoD1zNlFZEt L0x1EbndFwelLlF1ChNh3NPNUkjwRbla07FDlONs1GMJq6w4vW11ns+pUvAZ2+RM EVFjugip8az2ncn3UujGTVdFxnSTLBsRlMP/tFDK3ky//8zn/5ha9SKKw4v1uv6M JuoIbwOBhQACgYEAoeKeR90nwrnoPi5MOUPBLQvuzB87slfr+3kL8vFCmgjA6MtB 7TxQKoBTOo5aVgWDp0lMIMxLd6btzBrm6r3VdRlh/cL8/PtbxkFwBa+Upe4o5NAh ISCe2/f2leT1PxtF8xxYjz/fszeUeHsJbVMilE2cuB2SYrR5tMExiqy+QpqjUzBR MB0GA1UdDgQWBBQDMIEL8Z3jc1d9wCxWtksUWc8RkjAfBgNVHSMEGDAWgBQDMIEL 8Z3jc1d9wCxWtksUWc8RkjAPBgNVHRMBAf8EBTADAQH/MAsGCWCGSAFlAwQDAgMv ADAsAhQFehZgI4OyKBGpfnXvyJ0Z/0a6nAIUTO265Ane87LfJuQr3FrqvuCI354= -----END CERTIFICATE----- ` func TestVerifyCertificateWithDSATooLongHash(t *testing.T) { pemBlock, _ := pem.Decode([]byte(dsaCert1024WithSha256)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("Failed to parse certificate: %s", err) } // test cert is self-signed if err = cert.CheckSignatureFrom(cert); err != nil { t.Fatalf("DSA Certificate self-signature verification failed: %s", err) } signed := []byte("A wild Gopher appears!\n") signature, _ := hex.DecodeString("302c0214417aca7ff458f5b566e43e7b82f994953da84be50214625901e249e33f4e4838f8b5966020c286dd610e") // This signature is using SHA256, but only has 1024 DSA key. The hash has to be truncated // in CheckSignature, otherwise it won't pass. if err = cert.CheckSignature(DSAWithSHA256, signed, signature); err != nil { t.Fatalf("DSA signature verification failed: %s", err) } } var rsaPSSSelfSignedPEM = `-----BEGIN CERTIFICATE----- MIIGHjCCA9KgAwIBAgIBdjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwbjELMAkGA1UEBhMC SlAxHDAaBgNVBAoME0phcGFuZXNlIEdvdmVybm1lbnQxKDAmBgNVBAsMH1RoZSBN aW5pc3RyeSBvZiBGb3JlaWduIEFmZmFpcnMxFzAVBgNVBAMMDmUtcGFzc3BvcnRD U0NBMB4XDTEzMDUxNDA1MDczMFoXDTI5MDUxNDA1MDczMFowbjELMAkGA1UEBhMC SlAxHDAaBgNVBAoME0phcGFuZXNlIEdvdmVybm1lbnQxKDAmBgNVBAsMH1RoZSBN aW5pc3RyeSBvZiBGb3JlaWduIEFmZmFpcnMxFzAVBgNVBAMMDmUtcGFzc3BvcnRD U0NBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx/E3WRVxcCDXhoST 8nVSLjW6hwM4Ni99AegWzcGtfGFo0zjFA1Cl5URqxauvYu3gQgQHBGA1CovWeGrl yVSRzOL1imcYsSgLOcnhVYB3Xcrof4ebv9+W+TwNdc9YzAwcj8rNd5nP6PKXIQ+W PCkEOXdyb80YEnxuT+NPjkVfFSPBS7QYZpvT2fwy4fZ0eh48253+7VleSmTO0mqj 7TlzaG56q150SLZbhpOd8jD8bM/wACnLCPR88wj4hCcDLEwoLyY85HJCTIQQMnoT UpqyzEeupPREIm6yi4d8C9YqIWFn2YTnRcWcmMaJLzq+kYwKoudfnoC6RW2vzZXn defQs68IZuK+uALu9G3JWGPgu0CQGj0JNDT8zkiDV++4eNrZczWKjr1YnAL+VbLK bApwL2u19l2WDpfUklimhWfraqHNIUKU6CjZOG31RzXcplIj0mtqs0E1r7r357Es yFoB28iNo4cz1lCulh0E4WJzWzLZcT4ZspHHRCFyvYnXoibXEV1nULq8ByKKG0FS 7nn4SseoV+8PvjHLPhmHGMvi4mxkbcXdV3wthHT1/HXdqY84A4xHWt1+sB/TpTek tDhFlEfcUygvTu58UtOnysomOVVeERmi7WSujfzKsGJAJYeetiA5R+zX7BxeyFVE qW0zh1Tkwh0S8LRe5diJh4+6FG0CAwEAAaNfMF0wHQYDVR0OBBYEFD+oahaikBTV Urk81Uz7kRS2sx0aMA4GA1UdDwEB/wQEAwIBBjAYBgNVHSAEETAPMA0GCyqDCIaP fgYFAQEBMBIGA1UdEwEB/wQIMAYBAf8CAQAwQQYJKoZIhvcNAQEKMDSgDzANBglg hkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgA4IC AQAaxWBQn5CZuNBfyzL57mn31ukHUFd61OMROSX3PT7oCv1Dy+C2AdRlxOcbN3/n li0yfXUUqiY3COlLAHKRlkr97mLtxEFoJ0R8nVN2IQdChNQM/XSCzSGyY8NVa1OR TTpEWLnexJ9kvIdbFXwUqdTnAkOI0m7Rg8j+E+lRRHg1xDAA1qKttrtUj3HRQWf3 kNTu628SiMvap6aIdncburaK56MP7gkR1Wr/ichOfjIA3Jgw2PapI31i0GqeMd66 U1+lC9FeyMAJpuSVp/SoiYzYo+79SFcVoM2yw3yAnIKg7q9GLYYqzncdykT6C06c 15gWFI6igmReAsD9ITSvYh0jLrLHfEYcPTOD3ZXJ4EwwHtWSoO3gq1EAtOYKu/Lv C8zfBsZcFdsHvsSiYeBU8Oioe42mguky3Ax9O7D805Ek6R68ra07MW/G4YxvV7IN 2BfSaYy8MX9IG0ZMIOcoc0FeF5xkFmJ7kdrlTaJzC0IE9PNxNaH5QnOAFB8vxHcO FioUxb6UKdHcPLR1VZtAdTdTMjSJxUqD/35Cdfqs7oDJXz8f6TXO2Tdy6G++YUs9 qsGZWxzFvvkXUkQSl0dQQ5jO/FtUJcAVXVVp20LxPemfatAHpW31WdJYeWSQWky2 +f9b5TXKXVyjlUL7uHxowWrT2AtTchDH22wTEtqLEF9Z3Q== -----END CERTIFICATE-----` // openssl req -newkey rsa:2048 -keyout test.key -sha256 -sigopt \ // rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -sigopt rsa_mgf1_md:sha256 \ // -x509 -days 3650 -nodes -subj '/C=US/ST=CA/L=SF/O=Test/CN=Test' -out \ // test.pem var rsaPSSSelfSignedOpenSSL110PEM = `-----BEGIN CERTIFICATE----- MIIDwDCCAnigAwIBAgIJAM9LAMHTE5xpMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZI AWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIDAgEgMEUxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDTALBgNVBAoMBFRlc3Qx DTALBgNVBAMMBFRlc3QwHhcNMTgwMjIyMjIxMzE4WhcNMjgwMjIwMjIxMzE4WjBF MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQK DARUZXN0MQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA4Zrsydod+GoTAJLLutWNF87qhhVPBsK1zB1Gj+NAAe4+VbrZ1E41H1wp qITx7DA8DRtJEf+NqrTAnAdZWBG/tAOA5LfXVax0ZSQtLnYLSeylLoMtDyY3eFAj TmuTOoyVy6raktowCnHCh01NsstqqTfrx6SbmzOmDmKTkq/I+7K0MCVsn41xRDVM +ShD0WGFGioEGoiWnFSWupxJDA3Q6jIDEygVwNKHwnhv/2NgG2kqZzrZSQA67en0 iKAXtoDNPpmyD5oS9YbEJ+2Nbm7oLeON30i6kZvXKIzJXx+UWViazHZqnsi5rQ8G RHF+iVFXsqd0MzDKmkKOT5FDhrsbKQIDAQABo1MwUTAdBgNVHQ4EFgQU9uFY/nlg gLH00NBnr/o7QvpN9ugwHwYDVR0jBBgwFoAU9uFY/nlggLH00NBnr/o7QvpN9ugw DwYDVR0TAQH/BAUwAwEB/zA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEa MBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiAwIBIAOCAQEAhJzpwxBNGKvzKWDe WLqv6RMrl/q4GcH3b7M9wjxe0yOm4F+Tb2zJ7re4h+D39YkJf8cX1NV9UQVu6z4s Fvo2kmlR0qZOXAg5augmCQ1xS0WHFoF6B52anNzHkZQbAIYJ3kGoFsUHzs7Sz7F/ 656FsRpHA9UzJQ3avPPMrA4Y4aoJ7ANJ6XIwTrdWrhULOVuvYRLCl4CdTVztVFX6 wxX8nS1ISYd8jXPUMgsBKVbWufvLoIymMJW8CZbpprVZel5zFn0bmPrON8IHS30w Gs+ITJjKEnZgXmAQ25SLKVzkZkBcGANs2GsdHNJ370Puisy0FIPD2NXR5uASAf7J +w9fjQ== -----END CERTIFICATE-----` func TestRSAPSSSelfSigned(t *testing.T) { for i, pemBlock := range []string{rsaPSSSelfSignedPEM, rsaPSSSelfSignedOpenSSL110PEM} { der, _ := pem.Decode([]byte(pemBlock)) if der == nil { t.Errorf("#%d: failed to find PEM block", i) continue } cert, err := ParseCertificate(der.Bytes) if err != nil { t.Errorf("#%d: failed to parse: %s", i, err) continue } if err = cert.CheckSignatureFrom(cert); err != nil { t.Errorf("#%d: signature check failed: %s", i, err) continue } } } const ed25519Certificate = ` Certificate: Data: Version: 3 (0x2) Serial Number: 0c:83:d8:21:2b:82:cb:23:98:23:63:e2:f7:97:8a:43:5b:f3:bd:92 Signature Algorithm: ED25519 Issuer: CN = Ed25519 test certificate Validity Not Before: May 6 17:27:16 2019 GMT Not After : Jun 5 17:27:16 2019 GMT Subject: CN = Ed25519 test certificate Subject Public Key Info: Public Key Algorithm: ED25519 ED25519 Public-Key: pub: 36:29:c5:6c:0d:4f:14:6c:81:d0:ff:75:d3:6a:70: 5f:69:cd:0f:4d:66:d5:da:98:7e:82:49:89:a3:8a: 3c:fa X509v3 extensions: X509v3 Subject Key Identifier: 09:3B:3A:9D:4A:29:D8:95:FF:68:BE:7B:43:54:72:E0:AD:A2:E3:AE X509v3 Authority Key Identifier: keyid:09:3B:3A:9D:4A:29:D8:95:FF:68:BE:7B:43:54:72:E0:AD:A2:E3:AE X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: ED25519 53:a5:58:1c:2c:3b:2a:9e:ac:9d:4e:a5:1d:5f:5d:6d:a6:b5: 08:de:12:82:f3:97:20:ae:fa:d8:98:f4:1a:83:32:6b:91:f5: 24:1d:c4:20:7f:2c:e2:4d:da:13:3b:6d:54:1a:d2:a8:28:dc: 60:b9:d4:f4:78:4b:3c:1c:91:00 -----BEGIN CERTIFICATE----- MIIBWzCCAQ2gAwIBAgIUDIPYISuCyyOYI2Pi95eKQ1vzvZIwBQYDK2VwMCMxITAf BgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0xOTA1MDYxNzI3MTZa Fw0xOTA2MDUxNzI3MTZaMCMxITAfBgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZp Y2F0ZTAqMAUGAytlcAMhADYpxWwNTxRsgdD/ddNqcF9pzQ9NZtXamH6CSYmjijz6 o1MwUTAdBgNVHQ4EFgQUCTs6nUop2JX/aL57Q1Ry4K2i464wHwYDVR0jBBgwFoAU CTs6nUop2JX/aL57Q1Ry4K2i464wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBT pVgcLDsqnqydTqUdX11tprUI3hKC85cgrvrYmPQagzJrkfUkHcQgfyziTdoTO21U GtKoKNxgudT0eEs8HJEA -----END CERTIFICATE-----` func TestEd25519SelfSigned(t *testing.T) { der, _ := pem.Decode([]byte(ed25519Certificate)) if der == nil { t.Fatalf("Failed to find PEM block") } cert, err := ParseCertificate(der.Bytes) if err != nil { t.Fatalf("Failed to parse: %s", err) } if cert.PublicKeyAlgorithm != Ed25519 { t.Fatalf("Parsed key algorithm was not Ed25519") } parsedKey, ok := cert.PublicKey.(ed25519.PublicKey) if !ok { t.Fatalf("Parsed key was not an Ed25519 key: %s", err) } if len(parsedKey) != ed25519.PublicKeySize { t.Fatalf("Invalid Ed25519 key") } if err = cert.CheckSignatureFrom(cert); err != nil { t.Fatalf("Signature check failed: %s", err) } } // Valid EKCert (from an Infineon TPM1.2) with RSAES-OAEP Public Key. // TPM1.2 uses RSA keys with OAEP padding (SHA1). // The hardware only supports SHA1 so manufacturers have since switched // to using rsaEncryption keys but millions of certificates still exist // that have this type of key. var oaepCertPEM = `-----BEGIN CERTIFICATE----- MIIFbjCCBFagAwIBAgIEZPW9pTANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJE RTEPMA0GA1UECBMGU2F4b255MSEwHwYDVQQKExhJbmZpbmVvbiBUZWNobm9sb2dp ZXMgQUcxDDAKBgNVBAsTA0FJTTEmMCQGA1UEAxMdSUZYIFRQTSBFSyBJbnRlcm1l ZGlhdGUgQ0EgMjAwHhcNMTUwOTIxMjEyOTI4WhcNMjUwOTIxMjEyOTI4WjAAMIIB NzAiBgkqhkiG9w0BAQcwFaITMBEGCSqGSIb3DQEBCQQEVENQQQOCAQ8AMIIBCgKC AQEAjnmDEFBjo/HC5318i09BHbnHbxE2bMuxF1cAH6UYBbu/aLbczZrSioLGqDHh WR8GGBwuMowmHPmhT/FwTcMmHMFtp1F2AUGWkzCnWz/Frhhax21japejOYOL0EKq fFmZ9j+UbGmHsOi+j1kI0IVj9ivG7pUQ42dSvTsue1UoG8kqjcyLqKMNaqp1gLZy 2DgCRJ00v+oENO3qTbw6myAfPeC/WrLKlqUlT052pYrcjTvDhb6U6T4AUuQGbyOK nBGV6qzaAqbceDkwYzQAVOPOmXVKh3DI78ykWtj8mZ96qmeoqDlgFB5fS4ktOvMz 8JttE+YJAODqi9O17qMPTyuImwIDAQABo4ICYjCCAl4wVQYDVR0RAQH/BEswSaRH MEUxFjAUBgVngQUCAQwLaWQ6NDk0NjU4MDAxFzAVBgVngQUCAgwMU0xCOTY2MHh4 MS4yMRIwEAYFZ4EFAgMMB2lkOjA0MjgwDAYDVR0TAQH/BAIwADCBvAYDVR0gAQH/ BIGxMIGuMIGrBgtghkgBhvhFAQcvATCBmzA5BggrBgEFBQcCARYtaHR0cDovL3d3 dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9pbmRleC5odG1sMF4GCCsGAQUFBwIC MFIeUABUAEMAUABBACAAVAByAHUAcwB0AGUAZAAgAFAAbABhAHQAZgBvAHIAbQAg AE0AbwBkAHUAbABlACAARQBuAGQAbwByAHMAZQBtAGUAbgB0MIGhBgNVHSMEgZkw gZaAFI/9R4gOI5o6OiDeE+3xAeiCqdIdoXukeTB3MQswCQYDVQQGEwJERTEPMA0G A1UECBMGU2F4b255MSEwHwYDVQQKExhJbmZpbmVvbiBUZWNobm9sb2dpZXMgQUcx DDAKBgNVBAsTA0FJTTEmMCQGA1UEAxMdSUZYIFRQTSBFSyBJbnRlcm1lZGlhdGUg Q0EgMjCCAQUwgZMGA1UdCQSBizCBiDA6BgNVBDQxMzALMAkGBSsOAwIaBQAwJDAi BgkqhkiG9w0BAQcwFaITMBEGCSqGSIb3DQEBCQQEVENQQTAWBgVngQUCEDENMAsM AzEuMgIBAgIBAzAyBgVngQUCEjEpMCcBAf+gAwoBAaEDCgEAogMKAQCjEDAOFgMz LjEKAQQKAQEBAf8BAf8wDQYJKoZIhvcNAQEFBQADggEBADsl05WM8IssMs77QFcP hF4l+pj9OKR76MFuTvZj2PBXgk/1UAUrSjOONTsIv+cDZM2geWHT9Ptcv1SElzia WrWcNGnRS29b/cJ9s90MvSGsgYpkoUAyzUM6K/+5ObX1RVXCtokrX2R548OL1LMC g9Lo7lfsrXedrpC9nWBSrWvz77sw9jngOwojn1OaR2sSlUC8mAbi3HAR0mXcqKlT Aaq6hytOF2vbZw5lt1Cwr9wsqXs/C+9VhirhNb2GsdOijU7t/l7kRaPx1lu60K2u SZmeYT6rsTMWZGHPRxRt4Hin1/VQlAoaR17Mg07nhKam5C+krXhp740rIlHIMO+z hJY= -----END CERTIFICATE-----` func TestParseCertificateWithRSAESOAEPPublicKey(t *testing.T) { wantKey := &rsa.PublicKey{ E: 65537, N: bigFromHexString("8e7983105063a3f1c2e77d7c8b4f411db9c76f11366ccbb11757001fa51805bbbf68b6dccd9ad28a82c6a831e1591f06181c2e328c261cf9a14ff1704dc3261cc16da751760141969330a75b3fc5ae185ac76d636a97a339838bd042aa7c5999f63f946c6987b0e8be8f5908d08563f62bc6ee9510e36752bd3b2e7b55281bc92a8dcc8ba8a30d6aaa7580b672d83802449d34bfea0434edea4dbc3a9b201f3de0bf5ab2ca96a5254f4e76a58adc8d3bc385be94e93e0052e4066f238a9c1195eaacda02a6dc78393063340054e3ce99754a8770c8efcca45ad8fc999f7aaa67a8a83960141e5f4b892d3af333f09b6d13e60900e0ea8bd3b5eea30f4f2b889b"), } der, _ := pem.Decode([]byte(oaepCertPEM)) if der == nil { t.Fatalf("Failed to decode PEM cert") } cert, err := ParseCertificate(der.Bytes) if err != nil { t.Fatalf("Failed to parse certificate: %s", err) } if cert.PublicKeyAlgorithm != RSAESOAEP { t.Errorf("Parsed key algorithm was not RSAESOAEP") } parsedKey, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { t.Fatalf("Parsed key was not an RSA key: %s", err) } if wantKey.E != parsedKey.E || wantKey.N.Cmp(parsedKey.N) != 0 { t.Fatal("Parsed key differs from expected key") } } const ( pemCertificate = `-----BEGIN CERTIFICATE----- MIIDATCCAemgAwIBAgIRAKQkkrFx1T/dgB/Go/xBM5swDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA4MTcyMDM2MDdaFw0xNzA4MTcyMDM2 MDdaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDAoJtjG7M6InsWwIo+l3qq9u+g2rKFXNu9/mZ24XQ8XhV6PUR+5HQ4 jUFWC58ExYhottqK5zQtKGkw5NuhjowFUgWB/VlNGAUBHtJcWR/062wYrHBYRxJH qVXOpYKbIWwFKoXu3hcpg/CkdOlDWGKoZKBCwQwUBhWE7MDhpVdQ+ZljUJWL+FlK yQK5iRsJd5TGJ6VUzLzdT4fmN2DzeK6GLeyMpVpU3sWV90JJbxWQ4YrzkKzYhMmB EcpXTG2wm+ujiHU/k2p8zlf8Sm7VBM/scmnMFt0ynNXop4FWvJzEm1G0xD2t+e2I 5Utr04dOZPCgkm++QJgYhtZvgW7ZZiGTAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIF oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBsGA1UdEQQUMBKC EHRlc3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBADpqKQxrthH5InC7 X96UP0OJCu/lLEMkrjoEWYIQaFl7uLPxKH5AmQPH4lYwF7u7gksR7owVG9QU9fs6 1fK7II9CVgCd/4tZ0zm98FmU4D0lHGtPARrrzoZaqVZcAvRnFTlPX5pFkPhVjjai /mkxX9LpD8oK1445DFHxK5UjLMmPIIWd8EOi+v5a+hgGwnJpoW7hntSl8kHMtTmy fnnktsblSUV4lRCit0ymC7Ojhe+gzCCwkgs5kDzVVag+tnl/0e2DloIjASwOhpbH KVcg7fBd484ht/sS+l0dsB4KDOSpd8JzVDMF8OZqlaydizoJO0yWr9GbCN1+OKq5 EhLrEqU= -----END CERTIFICATE-----` pemPrecertificate = `-----BEGIN CERTIFICATE----- MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/ BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3 tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf kGzKLC0+6/yBmWTr2M98CIY/vg== -----END CERTIFICATE-----` ) func TestIsPrecertificate(t *testing.T) { tests := []struct { desc string certPEM string want bool }{ { desc: "certificate", certPEM: pemCertificate, want: false, }, { desc: "precertificate", certPEM: pemPrecertificate, want: true, }, { desc: "nil", certPEM: "", want: false, }, } for _, test := range tests { var cert *Certificate if test.certPEM != "" { var err error cert, err = certificateFromPEM(test.certPEM) if err != nil { t.Errorf("%s: error parsing certificate: %s", test.desc, err) continue } } if got := cert.IsPrecertificate(); got != test.want { t.Errorf("%s: c.IsPrecertificate() = %t, want %t", test.desc, got, test.want) } } } const ed25519CRLCertificate = ` Certificate: Data: Version: 3 (0x2) Serial Number: 7a:07:a0:9d:14:04:16:fc:1f:d8:e5:fe:d1:1d:1f:8d Signature Algorithm: ED25519 Issuer: CN = Ed25519 CRL Test CA Validity Not Before: Oct 30 01:20:20 2019 GMT Not After : Dec 31 23:59:59 9999 GMT Subject: CN = Ed25519 CRL Test CA Subject Public Key Info: Public Key Algorithm: ED25519 ED25519 Public-Key: pub: 95:73:3b:b0:06:2a:31:5a:b6:a7:a6:6e:ef:71:df: ac:6f:6b:39:03:85:5e:63:4b:f8:a6:0f:68:c6:6f: 75:21 X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign X509v3 Extended Key Usage: TLS Web Client Authentication, TLS Web Server Authentication, OCSP Signing X509v3 Basic Constraints: critical CA:TRUE X509v3 Subject Key Identifier: B7:17:DA:16:EA:C5:ED:1F:18:49:44:D3:D2:E3:A0:35:0A:81:93:60 X509v3 Authority Key Identifier: keyid:B7:17:DA:16:EA:C5:ED:1F:18:49:44:D3:D2:E3:A0:35:0A:81:93:60 Signature Algorithm: ED25519 fc:3e:14:ea:bb:70:c2:6f:38:34:70:bc:c8:a7:f4:7c:0d:1e: 28:d7:2a:9f:22:8a:45:e8:02:76:84:1e:2d:64:2d:1e:09:b5: 29:71:1f:95:8a:4e:79:87:51:60:9a:e7:86:40:f6:60:c7:d1: ee:68:76:17:1d:90:cc:92:93:07 -----BEGIN CERTIFICATE----- MIIBijCCATygAwIBAgIQegegnRQEFvwf2OX+0R0fjTAFBgMrZXAwHjEcMBoGA1UE AxMTRWQyNTUxOSBDUkwgVGVzdCBDQTAgFw0xOTEwMzAwMTIwMjBaGA85OTk5MTIz MTIzNTk1OVowHjEcMBoGA1UEAxMTRWQyNTUxOSBDUkwgVGVzdCBDQTAqMAUGAytl cAMhAJVzO7AGKjFatqembu9x36xvazkDhV5jS/imD2jGb3Uho4GNMIGKMA4GA1Ud DwEB/wQEAwIBhjAnBgNVHSUEIDAeBggrBgEFBQcDAgYIKwYBBQUHAwEGCCsGAQUF BwMJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLcX2hbqxe0fGElE09LjoDUK gZNgMB8GA1UdIwQYMBaAFLcX2hbqxe0fGElE09LjoDUKgZNgMAUGAytlcANBAPw+ FOq7cMJvODRwvMin9HwNHijXKp8iikXoAnaEHi1kLR4JtSlxH5WKTnmHUWCa54ZA 9mDH0e5odhcdkMySkwc= -----END CERTIFICATE-----` const ed25519CRLKey = `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEINdKh2096vUBYu4EIFpjShsUSh3vimKya1sQ1YTT4RZG -----END PRIVATE KEY-----` func TestCRLCreation(t *testing.T) { block, _ := pem.Decode([]byte(pemPrivateKey)) privRSA, _ := ParsePKCS1PrivateKey(block.Bytes) block, _ = pem.Decode([]byte(pemCertificate)) certRSA, _ := ParseCertificate(block.Bytes) block, _ = pem.Decode([]byte(ed25519CRLKey)) privEd25519, _ := ParsePKCS8PrivateKey(block.Bytes) block, _ = pem.Decode([]byte(ed25519CRLCertificate)) certEd25519, _ := ParseCertificate(block.Bytes) tests := []struct { name string priv interface{} cert *Certificate }{ {"RSA CA", privRSA, certRSA}, {"Ed25519 CA", privEd25519, certEd25519}, } loc := time.FixedZone("Oz/Atlantis", int((2 * time.Hour).Seconds())) now := time.Unix(1000, 0).In(loc) nowUTC := now.UTC() expiry := time.Unix(10000, 0) revokedCerts := []pkix.RevokedCertificate{ { SerialNumber: big.NewInt(1), RevocationTime: nowUTC, }, { SerialNumber: big.NewInt(42), // RevocationTime should be converted to UTC before marshaling. RevocationTime: now, }, } expectedCerts := []pkix.RevokedCertificate{ { SerialNumber: big.NewInt(1), RevocationTime: nowUTC, }, { SerialNumber: big.NewInt(42), RevocationTime: nowUTC, }, } for _, test := range tests { crlBytes, err := test.cert.CreateCRL(rand.Reader, test.priv, revokedCerts, now, expiry) if err != nil { t.Errorf("%s: error creating CRL: %s", test.name, err) } parsedCRL, err := ParseDERCRL(crlBytes) if err != nil { t.Errorf("%s: error reparsing CRL: %s", test.name, err) } if !reflect.DeepEqual(parsedCRL.TBSCertList.RevokedCertificates, expectedCerts) { t.Errorf("%s: RevokedCertificates mismatch: got %v; want %v.", test.name, parsedCRL.TBSCertList.RevokedCertificates, expectedCerts) } } } func fromBase64(in string) []byte { out := make([]byte, base64.StdEncoding.DecodedLen(len(in))) n, err := base64.StdEncoding.Decode(out, []byte(in)) if err != nil { panic("failed to base64 decode") } return out[:n] } func TestParseDERCRL(t *testing.T) { derBytes := fromBase64(derCRLBase64) certList, err := ParseDERCRL(derBytes) if err != nil { t.Errorf("error parsing: %s", err) return } numCerts := len(certList.TBSCertList.RevokedCertificates) expected := 88 if numCerts != expected { t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected) } if certList.HasExpired(time.Unix(1302517272, 0)) { t.Errorf("CRL has expired (but shouldn't have)") } // Can't check the signature here without a package cycle. } func TestCRLWithoutExpiry(t *testing.T) { derBytes := fromBase64("MIHYMIGZMAkGByqGSM44BAMwEjEQMA4GA1UEAxMHQ2FybERTUxcNOTkwODI3MDcwMDAwWjBpMBMCAgDIFw05OTA4MjIwNzAwMDBaMBMCAgDJFw05OTA4MjIwNzAwMDBaMBMCAgDTFw05OTA4MjIwNzAwMDBaMBMCAgDSFw05OTA4MjIwNzAwMDBaMBMCAgDUFw05OTA4MjQwNzAwMDBaMAkGByqGSM44BAMDLwAwLAIUfmVSdjP+NHMX0feW+aDU2G1cfT0CFAJ6W7fVWxjBz4fvftok8yqDnDWh") certList, err := ParseDERCRL(derBytes) if err != nil { t.Fatal(err) } if !certList.TBSCertList.NextUpdate.IsZero() { t.Errorf("NextUpdate is not the zero value") } } func TestParsePEMCRL(t *testing.T) { pemBytes := fromBase64(pemCRLBase64) certList, err := ParseCRL(pemBytes) if err != nil { t.Errorf("error parsing: %s", err) return } numCerts := len(certList.TBSCertList.RevokedCertificates) expected := 2 if numCerts != expected { t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected) } if certList.HasExpired(time.Unix(1302517272, 0)) { t.Errorf("CRL has expired (but shouldn't have)") } // Can't check the signature here without a package cycle. } func TestNonFatalErrors(t *testing.T) { nfe := NonFatalErrors{} nfe.AddError(errors.New("one")) nfe.AddError(errors.New("two")) nfe.AddError(errors.New("three")) if !nfe.HasError() { t.Fatal("NonFatalError claimed not to have an error") } if !strings.Contains(nfe.Error(), "one; two; three") { t.Fatalf("Didn't see expected string from Error(), got '%s'", nfe.Error()) } } func TestIsFatal(t *testing.T) { tests := []struct { err error want bool }{ {err: errors.New("normal error"), want: true}, {err: NonFatalErrors{}, want: false}, {err: nil, want: false}, {err: &Errors{}, want: false}, {err: &Errors{Errs: []Error{{ID: 1, Summary: "test", Fatal: true}}}, want: true}, {err: &Errors{Errs: []Error{{ID: 1, Summary: "test", Fatal: false}}}, want: false}, } for _, test := range tests { got := IsFatal(test.err) if got != test.want { t.Errorf("IsFatal(%T %v)=%v, want %v", test.err, test.err, got, test.want) } } } const ( tbsNoPoison = "30820245a003020102020842822a5b866fbfeb300d06092a864886f70d01010b" + "05003071310b3009060355040613024742310f300d060355040813064c6f6e64" + "6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" + "6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" + "654365727469666963617465417574686f72697479301e170d31363037313731" + "31313534305a170d3139303331393131313534305a3066310b30090603550406" + "130255533113301106035504080c0a43616c69666f726e696131163014060355" + "04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" + "6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" + "1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" + "54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" + "38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381ce3081cb301d" + "0603551d250416301406082b0601050507030106082b06010505070302306806" + "082b06010505070101045c305a302b06082b06010505073002861f687474703a" + "2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" + "010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" + "6f6d2f6f637370301d0603551d0e04160414dbf46e63eee2dcbebf38604f9831" + "d06444f163d830210603551d20041a3018300c060a2b06010401d67902050130" + "08060667810c010202" tbsPoisonFirst = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" + "05003071310b3009060355040613024742310f300d060355040813064c6f6e64" + "6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" + "6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" + "654365727469666963617465417574686f72697479301e170d31363037313731" + "31313534305a170d3139303331393131313534305a3066310b30090603550406" + "130255533113301106035504080c0a43616c69666f726e696131163014060355" + "04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" + "6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" + "1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" + "54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" + "38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e03013" + "060a2b06010401d6790204030101ff04020500301d0603551d25041630140608" + "2b0601050507030106082b06010505070302306806082b06010505070101045c" + "305a302b06082b06010505073002861f687474703a2f2f706b692e676f6f676c" + "652e636f6d2f47494147322e637274302b06082b06010505073001861f687474" + "703a2f2f636c69656e7473312e676f6f676c652e636f6d2f6f637370301d0603" + "551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83021060355" + "1d20041a3018300c060a2b06010401d6790205013008060667810c010202" tbsPoisonLast = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" + "05003071310b3009060355040613024742310f300d060355040813064c6f6e64" + "6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" + "6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" + "654365727469666963617465417574686f72697479301e170d31363037313731" + "31313534305a170d3139303331393131313534305a3066310b30090603550406" + "130255533113301106035504080c0a43616c69666f726e696131163014060355" + "04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" + "6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" + "1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" + "54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" + "38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e0301d" + "0603551d250416301406082b0601050507030106082b06010505070302306806" + "082b06010505070101045c305a302b06082b06010505073002861f687474703a" + "2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" + "010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" + "6f6d2f6f637370301d0603551d0e04160414dbf46e63eee2dcbebf38604f9831" + "d06444f163d830210603551d20041a3018300c060a2b06010401d67902050130" + "08060667810c0102023013060a2b06010401d6790204030101ff04020500" tbsPoisonMiddle = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" + "05003071310b3009060355040613024742310f300d060355040813064c6f6e64" + "6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" + "6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" + "654365727469666963617465417574686f72697479301e170d31363037313731" + "31313534305a170d3139303331393131313534305a3066310b30090603550406" + "130255533113301106035504080c0a43616c69666f726e696131163014060355" + "04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" + "6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" + "1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" + "54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" + "38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e0301d" + "0603551d250416301406082b0601050507030106082b06010505070302306806" + "082b06010505070101045c305a302b06082b06010505073002861f687474703a" + "2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" + "010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" + "6f6d2f6f6373703013060a2b06010401d6790204030101ff04020500301d0603" + "551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83021060355" + "1d20041a3018300c060a2b06010401d6790205013008060667810c010202" tbsPoisonTwice = "3082026fa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" + "05003071310b3009060355040613024742310f300d060355040813064c6f6e64" + "6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" + "6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" + "654365727469666963617465417574686f72697479301e170d31363037313731" + "31313534305a170d3139303331393131313534305a3066310b30090603550406" + "130255533113301106035504080c0a43616c69666f726e696131163014060355" + "04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" + "6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" + "1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" + "54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" + "38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381f83081f5301d" + "0603551d250416301406082b0601050507030106082b06010505070302306806" + "082b06010505070101045c305a302b06082b06010505073002861f687474703a" + "2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" + "010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" + "6f6d2f6f6373703013060a2b06010401d6790204030101ff04020500301d0603" + "551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83013060a2b" + "06010401d6790204030101ff0402050030210603551d20041a3018300c060a2b" + "06010401d6790205013008060667810c010202" ) func TestRemoveCTPoison(t *testing.T) { var tests = []struct { name string // for human consumption tbs string // hex encoded want string // hex encoded errstr string }{ {name: "invalid-der", tbs: "01020304", errstr: "failed to parse"}, {name: "trailing-data", tbs: tbsPoisonMiddle + "01020304", errstr: "trailing data"}, {name: "no-poison-ext", tbs: tbsNoPoison, errstr: "no extension of specified type present"}, {name: "two-poison-exts", tbs: tbsPoisonTwice, errstr: "multiple extensions of specified type present"}, {name: "poison-first", tbs: tbsPoisonFirst, want: tbsNoPoison}, {name: "poison-last", tbs: tbsPoisonLast, want: tbsNoPoison}, {name: "poison-middle", tbs: tbsPoisonMiddle, want: tbsNoPoison}, } for _, test := range tests { in, _ := hex.DecodeString(test.tbs) got, err := RemoveCTPoison(in) if test.errstr != "" { if err == nil { t.Errorf("RemoveCTPoison(%s)=%s,nil; want error %q", test.name, hex.EncodeToString(got), test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("RemoveCTPoison(%s)=nil,%q; want error %q", test.name, err, test.errstr) } continue } want, _ := hex.DecodeString(test.want) if err != nil { t.Errorf("RemoveCTPoison(%s)=nil,%q; want %s,nil", test.name, err, test.want) } else if !bytes.Equal(got, want) { t.Errorf("RemoveCTPoison(%s)=%s,nil; want %s,nil", test.name, hex.EncodeToString(got), test.want) } } } func makeCert(t *testing.T, template, issuer *Certificate) *Certificate { t.Helper() certData, err := CreateCertificate(rand.Reader, template, issuer, &testPrivateKey.PublicKey, testPrivateKey) if err != nil { t.Fatalf("failed to create pre-cert: %v", err) } cert, err := ParseCertificate(certData) if err != nil { t.Fatalf("failed to re-parse pre-cert: %v", err) } return cert } func TestBuildPrecertTBS(t *testing.T) { poisonExt := pkix.Extension{Id: OIDExtensionCTPoison, Critical: true, Value: asn1.NullBytes} preIssuerKeyID := []byte{0x19, 0x09, 0x19, 0x70} issuerKeyID := []byte{0x07, 0x07, 0x20, 0x07} preCertTemplate := Certificate{ Version: 3, SerialNumber: big.NewInt(123), Issuer: pkix.Name{CommonName: "precert Issuer"}, Subject: pkix.Name{CommonName: "precert subject"}, NotBefore: time.Now(), NotAfter: time.Now().Add(3 * time.Hour), ExtraExtensions: []pkix.Extension{poisonExt}, AuthorityKeyId: preIssuerKeyID, } preIssuerTemplate := Certificate{ Version: 3, SerialNumber: big.NewInt(1234), Issuer: pkix.Name{CommonName: "real Issuer"}, Subject: pkix.Name{CommonName: "precert Issuer"}, NotBefore: time.Now(), NotAfter: time.Now().Add(3 * time.Hour), AuthorityKeyId: issuerKeyID, SubjectKeyId: preIssuerKeyID, ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageCertificateTransparency}, } actualIssuerTemplate := Certificate{ Version: 3, SerialNumber: big.NewInt(12345), Issuer: pkix.Name{CommonName: "real Issuer"}, Subject: pkix.Name{CommonName: "real Issuer"}, NotBefore: time.Now(), NotAfter: time.Now().Add(3 * time.Hour), SubjectKeyId: issuerKeyID, } preCertWithAKI := makeCert(t, &preCertTemplate, &preIssuerTemplate) preIssuerWithAKI := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate) preIssuerTemplate.AuthorityKeyId = nil actualIssuerTemplate.SubjectKeyId = nil preIssuerWithoutAKI := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate) preCertTemplate.AuthorityKeyId = nil preIssuerTemplate.SubjectKeyId = nil preCertWithoutAKI := makeCert(t, &preCertTemplate, &preIssuerTemplate) preIssuerTemplate.ExtKeyUsage = nil invalidPreIssuer := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate) akiPrefix := []byte{0x30, 0x06, 0x80, 0x04} // SEQUENCE { [0] { ... } } var tests = []struct { name string tbs *Certificate preIssuer *Certificate wantAKI []byte wantErr bool }{ { name: "no-preIssuer-provided", tbs: preCertWithAKI, wantAKI: append(akiPrefix, preIssuerKeyID...), }, { name: "both-with-AKI", tbs: preCertWithAKI, preIssuer: preIssuerWithAKI, wantAKI: append(akiPrefix, issuerKeyID...), }, { name: "invalid-preIssuer", tbs: preCertWithAKI, preIssuer: invalidPreIssuer, wantErr: true, }, { name: "both-without-AKI", tbs: preCertWithoutAKI, preIssuer: preIssuerWithoutAKI, }, { name: "precert-with-preIssuer-without-AKI", tbs: preCertWithAKI, preIssuer: preIssuerWithoutAKI, }, { name: "precert-without-preIssuer-with-AKI", tbs: preCertWithoutAKI, preIssuer: preIssuerWithAKI, wantAKI: append(akiPrefix, issuerKeyID...), }, } for _, test := range tests { got, err := BuildPrecertTBS(test.tbs.RawTBSCertificate, test.preIssuer) if err != nil { if !test.wantErr { t.Errorf("BuildPrecertTBS(%s)=nil,%q; want _,nil", test.name, err) } continue } if test.wantErr { t.Errorf("BuildPrecertTBS(%s)=_,nil; want _,non-nil", test.name) } var tbs tbsCertificate if rest, err := asn1.Unmarshal(got, &tbs); err != nil { t.Errorf("BuildPrecertTBS(%s) gave unparsable TBS: %v", test.name, err) continue } else if len(rest) > 0 { t.Errorf("BuildPrecertTBS(%s) gave extra data in DER", test.name) } if test.preIssuer != nil { if got, want := tbs.Issuer.FullBytes, test.preIssuer.RawIssuer; !bytes.Equal(got, want) { t.Errorf("BuildPrecertTBS(%s).Issuer=%x, want %x", test.name, got, want) } } var gotAKI []byte for _, ext := range tbs.Extensions { if ext.Id.Equal(OIDExtensionAuthorityKeyId) { gotAKI = ext.Value break } } if gotAKI != nil { if test.wantAKI != nil { if !reflect.DeepEqual(gotAKI, test.wantAKI) { t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=%+v, want %+v", test.name, gotAKI, test.wantAKI) } } else { t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=%+v, want nil", test.name, gotAKI) } } else if test.wantAKI != nil { t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=nil, want %+v", test.name, test.wantAKI) } } } func TestImports(t *testing.T) { t.Skip("Import test skipped for forked codebase") if testing.Short() { t.Skip("skipping in -short mode") } // testenv.MustHaveGoRun(t) // Replace testenv.GoToolPath(t) with "go" for use outside of Go repo. if out, err := exec.Command("go", "run", "x509_test_import.go").CombinedOutput(); err != nil { t.Errorf("failed to run x509_test_import.go: %s\n%s", err, out) } } const derCRLBase64 = "MIINqzCCDJMCAQEwDQYJKoZIhvcNAQEFBQAwVjEZMBcGA1UEAxMQUEtJIEZJTk1FQ0NBTklDQTEVMBMGA1UEChMMRklOTUVDQ0FOSUNBMRUwEwYDVQQLEwxGSU5NRUNDQU5JQ0ExCzAJBgNVBAYTAklUFw0xMTA1MDQxNjU3NDJaFw0xMTA1MDQyMDU3NDJaMIIMBzAhAg4Ze1od49Lt1qIXBydAzhcNMDkwNzE2MDg0MzIyWjAAMCECDl0HSL9bcZ1Ci/UHJ0DPFw0wOTA3MTYwODQzMTNaMAAwIQIOESB9tVAmX3cY7QcnQNAXDTA5MDcxNjA4NDUyMlowADAhAg4S1tGAQ3mHt8uVBydA1RcNMDkwODA0MTUyNTIyWjAAMCECDlQ249Y7vtC25ScHJ0DWFw0wOTA4MDQxNTI1MzdaMAAwIQIOISMop3NkA4PfYwcnQNkXDTA5MDgwNDExMDAzNFowADAhAg56/BMoS29KEShTBydA2hcNMDkwODA0MTEwMTAzWjAAMCECDnBp/22HPH5CSWoHJ0DbFw0wOTA4MDQxMDU0NDlaMAAwIQIOV9IP+8CD8bK+XAcnQNwXDTA5MDgwNDEwNTcxN1owADAhAg4v5aRz0IxWqYiXBydA3RcNMDkwODA0MTA1NzQ1WjAAMCECDlOU34VzvZAybQwHJ0DeFw0wOTA4MDQxMDU4MjFaMAAwIAINO4CD9lluIxcwBydBAxcNMDkwNzIyMTUzMTU5WjAAMCECDgOllfO8Y1QA7/wHJ0ExFw0wOTA3MjQxMTQxNDNaMAAwIQIOJBX7jbiCdRdyjgcnQUQXDTA5MDkxNjA5MzAwOFowADAhAg5iYSAgmDrlH/RZBydBRRcNMDkwOTE2MDkzMDE3WjAAMCECDmu6k6srP3jcMaQHJ0FRFw0wOTA4MDQxMDU2NDBaMAAwIQIOX8aHlO0V+WVH4QcnQVMXDTA5MDgwNDEwNTcyOVowADAhAg5flK2rg3NnsRgDBydBzhcNMTEwMjAxMTUzMzQ2WjAAMCECDg35yJDL1jOPTgoHJ0HPFw0xMTAyMDExNTM0MjZaMAAwIQIOMyFJ6+e9iiGVBQcnQdAXDTA5MDkxODEzMjAwNVowADAhAg5Emb/Oykucmn8fBydB1xcNMDkwOTIxMTAxMDQ3WjAAMCECDjQKCncV+MnUavMHJ0HaFw0wOTA5MjIwODE1MjZaMAAwIQIOaxiFUt3dpd+tPwcnQfQXDTEwMDYxODA4NDI1MVowADAhAg5G7P8nO0tkrMt7BydB9RcNMTAwNjE4MDg0MjMwWjAAMCECDmTCC3SXhmDRst4HJ0H2Fw0wOTA5MjgxMjA3MjBaMAAwIQIOHoGhUr/pRwzTKgcnQfcXDTA5MDkyODEyMDcyNFowADAhAg50wrcrCiw8mQmPBydCBBcNMTAwMjE2MTMwMTA2WjAAMCECDifWmkvwyhEqwEcHJ0IFFw0xMDAyMTYxMzAxMjBaMAAwIQIOfgPmlW9fg+osNgcnQhwXDTEwMDQxMzA5NTIwMFowADAhAg4YHAGuA6LgCk7tBydCHRcNMTAwNDEzMDk1MTM4WjAAMCECDi1zH1bxkNJhokAHJ0IsFw0xMDA0MTMwOTU5MzBaMAAwIQIOMipNccsb/wo2fwcnQi0XDTEwMDQxMzA5NTkwMFowADAhAg46lCmvPl4GpP6ABydCShcNMTAwMTE5MDk1MjE3WjAAMCECDjaTcaj+wBpcGAsHJ0JLFw0xMDAxMTkwOTUyMzRaMAAwIQIOOMC13EOrBuxIOQcnQloXDTEwMDIwMTA5NDcwNVowADAhAg5KmZl+krz4RsmrBydCWxcNMTAwMjAxMDk0NjQwWjAAMCECDmLG3zQJ/fzdSsUHJ0JiFw0xMDAzMDEwOTUxNDBaMAAwIQIOP39ksgHdojf4owcnQmMXDTEwMDMwMTA5NTExN1owADAhAg4LDQzvWNRlD6v9BydCZBcNMTAwMzAxMDk0NjIyWjAAMCECDkmNfeclaFhIaaUHJ0JlFw0xMDAzMDEwOTQ2MDVaMAAwIQIOT/qWWfpH/m8NTwcnQpQXDTEwMDUxMTA5MTgyMVowADAhAg5m/ksYxvCEgJSvBydClRcNMTAwNTExMDkxODAxWjAAMCECDgvf3Ohq6JOPU9AHJ0KWFw0xMDA1MTEwOTIxMjNaMAAwIQIOKSPas10z4jNVIQcnQpcXDTEwMDUxMTA5MjEwMlowADAhAg4mCWmhoZ3lyKCDBydCohcNMTEwNDI4MTEwMjI1WjAAMCECDkeiyRsBMK0Gvr4HJ0KjFw0xMTA0MjgxMTAyMDdaMAAwIQIOa09b/nH2+55SSwcnQq4XDTExMDQwMTA4Mjk0NlowADAhAg5O7M7iq7gGplr1BydCrxcNMTEwNDAxMDgzMDE3WjAAMCECDjlT6mJxUjTvyogHJ0K1Fw0xMTAxMjcxNTQ4NTJaMAAwIQIODS/l4UUFLe21NAcnQrYXDTExMDEyNzE1NDgyOFowADAhAg5lPRA0XdOUF6lSBydDHhcNMTEwMTI4MTQzNTA1WjAAMCECDixKX4fFGGpENwgHJ0MfFw0xMTAxMjgxNDM1MzBaMAAwIQIORNBkqsPnpKTtbAcnQ08XDTEwMDkwOTA4NDg0MlowADAhAg5QL+EMM3lohedEBydDUBcNMTAwOTA5MDg0ODE5WjAAMCECDlhDnHK+HiTRAXcHJ0NUFw0xMDEwMTkxNjIxNDBaMAAwIQIOdBFqAzq/INz53gcnQ1UXDTEwMTAxOTE2MjA0NFowADAhAg4OjR7s8MgKles1BydDWhcNMTEwMTI3MTY1MzM2WjAAMCECDmfR/elHee+d0SoHJ0NbFw0xMTAxMjcxNjUzNTZaMAAwIQIOBTKv2ui+KFMI+wcnQ5YXDTEwMDkxNTEwMjE1N1owADAhAg49F3c/GSah+oRUBydDmxcNMTEwMTI3MTczMjMzWjAAMCECDggv4I61WwpKFMMHJ0OcFw0xMTAxMjcxNzMyNTVaMAAwIQIOXx/Y8sEvwS10LAcnQ6UXDTExMDEyODExMjkzN1owADAhAg5LSLbnVrSKaw/9BydDphcNMTEwMTI4MTEyOTIwWjAAMCECDmFFoCuhKUeACQQHJ0PfFw0xMTAxMTExMDE3MzdaMAAwIQIOQTDdFh2fSPF6AAcnQ+AXDTExMDExMTEwMTcxMFowADAhAg5B8AOXX61FpvbbBydD5RcNMTAxMDA2MTAxNDM2WjAAMCECDh41P2Gmi7PkwI4HJ0PmFw0xMDEwMDYxMDE2MjVaMAAwIQIOWUHGLQCd+Ale9gcnQ/0XDTExMDUwMjA3NTYxMFowADAhAg5Z2c9AYkikmgWOBydD/hcNMTEwNTAyMDc1NjM0WjAAMCECDmf/UD+/h8nf+74HJ0QVFw0xMTA0MTUwNzI4MzNaMAAwIQIOICvj4epy3MrqfwcnRBYXDTExMDQxNTA3Mjg1NlowADAhAg4bouRMfOYqgv4xBydEHxcNMTEwMzA4MTYyNDI1WjAAMCECDhebWHGoKiTp7pEHJ0QgFw0xMTAzMDgxNjI0NDhaMAAwIQIOX+qnxxAqJ8LtawcnRDcXDTExMDEzMTE1MTIyOFowADAhAg4j0fICqZ+wkOdqBydEOBcNMTEwMTMxMTUxMTQxWjAAMCECDhmXjsV4SUpWtAMHJ0RLFw0xMTAxMjgxMTI0MTJaMAAwIQIODno/w+zG43kkTwcnREwXDTExMDEyODExMjM1MlowADAhAg4b1gc88767Fr+LBydETxcNMTEwMTI4MTEwMjA4WjAAMCECDn+M3Pa1w2nyFeUHJ0RQFw0xMTAxMjgxMDU4NDVaMAAwIQIOaduoyIH61tqybAcnRJUXDTEwMTIxNTA5NDMyMlowADAhAg4nLqQPkyi3ESAKBydElhcNMTAxMjE1MDk0MzM2WjAAMCECDi504NIMH8578gQHJ0SbFw0xMTAyMTQxNDA1NDFaMAAwIQIOGuaM8PDaC5u1egcnRJwXDTExMDIxNDE0MDYwNFowADAhAg4ehYq/BXGnB5PWBydEnxcNMTEwMjA0MDgwOTUxWjAAMCECDkSD4eS4FxW5H20HJ0SgFw0xMTAyMDQwODA5MjVaMAAwIQIOOCcb6ilYObt1egcnRKEXDTExMDEyNjEwNDEyOVowADAhAg58tISWCCwFnKGnBydEohcNMTEwMjA0MDgxMzQyWjAAMCECDn5rjtabY/L/WL0HJ0TJFw0xMTAyMDQxMTAzNDFaMAAwDQYJKoZIhvcNAQEFBQADggEBAGnF2Gs0+LNiYCW1Ipm83OXQYP/bd5tFFRzyz3iepFqNfYs4D68/QihjFoRHQoXEB0OEe1tvaVnnPGnEOpi6krwekquMxo4H88B5SlyiFIqemCOIss0SxlCFs69LmfRYvPPvPEhoXtQ3ZThe0UvKG83GOklhvGl6OaiRf4Mt+m8zOT4Wox/j6aOBK6cw6qKCdmD+Yj1rrNqFGg1CnSWMoD6S6mwNgkzwdBUJZ22BwrzAAo4RHa2Uy3ef1FjwD0XtU5N3uDSxGGBEDvOe5z82rps3E22FpAA8eYl8kaXtmWqyvYU0epp4brGuTxCuBMCAsxt/OjIjeNNQbBGkwxgfYA0=" const pemCRLBase64 = "LS0tLS1CRUdJTiBYNTA5IENSTC0tLS0tDQpNSUlCOWpDQ0FWOENBUUV3RFFZSktvWklodmNOQVFFRkJRQXdiREVhTUJnR0ExVUVDaE1SVWxOQklGTmxZM1Z5DQphWFI1SUVsdVl5NHhIakFjQmdOVkJBTVRGVkpUUVNCUWRXSnNhV01nVW05dmRDQkRRU0IyTVRFdU1Dd0dDU3FHDQpTSWIzRFFFSkFSWWZjbk5oYTJWdmJuSnZiM1J6YVdkdVFISnpZWE5sWTNWeWFYUjVMbU52YlJjTk1URXdNakl6DQpNVGt5T0RNd1doY05NVEV3T0RJeU1Ua3lPRE13V2pDQmpEQktBaEVBckRxb2g5RkhKSFhUN09QZ3V1bjQrQmNODQpNRGt4TVRBeU1UUXlOekE1V2pBbU1Bb0dBMVVkRlFRRENnRUpNQmdHQTFVZEdBUVJHQTh5TURBNU1URXdNakUwDQpNalExTlZvd1BnSVJBTEd6blowOTVQQjVhQU9MUGc1N2ZNTVhEVEF5TVRBeU16RTBOVEF4TkZvd0dqQVlCZ05WDQpIUmdFRVJnUE1qQXdNakV3TWpNeE5EVXdNVFJhb0RBd0xqQWZCZ05WSFNNRUdEQVdnQlQxVERGNlVRTS9MTmVMDQpsNWx2cUhHUXEzZzltekFMQmdOVkhSUUVCQUlDQUlRd0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUZVNUFzNk16DQpxNVBSc2lmYW9iUVBHaDFhSkx5QytNczVBZ2MwYld5QTNHQWR4dXI1U3BQWmVSV0NCamlQL01FSEJXSkNsQkhQDQpHUmNxNXlJZDNFakRrYUV5eFJhK2k2N0x6dmhJNmMyOUVlNks5cFNZd2ppLzdSVWhtbW5Qclh0VHhsTDBsckxyDQptUVFKNnhoRFJhNUczUUE0Q21VZHNITnZicnpnbUNZcHZWRT0NCi0tLS0tRU5EIFg1MDkgQ1JMLS0tLS0NCg0K" func TestCreateCertificateRequest(t *testing.T) { random := rand.Reader ecdsa256Priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("Failed to generate ECDSA key: %s", err) } ecdsa384Priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { t.Fatalf("Failed to generate ECDSA key: %s", err) } ecdsa521Priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { t.Fatalf("Failed to generate ECDSA key: %s", err) } _, ed25519Priv, err := ed25519.GenerateKey(random) if err != nil { t.Fatalf("Failed to generate Ed25519 key: %s", err) } tests := []struct { name string priv interface{} sigAlgo SignatureAlgorithm }{ {"RSA", testPrivateKey, SHA1WithRSA}, {"ECDSA-256", ecdsa256Priv, ECDSAWithSHA1}, {"ECDSA-384", ecdsa384Priv, ECDSAWithSHA1}, {"ECDSA-521", ecdsa521Priv, ECDSAWithSHA1}, {"Ed25519", ed25519Priv, PureEd25519}, } for _, test := range tests { template := CertificateRequest{ Subject: pkix.Name{ CommonName: "test.example.com", Organization: []string{"Σ Acme Co"}, }, SignatureAlgorithm: test.sigAlgo, DNSNames: []string{"test.example.com"}, EmailAddresses: []string{"gopher@golang.org"}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")}, } derBytes, err := CreateCertificateRequest(random, &template, test.priv) if err != nil { t.Errorf("%s: failed to create certificate request: %s", test.name, err) continue } out, err := ParseCertificateRequest(derBytes) if err != nil { t.Errorf("%s: failed to create certificate request: %s", test.name, err) continue } err = out.CheckSignature() if err != nil { t.Errorf("%s: failed to check certificate request signature: %s", test.name, err) continue } if out.Subject.CommonName != template.Subject.CommonName { t.Errorf("%s: output subject common name and template subject common name don't match", test.name) } else if len(out.Subject.Organization) != len(template.Subject.Organization) { t.Errorf("%s: output subject organisation and template subject organisation don't match", test.name) } else if len(out.DNSNames) != len(template.DNSNames) { t.Errorf("%s: output DNS names and template DNS names don't match", test.name) } else if len(out.EmailAddresses) != len(template.EmailAddresses) { t.Errorf("%s: output email addresses and template email addresses don't match", test.name) } else if len(out.IPAddresses) != len(template.IPAddresses) { t.Errorf("%s: output IP addresses and template IP addresses names don't match", test.name) } } } func marshalAndParseCSR(t *testing.T, template *CertificateRequest) *CertificateRequest { derBytes, err := CreateCertificateRequest(rand.Reader, template, testPrivateKey) if err != nil { t.Fatal(err) } csr, err := ParseCertificateRequest(derBytes) if err != nil { t.Fatal(err) } return csr } func TestCertificateRequestOverrides(t *testing.T) { sanContents, err := marshalSANs([]string{"foo.example.com"}, nil, nil, nil) if err != nil { t.Fatal(err) } template := CertificateRequest{ Subject: pkix.Name{ CommonName: "test.example.com", Organization: []string{"Σ Acme Co"}, }, DNSNames: []string{"test.example.com"}, // An explicit extension should override the DNSNames from the // template. ExtraExtensions: []pkix.Extension{ { Id: OIDExtensionSubjectAltName, Value: sanContents, Critical: true, }, }, } csr := marshalAndParseCSR(t, &template) if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "foo.example.com" { t.Errorf("Extension did not override template. Got %v\n", csr.DNSNames) } if len(csr.Extensions) != 1 || !csr.Extensions[0].Id.Equal(OIDExtensionSubjectAltName) || !csr.Extensions[0].Critical { t.Errorf("SAN extension was not faithfully copied, got %#v", csr.Extensions) } // If there is already an attribute with X.509 extensions then the // extra extensions should be added to it rather than creating a CSR // with two extension attributes. template.Attributes = []pkix.AttributeTypeAndValueSET{ { Type: oidExtensionRequest, Value: [][]pkix.AttributeTypeAndValue{ { { Type: OIDExtensionAuthorityInfoAccess, Value: []byte("foo"), }, }, }, }, } csr = marshalAndParseCSR(t, &template) if l := len(csr.Attributes); l != 1 { t.Errorf("incorrect number of attributes: %d\n", l) } if !csr.Attributes[0].Type.Equal(oidExtensionRequest) || len(csr.Attributes[0].Value) != 1 || len(csr.Attributes[0].Value[0]) != 2 { t.Errorf("bad attributes: %#v\n", csr.Attributes) } sanContents2, err := marshalSANs([]string{"foo2.example.com"}, nil, nil, nil) if err != nil { t.Fatal(err) } // Extensions in Attributes should override those in ExtraExtensions. template.Attributes[0].Value[0] = append(template.Attributes[0].Value[0], pkix.AttributeTypeAndValue{ Type: OIDExtensionSubjectAltName, Value: sanContents2, }) csr = marshalAndParseCSR(t, &template) if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "foo2.example.com" { t.Errorf("Attributes did not override ExtraExtensions. Got %v\n", csr.DNSNames) } } func TestParseCertificateRequest(t *testing.T) { for _, csrBase64 := range csrBase64Array { csrBytes := fromBase64(csrBase64) csr, err := ParseCertificateRequest(csrBytes) if err != nil { t.Fatalf("failed to parse CSR: %s", err) } if len(csr.EmailAddresses) != 1 || csr.EmailAddresses[0] != "gopher@golang.org" { t.Errorf("incorrect email addresses found: %v", csr.EmailAddresses) } if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "test.example.com" { t.Errorf("incorrect DNS names found: %v", csr.DNSNames) } if len(csr.Subject.Country) != 1 || csr.Subject.Country[0] != "AU" { t.Errorf("incorrect Subject name: %v", csr.Subject) } found := false for _, e := range csr.Extensions { if e.Id.Equal(OIDExtensionBasicConstraints) { found = true break } } if !found { t.Errorf("basic constraints extension not found in CSR") } } } func TestCriticalFlagInCSRRequestedExtensions(t *testing.T) { // This CSR contains an extension request where the extensions have a // critical flag in them. In the past we failed to handle this. const csrBase64 = "MIICrTCCAZUCAQIwMzEgMB4GA1UEAwwXU0NFUCBDQSBmb3IgRGV2ZWxlciBTcmwxDzANBgNVBAsMBjQzNTk3MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALFMAJ7Zy9YyfgbNlbUWAW0LalNRMPs7aXmLANsCpjhnw3lLlfDPaLeWyKh1nK5I5ojaJOW6KIOSAcJkDUe3rrE0wR0RVt3UxArqs0R/ND3u5Q+bDQY2X1HAFUHzUzcdm5JRAIA355v90teMckaWAIlkRQjDE22Lzc6NAl64KOd1rqOUNj8+PfX6fSo20jm94Pp1+a6mfk3G/RUWVuSm7owO5DZI/Fsi2ijdmb4NUar6K/bDKYTrDFkzcqAyMfP3TitUtBp19Mp3B1yAlHjlbp/r5fSSXfOGHZdgIvp0WkLuK2u5eQrX5l7HMB/5epgUs3HQxKY6ljhh5wAjDwz//LsCAwEAAaA1MDMGCSqGSIb3DQEJDjEmMCQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQEFBQADggEBAAMq3bxJSPQEgzLYR/yaVvgjCDrc3zUbIwdOis6Go06Q4RnjH5yRaSZAqZQTDsPurQcnz2I39VMGEiSkFJFavf4QHIZ7QFLkyXadMtALc87tm17Ej719SbHcBSSZayR9VYJUNXRLayI6HvyUrmqcMKh+iX3WY3ICr59/wlM0tYa8DYN4yzmOa2Onb29gy3YlaF5A2AKAMmk003cRT9gY26mjpv7d21czOSSeNyVIoZ04IR9ee71vWTMdv0hu/af5kSjQ+ZG5/Qgc0+mnECLz/1gtxt1srLYbtYQ/qAY8oX1DCSGFS61tN/vl+4cxGMD/VGcGzADRLRHSlVqy2Qgss6Q=" csrBytes := fromBase64(csrBase64) csr, err := ParseCertificateRequest(csrBytes) if err != nil { t.Fatalf("failed to parse CSR: %s", err) } expected := []struct { Id asn1.ObjectIdentifier Value []byte }{ {OIDExtensionBasicConstraints, fromBase64("MAYBAf8CAQA=")}, {OIDExtensionKeyUsage, fromBase64("AwIChA==")}, } if n := len(csr.Extensions); n != len(expected) { t.Fatalf("expected to find %d extensions but found %d", len(expected), n) } for i, extension := range csr.Extensions { if !extension.Id.Equal(expected[i].Id) { t.Fatalf("extension #%d has unexpected type %v (expected %v)", i, extension.Id, expected[i].Id) } if !bytes.Equal(extension.Value, expected[i].Value) { t.Fatalf("extension #%d has unexpected contents %x (expected %x)", i, extension.Value, expected[i].Value) } } } // serialiseAndParse generates a self-signed certificate from template and // returns a parsed version of it. func serialiseAndParse(t *testing.T, template *Certificate) *Certificate { derBytes, err := CreateCertificate(rand.Reader, template, template, &testPrivateKey.PublicKey, testPrivateKey) if err != nil { t.Fatalf("failed to create certificate: %s", err) return nil } cert, err := ParseCertificate(derBytes) if err != nil { t.Fatalf("failed to parse certificate: %s", err) return nil } return cert } func TestMaxPathLen(t *testing.T) { template := &Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "Σ Acme Co", }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(100000, 0), BasicConstraintsValid: true, IsCA: true, } cert1 := serialiseAndParse(t, template) if m := cert1.MaxPathLen; m != -1 { t.Errorf("Omitting MaxPathLen didn't turn into -1, got %d", m) } if cert1.MaxPathLenZero { t.Errorf("Omitting MaxPathLen resulted in MaxPathLenZero") } template.MaxPathLen = 1 cert2 := serialiseAndParse(t, template) if m := cert2.MaxPathLen; m != 1 { t.Errorf("Setting MaxPathLen didn't work. Got %d but set 1", m) } if cert2.MaxPathLenZero { t.Errorf("Setting MaxPathLen resulted in MaxPathLenZero") } template.MaxPathLen = 0 template.MaxPathLenZero = true cert3 := serialiseAndParse(t, template) if m := cert3.MaxPathLen; m != 0 { t.Errorf("Setting MaxPathLenZero didn't work, got %d", m) } if !cert3.MaxPathLenZero { t.Errorf("Setting MaxPathLen to zero didn't result in MaxPathLenZero") } } func TestNoAuthorityKeyIdInSelfSignedCert(t *testing.T) { template := &Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "Σ Acme Co", }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(100000, 0), BasicConstraintsValid: true, IsCA: true, SubjectKeyId: []byte{1, 2, 3, 4}, } if cert := serialiseAndParse(t, template); len(cert.AuthorityKeyId) != 0 { t.Fatalf("self-signed certificate contained default authority key id") } template.AuthorityKeyId = []byte{1, 2, 3, 4} if cert := serialiseAndParse(t, template); len(cert.AuthorityKeyId) == 0 { t.Fatalf("self-signed certificate erased explicit authority key id") } } func TestASN1BitLength(t *testing.T) { tests := []struct { bytes []byte bitLen int }{ {nil, 0}, {[]byte{0x00}, 0}, {[]byte{0x00, 0x00}, 0}, {[]byte{0xf0}, 4}, {[]byte{0x88}, 5}, {[]byte{0xff}, 8}, {[]byte{0xff, 0x80}, 9}, {[]byte{0xff, 0x81}, 16}, } for i, test := range tests { if got := asn1BitLength(test.bytes); got != test.bitLen { t.Errorf("#%d: calculated bit-length of %d for %x, wanted %d", i, got, test.bytes, test.bitLen) } } } func TestVerifyEmptyCertificate(t *testing.T) { if _, err := new(Certificate).Verify(VerifyOptions{}); err != errNotParsed { t.Errorf("Verifying empty certificate resulted in unexpected error: %q (wanted %q)", err, errNotParsed) } } func TestInsecureAlgorithmErrorString(t *testing.T) { tests := []struct { sa SignatureAlgorithm want string }{ {MD2WithRSA, "x509: cannot verify signature: insecure algorithm MD2-RSA"}, {-1, "x509: cannot verify signature: insecure algorithm -1"}, {0, "x509: cannot verify signature: insecure algorithm 0"}, {9999, "x509: cannot verify signature: insecure algorithm 9999"}, } for i, tt := range tests { if got := fmt.Sprint(InsecureAlgorithmError(tt.sa)); got != tt.want { t.Errorf("%d. mismatch.\n got: %s\nwant: %s\n", i, got, tt.want) } } } // These CSR was generated with OpenSSL: // // openssl req -out CSR.csr -new -sha256 -nodes -keyout privateKey.key -config openssl.cnf // // With openssl.cnf containing the following sections: // // [ v3_req ] // basicConstraints = CA:FALSE // keyUsage = nonRepudiation, digitalSignature, keyEncipherment // subjectAltName = email:gopher@golang.org,DNS:test.example.com // [ req_attributes ] // challengePassword = ignored challenge // unstructuredName = ignored unstructured name var csrBase64Array = [...]string{ // Just [ v3_req ] "MIIDHDCCAgQCAQAwfjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLQ29tbW9uIE5hbWUxITAfBgkqhkiG9w0BCQEWEnRlc3RAZW1haWwuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1GY4YFx2ujlZEOJxQVYmsjUnLsd5nFVnNpLE4cV+77sgv9NPNlB8uhn3MXt5leD34rm/2BisCHOifPucYlSrszo2beuKhvwn4+2FxDmWtBEMu/QA16L5IvoOfYZm/gJTsPwKDqvaR0tTU67a9OtxwNTBMI56YKtmwd/o8d3hYv9cg+9ZGAZ/gKONcg/OWYx/XRh6bd0g8DMbCikpWgXKDsvvK1Nk+VtkDO1JxuBaj4Lz/p/MifTfnHoqHxWOWl4EaTs4Ychxsv34/rSj1KD1tJqorIv5Xv2aqv4sjxfbrYzX4kvS5SC1goIovLnhj5UjmQ3Qy8u65eow/LLWw+YFcCAwEAAaBZMFcGCSqGSIb3DQEJDjFKMEgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLgYDVR0RBCcwJYERZ29waGVyQGdvbGFuZy5vcmeCEHRlc3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAB6VPMRrchvNW61Tokyq3ZvO6/NoGIbuwUn54q6l5VZW0Ep5Nq8juhegSSnaJ0jrovmUgKDN9vEo2KxuAtwG6udS6Ami3zP+hRd4k9Q8djJPb78nrjzWiindLK5Fps9U5mMoi1ER8ViveyAOTfnZt/jsKUaRsscY2FzE9t9/o5moE6LTcHUS4Ap1eheR+J72WOnQYn3cifYaemsA9MJuLko+kQ6xseqttbh9zjqd9fiCSh/LNkzos9c+mg2yMADitaZinAh+HZi50ooEbjaT3erNq9O6RqwJlgD00g6MQdoz9bTAryCUhCQfkIaepmQ7BxS0pqWNW3MMwfDwx/Snz6g=", // Both [ v3_req ] and [ req_attributes ] "MIIDaTCCAlECAQAwfjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLQ29tbW9uIE5hbWUxITAfBgkqhkiG9w0BCQEWEnRlc3RAZW1haWwuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1GY4YFx2ujlZEOJxQVYmsjUnLsd5nFVnNpLE4cV+77sgv9NPNlB8uhn3MXt5leD34rm/2BisCHOifPucYlSrszo2beuKhvwn4+2FxDmWtBEMu/QA16L5IvoOfYZm/gJTsPwKDqvaR0tTU67a9OtxwNTBMI56YKtmwd/o8d3hYv9cg+9ZGAZ/gKONcg/OWYx/XRh6bd0g8DMbCikpWgXKDsvvK1Nk+VtkDO1JxuBaj4Lz/p/MifTfnHoqHxWOWl4EaTs4Ychxsv34/rSj1KD1tJqorIv5Xv2aqv4sjxfbrYzX4kvS5SC1goIovLnhj5UjmQ3Qy8u65eow/LLWw+YFcCAwEAAaCBpTAgBgkqhkiG9w0BCQcxEwwRaWdub3JlZCBjaGFsbGVuZ2UwKAYJKoZIhvcNAQkCMRsMGWlnbm9yZWQgdW5zdHJ1Y3R1cmVkIG5hbWUwVwYJKoZIhvcNAQkOMUowSDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAuBgNVHREEJzAlgRFnb3BoZXJAZ29sYW5nLm9yZ4IQdGVzdC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAgxe2N5O48EMsYE7o0rZBB0wi3Ov5/yYfnmmVI22Y3sP6VXbLDW0+UWIeSccOhzUCcZ/G4qcrfhhx6gTZTeA01nP7TdTJURvWAH5iFqj9sQ0qnLq6nEcVHij3sG6M5+BxAIVClQBk6lTCzgphc835Fjj6qSLuJ20XHdL5UfUbiJxx299CHgyBRL+hBUIPfz8p+ZgamyAuDLfnj54zzcRVyLlrmMLNPZNll1Q70RxoU6uWvLH8wB8vQe3Q/guSGubLyLRTUQVPh+dw1L4t8MKFWfX/48jwRM4gIRHFHPeAAE9D9YAoqdIvj/iFm/eQ++7DP8MDwOZWsXeB6jjwHuLmkQ==", } var md5cert = ` -----BEGIN CERTIFICATE----- MIIB4TCCAUoCCQCfmw3vMgPS5TANBgkqhkiG9w0BAQQFADA1MQswCQYDVQQGEwJB VTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UEChMITUQ1IEluYy4wHhcNMTUx MjAzMTkyOTMyWhcNMjkwODEyMTkyOTMyWjA1MQswCQYDVQQGEwJBVTETMBEGA1UE CBMKU29tZS1TdGF0ZTERMA8GA1UEChMITUQ1IEluYy4wgZ8wDQYJKoZIhvcNAQEB BQADgY0AMIGJAoGBANrq2nhLQj5mlXbpVX3QUPhfEm/vdEqPkoWtR/jRZIWm4WGf Wpq/LKHJx2Pqwn+t117syN8l4U5unyAi1BJSXjBwPZNd7dXjcuJ+bRLV7FZ/iuvs cfYyQQFTxan4TaJMd0x1HoNDbNbjHa02IyjjYE/r3mb/PIg+J2t5AZEh80lPAgMB AAEwDQYJKoZIhvcNAQEEBQADgYEAjGzp3K3ey/YfKHohf33yHHWd695HQxDAP+wY cs9/TAyLR+gJzJP7d18EcDDLJWVi7bhfa4EAD86di05azOh9kWSn4b3o9QYRGCSw GNnI3Zk0cwNKA49hZntKKiy22DhRk7JAHF01d6Bu3KkHkmENrtJ+zj/+159WAnUa qViorq4= -----END CERTIFICATE----- ` func TestMD5(t *testing.T) { pemBlock, _ := pem.Decode([]byte(md5cert)) cert, err := ParseCertificate(pemBlock.Bytes) if err != nil { t.Fatalf("failed to parse certificate: %s", err) } if sa := cert.SignatureAlgorithm; sa != MD5WithRSA { t.Errorf("signature algorithm is %v, want %v", sa, MD5WithRSA) } if err = cert.CheckSignatureFrom(cert); err == nil { t.Fatalf("certificate verification succeeded incorrectly") } if _, ok := err.(InsecureAlgorithmError); !ok { t.Fatalf("certificate verification returned %v (%T), wanted InsecureAlgorithmError", err, err) } } // certMissingRSANULL contains an RSA public key where the AlgorithmIdentifer // parameters are omitted rather than being an ASN.1 NULL. const certMissingRSANULL = ` -----BEGIN CERTIFICATE----- MIIB7TCCAVigAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTExMTIwODA3NTUxMloXDTEyMTIwNzA4 MDAxMlowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGc MAsGCSqGSIb3DQEBAQOBjAAwgYgCgYBO0Hsx44Jk2VnAwoekXh6LczPHY1PfZpIG hPZk1Y/kNqcdK+izIDZFI7Xjla7t4PUgnI2V339aEu+H5Fto5OkOdOwEin/ekyfE ARl6vfLcPRSr0FTKIQzQTW6HLlzF0rtNS0/Otiz3fojsfNcCkXSmHgwa2uNKWi7e E5xMQIhZkwIDAQABozIwMDAOBgNVHQ8BAf8EBAMCAKAwDQYDVR0OBAYEBAECAwQw DwYDVR0jBAgwBoAEAQIDBDALBgkqhkiG9w0BAQUDgYEANh+zegx1yW43RmEr1b3A p0vMRpqBWHyFeSnIyMZn3TJWRSt1tukkqVCavh9a+hoV2cxVlXIWg7nCto/9iIw4 hB2rXZIxE0/9gzvGnfERYraL7KtnvshksBFQRlgXa5kc0x38BvEO5ZaoDPl4ILdE GFGNEH5PlGffo05wc46QkYU= -----END CERTIFICATE-----` func TestRSAMissingNULLParameters(t *testing.T) { block, _ := pem.Decode([]byte(certMissingRSANULL)) if _, err := ParseCertificate(block.Bytes); err == nil { t.Error("unexpected success when parsing certificate with missing RSA NULL parameter") } else if !strings.Contains(err.Error(), "missing NULL") { t.Errorf("unrecognised error when parsing certificate with missing RSA NULL parameter: %s", err) } } const certISOOID = ` -----BEGIN CERTIFICATE----- MIIB5TCCAVKgAwIBAgIQtwyL3RPWV7dJQp34HwZG9DAJBgUrDgMCHQUAMBExDzAN BgNVBAMTBm15dGVzdDAeFw0xNjA4MDkyMjExMDVaFw0zOTEyMzEyMzU5NTlaMBEx DzANBgNVBAMTBm15dGVzdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArzIH GsyDB3ohIGkkvijF2PTRUX1bvOtY1eUUpjwHyu0twpAKSuaQv2Ha+/63+aHe8O86 BT+98wjXFX6RFSagtAujo80rIF2dSm33BGt18pDN8v6zp93dnAm0jRaSQrHJ75xw 5O+S1oEYR1LtUoFJy6qB104j6aINBAgOiLIKiMkCAwEAAaNGMEQwQgYDVR0BBDsw OYAQVuYVQ/WDjdGSkZRlTtJDNKETMBExDzANBgNVBAMTBm15dGVzdIIQtwyL3RPW V7dJQp34HwZG9DAJBgUrDgMCHQUAA4GBABngrSkH7vG5lY4sa4AZF59lAAXqBVJE J4TBiKC62hCdZv18rBleP6ETfhbPg7pTs8p4ebQbpmtNxRS9Lw3MzQ8Ya5Ybwzj2 NwBSyCtCQl7mrEg4nJqJl4A2EUhnET/oVxU0oTV/SZ3ziGXcY1oG1s6vidV7TZTu MCRtdSdaM7g3 -----END CERTIFICATE-----` func TestISOOIDInCertificate(t *testing.T) { block, _ := pem.Decode([]byte(certISOOID)) if cert, err := ParseCertificate(block.Bytes); err != nil { t.Errorf("certificate with ISO OID failed to parse: %s", err) } else if cert.SignatureAlgorithm == UnknownSignatureAlgorithm { t.Errorf("ISO OID not recognised in certificate") } } // certMultipleRDN contains a RelativeDistinguishedName with two elements (the // common name and serial number). This particular certificate was the first // such certificate in the “Pilot” Certificate Transparency log. const certMultipleRDN = ` -----BEGIN CERTIFICATE----- MIIFRzCCBC+gAwIBAgIEOl59NTANBgkqhkiG9w0BAQUFADA9MQswCQYDVQQGEwJz aTEbMBkGA1UEChMSc3RhdGUtaW5zdGl0dXRpb25zMREwDwYDVQQLEwhzaWdvdi1j YTAeFw0xMjExMTYxMDUyNTdaFw0xNzExMTYxMjQ5MDVaMIGLMQswCQYDVQQGEwJz aTEbMBkGA1UEChMSc3RhdGUtaW5zdGl0dXRpb25zMRkwFwYDVQQLExB3ZWItY2Vy dGlmaWNhdGVzMRAwDgYDVQQLEwdTZXJ2ZXJzMTIwFAYDVQQFEw0xMjM2NDg0MDEw MDEwMBoGA1UEAxMTZXBvcnRhbC5tc3MuZWR1cy5zaTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAMrNkZH9MPuBTjMGNk3sJX8V+CkFx/4ru7RTlLS6dlYM 098dtSfJ3s2w0p/1NB9UmR8j0yS0Kg6yoZ3ShsSO4DWBtcQD8820a6BYwqxxQTNf HSRZOc+N/4TQrvmK6t4k9Aw+YEYTMrWOU4UTeyhDeCcUsBdh7HjfWsVaqNky+2sv oic3zP5gF+2QfPkvOoHT3FLR8olNhViIE6Kk3eFIEs4dkq/ZzlYdLb8pHQoj/sGI zFmA5AFvm1HURqOmJriFjBwaCtn8AVEYOtQrnUCzJYu1ex8azyS2ZgYMX0u8A5Z/ y2aMS/B2W+H79WcgLpK28vPwe7vam0oFrVytAd+u65ECAwEAAaOCAf4wggH6MA4G A1UdDwEB/wQEAwIFoDBABgNVHSAEOTA3MDUGCisGAQQBr1kBAwMwJzAlBggrBgEF BQcCARYZaHR0cDovL3d3dy5jYS5nb3Yuc2kvY3BzLzAfBgNVHREEGDAWgRRwb2Rw b3JhLm1pemtzQGdvdi5zaTCB8QYDVR0fBIHpMIHmMFWgU6BRpE8wTTELMAkGA1UE BhMCc2kxGzAZBgNVBAoTEnN0YXRlLWluc3RpdHV0aW9uczERMA8GA1UECxMIc2ln b3YtY2ExDjAMBgNVBAMTBUNSTDM5MIGMoIGJoIGGhldsZGFwOi8veDUwMC5nb3Yu c2kvb3U9c2lnb3YtY2Esbz1zdGF0ZS1pbnN0aXR1dGlvbnMsYz1zaT9jZXJ0aWZp Y2F0ZVJldm9jYXRpb25MaXN0P2Jhc2WGK2h0dHA6Ly93d3cuc2lnb3YtY2EuZ292 LnNpL2NybC9zaWdvdi1jYS5jcmwwKwYDVR0QBCQwIoAPMjAxMjExMTYxMDUyNTda gQ8yMDE3MTExNjEyNDkwNVowHwYDVR0jBBgwFoAUHvjUU2uzgwbpBAZXAvmlv8ZY PHIwHQYDVR0OBBYEFGI1Duuu+wTGDZka/xHNbwcbM69ZMAkGA1UdEwQCMAAwGQYJ KoZIhvZ9B0EABAwwChsEVjcuMQMCA6gwDQYJKoZIhvcNAQEFBQADggEBAHny0K1y BQznrzDu3DDpBcGYguKU0dvU9rqsV1ua4nxkriSMWjgsX6XJFDdDW60I3P4VWab5 ag5fZzbGqi8kva/CzGgZh+CES0aWCPy+4Gb8lwOTt+854/laaJvd6kgKTER7z7U9 9C86Ch2y4sXNwwwPJ1A9dmrZJZOcJjS/WYZgwaafY2Hdxub5jqPE5nehwYUPVu9R uH6/skk4OEKcfOtN0hCnISOVuKYyS4ANARWRG5VGHIH06z3lGUVARFRJ61gtAprd La+fgSS+LVZ+kU2TkeoWAKvGq8MAgDq4D4Xqwekg7WKFeuyusi/NI5rm40XgjBMF DF72IUofoVt7wo0= -----END CERTIFICATE-----` func TestMultipleRDN(t *testing.T) { block, _ := pem.Decode([]byte(certMultipleRDN)) cert, err := ParseCertificate(block.Bytes) if err != nil { t.Fatalf("certificate with two elements in an RDN failed to parse: %v", err) } if want := "eportal.mss.edus.si"; cert.Subject.CommonName != want { t.Errorf("got common name of %q, but want %q", cert.Subject.CommonName, want) } if want := "1236484010010"; cert.Subject.SerialNumber != want { t.Errorf("got serial number of %q, but want %q", cert.Subject.SerialNumber, want) } } func TestSystemCertPool(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("not implemented on Windows; Issue 16736, 18609") } a, err := SystemCertPool() if err != nil { t.Fatal(err) } b, err := SystemCertPool() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(a, b) { t.Fatal("two calls to SystemCertPool had different results") } if ok := b.AppendCertsFromPEM([]byte(` -----BEGIN CERTIFICATE----- MIIDBjCCAe6gAwIBAgIRANXM5I3gjuqDfTp/PYrs+u8wDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xODAzMjcxOTU2MjFaFw0xOTAzMjcxOTU2 MjFaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDK+9m3rjsO2Djes6bIYQZ3eV29JF09ZrjOrEHLtaKrD6/acsoSoTsf cQr+rzzztdB5ijWXCS64zo/0OiqBeZUNZ67jVdToa9qW5UYe2H0Y+ZNdfA5GYMFD yk/l3/uBu3suTZPfXiW2TjEi27Q8ruNUIZ54DpTcs6y2rBRFzadPWwn/VQMlvRXM jrzl8Y08dgnYmaAHprxVzwMXcQ/Brol+v9GvjaH1DooHqkn8O178wsPQNhdtvN01 IXL46cYdcUwWrE/GX5u+9DaSi+0KWxAPQ+NVD5qUI0CKl4714yGGh7feXMjJdHgl VG4QJZlJvC4FsURgCHJT6uHGIelnSwhbAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIF oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeC FVRlc3RTeXN0ZW1DZXJ0UG9vbC5nbzANBgkqhkiG9w0BAQsFAAOCAQEAwuSRx/VR BKh2ICxZjL6jBwk/7UlU1XKbhQD96RqkidDNGEc6eLZ90Z5XXTurEsXqdm5jQYPs 1cdcSW+fOSMl7MfW9e5tM66FaIPZl9rKZ1r7GkOfgn93xdLAWe8XHd19xRfDreub YC8DVqgLASOEYFupVSl76ktPfxkU5KCvmUf3P2PrRybk1qLGFytGxfyice2gHSNI gify3K/+H/7wCkyFW4xYvzl7WW4mXxoqPRPjQt1J423DhnnQ4G1P8V/vhUpXNXOq N9IEPnWuihC09cyx/WMQIUlWnaQLHdfpPS04Iez3yy2PdfXJzwfPrja7rNE+skK6 pa/O1nF0AfWOpw== -----END CERTIFICATE----- `)); !ok { t.Fatal("AppendCertsFromPEM failed") } if reflect.DeepEqual(a, b) { t.Fatal("changing one pool modified the other") } } const emptyNameConstraintsPEM = ` -----BEGIN CERTIFICATE----- MIIC1jCCAb6gAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UEAxMdRW1w dHkgbmFtZSBjb25zdHJhaW50cyBpc3N1ZXIwHhcNMTMwMjAxMDAwMDAwWhcNMjAw NTMwMTA0ODM4WjAhMR8wHQYDVQQDExZFbXB0eSBuYW1lIGNvbnN0cmFpbnRzMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwriElUIt3LCqmJObs+yDoWPD F5IqgWk6moIobYjPfextZiYU6I3EfvAwoNxPDkN2WowcocUZMJbEeEq5ebBksFnx f12gBxlIViIYwZAzu7aFvhDMyPKQI3C8CG0ZSC9ABZ1E3umdA3CEueNOmP/TChNq Cl23+BG1Qb/PJkpAO+GfpWSVhTcV53Mf/cKvFHcjGNrxzdSoq9fyW7a6gfcGEQY0 LVkmwFWUfJ0wT8kaeLr0E0tozkIfo01KNWNzv6NcYP80QOBRDlApWu9ODmEVJHPD blx4jzTQ3JLa+4DvBNOjVUOp+mgRmjiW0rLdrxwOxIqIOwNjweMCp/hgxX/hTQID AQABoxEwDzANBgNVHR4EBjAEoAChADANBgkqhkiG9w0BAQsFAAOCAQEAWG+/zUMH QhP8uNCtgSHyim/vh7wminwAvWgMKxlkLBFns6nZeQqsOV1lABY7U0Zuoqa1Z5nb 6L+iJa4ElREJOi/erLc9uLwBdDCAR0hUTKD7a6i4ooS39DTle87cUnj0MW1CUa6H v5SsvpYW+1XleYJk/axQOOTcy4Es53dvnZsjXH0EA/QHnn7UV+JmlE3rtVxcYp6M LYPmRhTioROA/drghicRkiu9hxdPyxkYS16M5g3Zj30jdm+k/6C6PeNtN9YmOOga nCOSyFYfGhqOANYzpmuV+oIedAsPpIbfIzN8njYUs1zio+1IoI4o8ddM9sCbtPU8 o+WoY6IsCKXV/g== -----END CERTIFICATE-----` func TestEmptyNameConstraints(t *testing.T) { block, _ := pem.Decode([]byte(emptyNameConstraintsPEM)) _, err := ParseCertificate(block.Bytes) if err == nil { t.Fatal("unexpected success") } const expected = "empty name constraints" if str := err.Error(); !strings.Contains(str, expected) { t.Errorf("expected %q in error but got %q", expected, str) } } func TestPKIXNameString(t *testing.T) { pem, err := hex.DecodeString(certBytes) if err != nil { t.Fatal(err) } certs, err := ParseCertificates(pem) if IsFatal(err) { t.Fatal(err) } tests := []struct { dn pkix.Name want string }{ {pkix.Name{ CommonName: "Steve Kille", Organization: []string{"Isode Limited"}, OrganizationalUnit: []string{"RFCs"}, Locality: []string{"Richmond"}, Province: []string{"Surrey"}, StreetAddress: []string{"The Square"}, PostalCode: []string{"TW9 1DT"}, SerialNumber: "RFC 2253", Country: []string{"GB"}, }, "SERIALNUMBER=RFC 2253,CN=Steve Kille,OU=RFCs,O=Isode Limited,POSTALCODE=TW9 1DT,STREET=The Square,L=Richmond,ST=Surrey,C=GB"}, {certs[0].Subject, "CN=mail.google.com,O=Google Inc,L=Mountain View,ST=California,C=US"}, {pkix.Name{ Organization: []string{"#Google, Inc. \n-> 'Alphabet\" "}, Country: []string{"US"}, }, "O=\\#Google\\, Inc. \n-\\> 'Alphabet\\\"\\ ,C=US"}, {pkix.Name{ CommonName: "foo.com", Organization: []string{"Gopher Industries"}, ExtraNames: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier([]int{2, 5, 4, 3}), Value: "bar.com"}}, }, "CN=bar.com,O=Gopher Industries"}, {pkix.Name{ Locality: []string{"Gophertown"}, ExtraNames: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4, 5}), Value: "golang.org"}}, }, "1.2.3.4.5=#130a676f6c616e672e6f7267,L=Gophertown"}, } for i, test := range tests { if got := test.dn.String(); got != test.want { t.Errorf("#%d: String() = \n%s\n, want \n%s", i, got, test.want) } } } func TestRDNSequenceString(t *testing.T) { // Test some extra cases that get lost in pkix.Name conversions such as // multi-valued attributes. var ( oidCountry = []int{2, 5, 4, 6} oidOrganization = []int{2, 5, 4, 10} oidOrganizationalUnit = []int{2, 5, 4, 11} oidCommonName = []int{2, 5, 4, 3} ) tests := []struct { seq pkix.RDNSequence want string }{ { seq: pkix.RDNSequence{ pkix.RelativeDistinguishedNameSET{ pkix.AttributeTypeAndValue{Type: oidCountry, Value: "US"}, }, pkix.RelativeDistinguishedNameSET{ pkix.AttributeTypeAndValue{Type: oidOrganization, Value: "Widget Inc."}, }, pkix.RelativeDistinguishedNameSET{ pkix.AttributeTypeAndValue{Type: oidOrganizationalUnit, Value: "Sales"}, pkix.AttributeTypeAndValue{Type: oidCommonName, Value: "J. Smith"}, }, }, want: "OU=Sales+CN=J. Smith,O=Widget Inc.,C=US", }, } for i, test := range tests { if got := test.seq.String(); got != test.want { t.Errorf("#%d: String() = \n%s\n, want \n%s", i, got, test.want) } } } const criticalNameConstraintWithUnknownTypePEM = ` -----BEGIN CERTIFICATE----- MIIC/TCCAeWgAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UEAxMdRW1w dHkgbmFtZSBjb25zdHJhaW50cyBpc3N1ZXIwHhcNMTMwMjAxMDAwMDAwWhcNMjAw NTMwMTA0ODM4WjAhMR8wHQYDVQQDExZFbXB0eSBuYW1lIGNvbnN0cmFpbnRzMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwriElUIt3LCqmJObs+yDoWPD F5IqgWk6moIobYjPfextZiYU6I3EfvAwoNxPDkN2WowcocUZMJbEeEq5ebBksFnx f12gBxlIViIYwZAzu7aFvhDMyPKQI3C8CG0ZSC9ABZ1E3umdA3CEueNOmP/TChNq Cl23+BG1Qb/PJkpAO+GfpWSVhTcV53Mf/cKvFHcjGNrxzdSoq9fyW7a6gfcGEQY0 LVkmwFWUfJ0wT8kaeLr0E0tozkIfo01KNWNzv6NcYP80QOBRDlApWu9ODmEVJHPD blx4jzTQ3JLa+4DvBNOjVUOp+mgRmjiW0rLdrxwOxIqIOwNjweMCp/hgxX/hTQID AQABozgwNjA0BgNVHR4BAf8EKjAooCQwIokgIACrzQAAAAAAAAAAAAAAAP////8A AAAAAAAAAAAAAAChADANBgkqhkiG9w0BAQsFAAOCAQEAWG+/zUMHQhP8uNCtgSHy im/vh7wminwAvWgMKxlkLBFns6nZeQqsOV1lABY7U0Zuoqa1Z5nb6L+iJa4ElREJ Oi/erLc9uLwBdDCAR0hUTKD7a6i4ooS39DTle87cUnj0MW1CUa6Hv5SsvpYW+1Xl eYJk/axQOOTcy4Es53dvnZsjXH0EA/QHnn7UV+JmlE3rtVxcYp6MLYPmRhTioROA /drghicRkiu9hxdPyxkYS16M5g3Zj30jdm+k/6C6PeNtN9YmOOganCOSyFYfGhqO ANYzpmuV+oIedAsPpIbfIzN8njYUs1zio+1IoI4o8ddM9sCbtPU8o+WoY6IsCKXV /g== -----END CERTIFICATE-----` func TestCriticalNameConstraintWithUnknownType(t *testing.T) { block, _ := pem.Decode([]byte(criticalNameConstraintWithUnknownTypePEM)) cert, err := ParseCertificate(block.Bytes) if err != nil { t.Fatalf("unexpected parsing failure: %s", err) } if l := len(cert.UnhandledCriticalExtensions); l != 1 { t.Fatalf("expected one unhandled critical extension, but found %d", l) } } const badIPMaskPEM = ` -----BEGIN CERTIFICATE----- MIICzzCCAbegAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAxMSQmFk IElQIG1hc2sgaXNzdWVyMB4XDTEzMDIwMTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow FjEUMBIGA1UEAxMLQmFkIElQIG1hc2swggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDCuISVQi3csKqYk5uz7IOhY8MXkiqBaTqagihtiM997G1mJhTojcR+ 8DCg3E8OQ3ZajByhxRkwlsR4Srl5sGSwWfF/XaAHGUhWIhjBkDO7toW+EMzI8pAj cLwIbRlIL0AFnUTe6Z0DcIS5406Y/9MKE2oKXbf4EbVBv88mSkA74Z+lZJWFNxXn cx/9wq8UdyMY2vHN1Kir1/JbtrqB9wYRBjQtWSbAVZR8nTBPyRp4uvQTS2jOQh+j TUo1Y3O/o1xg/zRA4FEOUCla704OYRUkc8NuXHiPNNDcktr7gO8E06NVQ6n6aBGa OJbSst2vHA7Eiog7A2PB4wKn+GDFf+FNAgMBAAGjIDAeMBwGA1UdHgEB/wQSMBCg DDAKhwgBAgME//8BAKEAMA0GCSqGSIb3DQEBCwUAA4IBAQBYb7/NQwdCE/y40K2B IfKKb++HvCaKfAC9aAwrGWQsEWezqdl5Cqw5XWUAFjtTRm6iprVnmdvov6IlrgSV EQk6L96stz24vAF0MIBHSFRMoPtrqLiihLf0NOV7ztxSePQxbUJRroe/lKy+lhb7 VeV5gmT9rFA45NzLgSznd2+dmyNcfQQD9AeeftRX4maUTeu1XFxinowtg+ZGFOKh E4D92uCGJxGSK72HF0/LGRhLXozmDdmPfSN2b6T/oLo942031iY46BqcI5LIVh8a Go4A1jOma5X6gh50Cw+kht8jM3yeNhSzXOKj7Uigjijx10z2wJu09Tyj5ahjoiwI pdX+ -----END CERTIFICATE-----` func TestBadIPMask(t *testing.T) { block, _ := pem.Decode([]byte(badIPMaskPEM)) _, err := ParseCertificate(block.Bytes) if err == nil { t.Fatalf("unexpected success") } const expected = "contained invalid mask" if !strings.Contains(err.Error(), expected) { t.Fatalf("expected %q in error but got: %s", expected, err) } } const additionalGeneralSubtreePEM = ` -----BEGIN CERTIFICATE----- MIIG4TCCBMmgAwIBAgIRALss+4rLw2Ia7tFFhxE8g5cwDQYJKoZIhvcNAQELBQAw bjELMAkGA1UEBhMCTkwxIDAeBgNVBAoMF01pbmlzdGVyaWUgdmFuIERlZmVuc2ll MT0wOwYDVQQDDDRNaW5pc3RlcmllIHZhbiBEZWZlbnNpZSBDZXJ0aWZpY2F0aWUg QXV0b3JpdGVpdCAtIEcyMB4XDTEzMDMwNjEyMDM0OVoXDTEzMTEzMDEyMDM1MFow bDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUNlcnRpUGF0aCBMTEMxIjAgBgNVBAsT GUNlcnRpZmljYXRpb24gQXV0aG9yaXRpZXMxITAfBgNVBAMTGENlcnRpUGF0aCBC cmlkZ2UgQ0EgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANLW 4kXiRqvwBhJfN9uz12FA+P2D34MPxOt7TGXljm2plJ2CLzvaH8/ymsMdSWdJBS1M 8FmwvNL1w3A6ZuzksJjPikAu8kY3dcp3mrkk9eCPORDAwGtfsXwZysLiuEaDWpbD dHOaHnI6qWU0N6OI+hNX58EjDpIGC1WQdho1tHOTPc5Hf5/hOpM/29v/wr7kySjs Z+7nsvkm5rNhuJNzPsLsgzVaJ5/BVyOplZy24FKM8Y43MjR4osZm+a2e0zniqw6/ rvcjcGYabYaznZfQG1GXoyf2Vea+CCgpgUhlVafgkwEs8izl8rIpvBzXiFAgFQuG Ituoy92PJbDs430fA/cCAwEAAaOCAnowggJ2MEUGCCsGAQUFBwEBBDkwNzA1Bggr BgEFBQcwAoYpaHR0cDovL2NlcnRzLmNhLm1pbmRlZi5ubC9taW5kZWYtY2EtMi5w N2MwHwYDVR0jBBgwFoAUzln9WSPz2M64Rl2HYf2/KD8StmQwDwYDVR0TAQH/BAUw AwEB/zCB6QYDVR0gBIHhMIHeMEgGCmCEEAGHawECBQEwOjA4BggrBgEFBQcCARYs aHR0cDovL2Nwcy5kcC5jYS5taW5kZWYubmwvbWluZGVmLWNhLWRwLWNwcy8wSAYK YIQQAYdrAQIFAjA6MDgGCCsGAQUFBwIBFixodHRwOi8vY3BzLmRwLmNhLm1pbmRl Zi5ubC9taW5kZWYtY2EtZHAtY3BzLzBIBgpghBABh2sBAgUDMDowOAYIKwYBBQUH AgEWLGh0dHA6Ly9jcHMuZHAuY2EubWluZGVmLm5sL21pbmRlZi1jYS1kcC1jcHMv MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmxzLmNhLm1pbmRlZi5ubC9taW5k ZWYtY2EtMi5jcmwwDgYDVR0PAQH/BAQDAgEGMEYGA1UdHgEB/wQ8MDqhODA2pDEw LzELMAkGA1UEBhMCTkwxIDAeBgNVBAoTF01pbmlzdGVyaWUgdmFuIERlZmVuc2ll gQFjMF0GA1UdIQRWMFQwGgYKYIQQAYdrAQIFAQYMKwYBBAGBu1MBAQECMBoGCmCE EAGHawECBQIGDCsGAQQBgbtTAQEBAjAaBgpghBABh2sBAgUDBgwrBgEEAYG7UwEB AQIwHQYDVR0OBBYEFNDCjBM3M3ZKkag84ei3/aKc0d0UMA0GCSqGSIb3DQEBCwUA A4ICAQAQXFn9jF90/DNFf15JhoGtta/0dNInb14PMu3PAjcdrXYCDPpQZOArTUng 5YT1WuzfmjnXiTsziT3my0r9Mxvz/btKK/lnVOMW4c2q/8sIsIPnnW5ZaRGrsANB dNDZkzMYmeG2Pfgvd0AQSOrpE/TVgWfu/+MMRWwX9y6VbooBR7BLv7zMuVH0WqLn 6OMFth7fqsThlfMSzkE/RDSaU6n3wXAWT1SIqBITtccRjSUQUFm/q3xrb2cwcZA6 8vdS4hzNd+ttS905ay31Ks4/1Wrm1bH5RhEfRSH0VSXnc0b+z+RyBbmiwtVZqzxE u3UQg/rAmtLDclLFEzjp8YDTIRYSLwstDbEXO/0ArdGrQm79HQ8i/3ZbP2357myW i15qd6gMJIgGHS4b8Hc7R1K8LQ9Gm1aLKBEWVNGZlPK/cpXThpVmoEyslN2DHCrc fbMbjNZpXlTMa+/b9z7Fa4X8dY8u/ELzZuJXJv5Rmqtg29eopFFYDCl0Nkh1XAjo QejEoHHUvYV8TThHZr6Z6Ib8CECgTehU4QvepkgDXNoNrKRZBG0JhLjkwxh2whZq nvWBfALC2VuNOM6C0rDY+HmhMlVt0XeqnybD9MuQALMit7Z00Cw2CIjNsBI9xBqD xKK9CjUb7gzRUWSpB9jGHsvpEMHOzIFhufvH2Bz1XJw+Cl7khw== -----END CERTIFICATE-----` func TestAdditionFieldsInGeneralSubtree(t *testing.T) { // Very rarely, certificates can include additional fields in the // GeneralSubtree structure. This tests that such certificates can be // parsed. block, _ := pem.Decode([]byte(additionalGeneralSubtreePEM)) if _, err := ParseCertificate(block.Bytes); IsFatal(err) { t.Fatalf("failed to parse certificate: %s", err) } } func TestEmptySubject(t *testing.T) { template := Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"example.com"}, } derBytes, err := CreateCertificate(rand.Reader, &template, &template, &testPrivateKey.PublicKey, testPrivateKey) if err != nil { t.Fatalf("failed to create certificate: %s", err) } cert, err := ParseCertificate(derBytes) if err != nil { t.Fatalf("failed to parse certificate: %s", err) } for _, ext := range cert.Extensions { if ext.Id.Equal(OIDExtensionSubjectAltName) { if !ext.Critical { t.Fatal("SAN extension is not critical") } return } } t.Fatal("SAN extension is missing") } // multipleURLsInCRLDPPEM contains two URLs in a single CRL DistributionPoint // structure. It is taken from https://crt.sh/?id=12721534. const multipleURLsInCRLDPPEM = ` -----BEGIN CERTIFICATE----- MIIF4TCCBMmgAwIBAgIQc+6uFePfrahUGpXs8lhiTzANBgkqhkiG9w0BAQsFADCB 8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD LUFDQzAeFw0xNDA5MTgwODIxMDBaFw0zMDA5MTgwODIxMDBaMIGGMQswCQYDVQQG EwJFUzEzMDEGA1UECgwqQ09OU09SQ0kgQURNSU5JU1RSQUNJTyBPQkVSVEEgREUg Q0FUQUxVTllBMSowKAYDVQQLDCFTZXJ2ZWlzIFDDumJsaWNzIGRlIENlcnRpZmlj YWNpw7MxFjAUBgNVBAMMDUVDLUNpdXRhZGFuaWEwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDFkHPRZPZlXTWZ5psJhbS/Gx+bxcTpGrlVQHHtIkgGz77y TA7UZUFb2EQMncfbOhR0OkvQQn1aMvhObFJSR6nI+caf2D+h/m/InMl1MyH3S0Ak YGZZsthnyC6KxqK2A/NApncrOreh70ULkQs45aOKsi1kR1W0zE+iFN+/P19P7AkL Rl3bXBCVd8w+DLhcwRrkf1FCDw6cEqaFm3cGgf5cbBDMaVYAweWTxwBZAq2RbQAW jE7mledcYghcZa4U6bUmCBPuLOnO8KMFAvH+aRzaf3ws5/ZoOVmryyLLJVZ54peZ OwnP9EL4OuWzmXCjBifXR2IAblxs5JYj57tls45nAgMBAAGjggHaMIIB1jASBgNV HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUC2hZPofI oxUa4ECCIl+fHbLFNxUwHwYDVR0jBBgwFoAUoMOLRKo3pUW/l4Ba0fF4opvpXY0w gdYGA1UdIASBzjCByzCByAYEVR0gADCBvzAxBggrBgEFBQcCARYlaHR0cHM6Ly93 d3cuYW9jLmNhdC9DQVRDZXJ0L1JlZ3VsYWNpbzCBiQYIKwYBBQUHAgIwfQx7QXF1 ZXN0IGNlcnRpZmljYXQgw6lzIGVtw6hzIMO6bmljYSBpIGV4Y2x1c2l2YW1lbnQg YSBFbnRpdGF0cyBkZSBDZXJ0aWZpY2FjacOzLiBWZWdldSBodHRwczovL3d3dy5h b2MuY2F0L0NBVENlcnQvUmVndWxhY2lvMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEF BQcwAYYXaHR0cDovL29jc3AuY2F0Y2VydC5jYXQwYgYDVR0fBFswWTBXoFWgU4Yn aHR0cDovL2Vwc2NkLmNhdGNlcnQubmV0L2NybC9lYy1hY2MuY3JshihodHRwOi8v ZXBzY2QyLmNhdGNlcnQubmV0L2NybC9lYy1hY2MuY3JsMA0GCSqGSIb3DQEBCwUA A4IBAQChqFTjlAH5PyIhLjLgEs68CyNNC1+vDuZXRhy22TI83JcvGmQrZosPvVIL PsUXx+C06Pfqmh48Q9S89X9K8w1SdJxP/rZeGEoRiKpwvQzM4ArD9QxyC8jirxex 3Umg9Ai/sXQ+1lBf6xw4HfUUr1WIp7pNHj0ZWLo106urqktcdeAFWme+/klis5fu labCSVPuT/QpwakPrtqOhRms8vgpKiXa/eLtL9ZiA28X/Mker0zlAeTA7Z7uAnp6 oPJTlZu1Gg1ZDJueTWWsLlO+P+Wzm3MRRIbcgdRzm4mdO7ubu26SzX/aQXDhuih+ eVxXDTCfs7GUlxnjOp5j559X/N0A -----END CERTIFICATE----- ` func TestMultipleURLsInCRLDP(t *testing.T) { block, _ := pem.Decode([]byte(multipleURLsInCRLDPPEM)) cert, err := ParseCertificate(block.Bytes) if err != nil { t.Fatalf("failed to parse certificate: %s", err) } want := []string{ "http://epscd.catcert.net/crl/ec-acc.crl", "http://epscd2.catcert.net/crl/ec-acc.crl", } if got := cert.CRLDistributionPoints; !reflect.DeepEqual(got, want) { t.Errorf("CRL distribution points = %#v, want #%v", got, want) } } func TestParseCertificateFail(t *testing.T) { var tests = []struct { desc string in string wantErr string wantFatal bool }{ {desc: "SubjectInfoEmpty", in: "testdata/invalid/xf-ext-subject-info-empty.pem", wantErr: "empty SubjectInfoAccess"}, {desc: "RSAParamsNonNULL", in: "testdata/invalid/xf-pubkey-rsa-param-nonnull.pem", wantErr: "RSA key missing NULL parameters"}, {desc: "EmptyEKU", in: "testdata/invalid/xf-ext-extended-key-usage-empty.pem", wantErr: "empty ExtendedKeyUsage"}, {desc: "EKUEmptyOID", in: "testdata/invalid/xf-ext-extended-key-usage-empty-oid.pem", wantErr: "zero length OBJECT IDENTIFIER"}, {desc: "SECp192r1TooShort", in: "testdata/invalid/xf-pubkey-ecdsa-secp192r1.pem", wantErr: "insecure curve (secp192r1)"}, {desc: "SerialNumIntegerNotMinimal", in: "testdata/invalid/xf-der-invalid-nonminimal-int.pem", wantErr: "integer not minimally-encoded"}, {desc: "RSAIntegerNotMinimal", in: "testdata/invalid/xf-der-pubkey-rsa-nonminimal-int.pem", wantErr: "integer not minimally-encoded"}, {desc: "SubjectNonPrintable", in: "testdata/invalid/xf-subject-nonprintable.pem", wantErr: "PrintableString contains invalid character"}, {desc: "NegativeRSAModulus", in: "testdata/invalid/xf-pubkey-rsa-modulus-negative.pem", wantErr: "RSA modulus is not a positive number"}, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { data, err := os.ReadFile(test.in) if err != nil { t.Fatalf("failed to read test data: %v", err) } block, _ := pem.Decode(data) got, err := ParseCertificate(block.Bytes) if err == nil { t.Fatalf("ParseCertificate()=%+v,nil; want nil, err containing %q", got, test.wantErr) } if !strings.Contains(err.Error(), test.wantErr) { t.Errorf("ParseCertificate()=_,%v; want nil, err containing %q", err, test.wantErr) } gotFatal := IsFatal(err) if gotFatal != test.wantFatal { t.Errorf("ParseCertificate()=_,%v with fatal=%t; want nil, err containing %q with fatal=%t", err, gotFatal, test.wantErr, test.wantFatal) } }) } } google-certificate-transparency-go-2308f62/x509/x509_test_import.go000066400000000000000000000034601462611535200250360ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore // +build ignore // This file is run by the x509 tests to ensure that a program with minimal // imports can sign certificates without errors resulting from missing hash // functions. package main import ( "crypto/rand" "encoding/pem" "math/big" "strings" "time" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" ) func main() { block, _ := pem.Decode([]byte(pemPrivateKey)) rsaPriv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { panic("Failed to parse private key: " + err.Error()) } template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "test", Organization: []string{"Σ Acme Co"}, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(100000, 0), KeyUsage: x509.KeyUsageCertSign, } if _, err = x509.CreateCertificate(rand.Reader, &template, &template, &rsaPriv.PublicKey, rsaPriv); err != nil { panic("failed to create certificate with basic imports: " + err.Error()) } } var pemPrivateKey = testingKey(`-----BEGIN RSA TESTING KEY----- MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0 fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu /ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/ EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V -----END RSA TESTING KEY----- `) func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } google-certificate-transparency-go-2308f62/x509util/000077500000000000000000000000001462611535200222445ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509util/certcheck/000077500000000000000000000000001462611535200241775ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509util/certcheck/certcheck.go000066400000000000000000000202371462611535200264650ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // certcheck is a utility to show and check the contents of certificates. package main import ( "bytes" "crypto/tls" "flag" "fmt" "net/url" "os" "strings" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) var ( root = flag.String("root", "", "Root CA certificate file") intermediate = flag.String("intermediate", "", "Intermediate CA certificate file") useSystemRoots = flag.Bool("system_roots", false, "Use system roots") verbose = flag.Bool("verbose", false, "Verbose output") strict = flag.Bool("strict", true, "Set non-zero exit code for non-fatal errors in parsing") validate = flag.Bool("validate", false, "Validate certificate signatures") checkTime = flag.Bool("check_time", false, "Check current validity of certificate") checkName = flag.Bool("check_name", true, "Check certificate name validity") checkEKU = flag.Bool("check_eku", true, "Check EKU nesting validity") checkPathLen = flag.Bool("check_path_len", true, "Check path len constraint validity") checkNameConstraint = flag.Bool("check_name_constraint", true, "Check name constraints") checkUnknownCriticalExts = flag.Bool("check_unknown_critical_exts", true, "Check for unknown critical extensions") checkRevoked = flag.Bool("check_revocation", false, "Check revocation status of certificate") ) func addCerts(filename string, pool *x509.CertPool) { if filename != "" { dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { klog.Exitf("Failed to read certificate file: %v", err) } for _, data := range dataList { certs, err := x509.ParseCertificates(data) if err != nil { klog.Exitf("Failed to parse certificate from %s: %v", filename, err) } for _, cert := range certs { pool.AddCert(cert) } } } } func main() { klog.InitFlags(nil) flag.Parse() failed := false for _, target := range flag.Args() { var err error var chain []*x509.Certificate if strings.HasPrefix(target, "https://") { chain, err = chainFromSite(target) } else { chain, err = chainFromFile(target) } if err != nil { klog.Errorf("%v", err) } if x509.IsFatal(err) { failed = true continue } else if err != nil && *strict { failed = true } for _, cert := range chain { if *verbose { fmt.Print(x509util.CertificateToString(cert)) } if *checkRevoked { if err := checkRevocation(cert, *verbose); err != nil { klog.Errorf("%s: certificate is revoked: %v", target, err) failed = true } } } if *validate && len(chain) > 0 { opts := x509.VerifyOptions{ DisableTimeChecks: !*checkTime, DisableCriticalExtensionChecks: !*checkUnknownCriticalExts, DisableNameChecks: !*checkName, DisableEKUChecks: !*checkEKU, DisablePathLenChecks: !*checkPathLen, DisableNameConstraintChecks: !*checkNameConstraint, } if err := validateChain(chain, opts, *root, *intermediate, *useSystemRoots); err != nil { klog.Errorf("%s: verification error: %v", target, err) failed = true } } } if failed { os.Exit(1) } } // chainFromSite retrieves the certificate chain from an https: URL. // Note that both a chain and an error can be returned (in which case // the error will be of type x509.NonFatalErrors). func chainFromSite(target string) ([]*x509.Certificate, error) { u, err := url.Parse(target) if err != nil { return nil, fmt.Errorf("%s: failed to parse URL: %v", target, err) } if u.Scheme != "https" { return nil, fmt.Errorf("%s: non-https URL provided", target) } host := u.Host if !strings.Contains(host, ":") { host += ":443" } // Insecure TLS connection here so we can always proceed. conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true}) if err != nil { return nil, fmt.Errorf("%s: failed to dial %q: %v", target, host, err) } defer conn.Close() // Convert base crypto/x509.Certificates to our forked x509.Certificate type. goChain := conn.ConnectionState().PeerCertificates var nfe *x509.NonFatalErrors chain := make([]*x509.Certificate, len(goChain)) for i, goCert := range goChain { cert, err := x509.ParseCertificate(goCert.Raw) if x509.IsFatal(err) { return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err) } else if errs, ok := err.(x509.NonFatalErrors); ok { nfe = nfe.Append(&errs) } else if err != nil { return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err) } chain[i] = cert } if nfe.HasError() { return chain, *nfe } return chain, nil } // chainFromSite retrieves a certificate chain from a PEM file. // Note that both a chain and an error can be returned (in which case // the error will be of type x509.NonFatalErrors). func chainFromFile(filename string) ([]*x509.Certificate, error) { dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { return nil, fmt.Errorf("%s: failed to read data: %v", filename, err) } var nfe *x509.NonFatalErrors var chain []*x509.Certificate for _, data := range dataList { certs, err := x509.ParseCertificates(data) if x509.IsFatal(err) { return nil, fmt.Errorf("%s: failed to parse: %v", filename, err) } else if errs, ok := err.(x509.NonFatalErrors); ok { nfe = nfe.Append(&errs) } else if err != nil { return nil, fmt.Errorf("%s: failed to parse: %v", filename, err) } chain = append(chain, certs...) } if nfe.HasError() { return chain, *nfe } return chain, nil } func validateChain(chain []*x509.Certificate, opts x509.VerifyOptions, rootsFile, intermediatesFile string, useSystemRoots bool) error { roots := x509.NewCertPool() if useSystemRoots { systemRoots, err := x509.SystemCertPool() if err != nil { klog.Errorf("Failed to get system roots: %v", err) } roots = systemRoots } opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageAny} opts.Roots = roots opts.Intermediates = x509.NewCertPool() addCerts(rootsFile, opts.Roots) addCerts(intermediatesFile, opts.Intermediates) if !useSystemRoots && len(rootsFile) == 0 { // No root CA certs provided, so assume the chain is self-contained. if len(chain) > 1 { last := chain[len(chain)-1] if bytes.Equal(last.RawSubject, last.RawIssuer) { opts.Roots.AddCert(last) } } } if len(intermediatesFile) == 0 { // No intermediate CA certs provided, so assume later entries in the chain are intermediates. for i := 1; i < len(chain); i++ { opts.Intermediates.AddCert(chain[i]) } } _, err := chain[0].Verify(opts) return err } func checkRevocation(cert *x509.Certificate, verbose bool) error { for _, crldp := range cert.CRLDistributionPoints { crlDataList, err := x509util.ReadPossiblePEMURL(crldp, "X509 CRL") if err != nil { klog.Errorf("failed to retrieve CRL from %q: %v", crldp, err) continue } for _, crlData := range crlDataList { crl, err := x509.ParseCertificateList(crlData) if x509.IsFatal(err) { klog.Errorf("failed to parse CRL from %q: %v", crldp, err) continue } if err != nil { klog.Errorf("non-fatal error parsing CRL from %q: %v", crldp, err) } if verbose { fmt.Printf("\nRevocation data from %s:\n", crldp) fmt.Print(x509util.CRLToString(crl)) } for _, c := range crl.TBSCertList.RevokedCertificates { if c.SerialNumber.Cmp(cert.SerialNumber) == 0 { return fmt.Errorf("certificate is revoked since %v", c.RevocationTime) } } } } return nil } google-certificate-transparency-go-2308f62/x509util/certs_test.go000066400000000000000000000420601462611535200247540ustar00rootroot00000000000000// Copyright 2022 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509util_test // pemUnknownBlockType is a PEM containing only an empty block of a // non-standard type. const pemUnknownBlockType = ` -----BEGIN SOMETHING----- -----END SOMETHING-----` // pemCACertWithOtherStuff is a valid test CA certificate (pemCACert below) // with additional blocks surrounding it. const pemCACertWithOtherStuff = ` -----BEGIN SOMETHING----- -----END SOMETHING----- -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE----- -----BEGIN SOMETHING----- -----END SOMETHING-----` // pemCACert is a valid test CA certificate. // // Data: // Version: 3 (0x2) // Serial Number: 0 (0x0) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:d5:8a:68:53:62:10:a2:71:19:93:6e:77:83:21: // 18:1c:2a:40:13:c6:d0:7b:8c:76:eb:91:57:d3:d0: // fb:4b:3b:51:6e:ce:cb:d1:c9:8d:91:c5:2f:74:3f: // ab:63:5d:55:09:9c:d1:3a:ba:f3:1a:e5:41:44:24: // 51:a7:4c:78:16:f2:24:3c:f8:48:cf:28:31:cc:e6: // 7b:a0:4a:5a:23:81:9f:3c:ba:37:e6:24:d9:c3:bd: // b2:99:b8:39:dd:fe:26:31:d2:cb:3a:84:fc:7b:b2: // b5:c5:2f:cf:c1:4f:ff:40:6f:5c:d4:46:69:cb:b2: // f7:cf:df:86:fb:6a:b9:d1:b1 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:TRUE // Signature Algorithm: sha1WithRSAEncryption // 06:08:cc:4a:6d:64:f2:20:5e:14:6c:04:b2:76:f9:2b:0e:fa: // 94:a5:da:f2:3a:fc:38:06:60:6d:39:90:d0:a1:ea:23:3d:40: // 29:57:69:46:3b:04:66:61:e7:fa:1d:17:99:15:20:9a:ea:2e: // 0a:77:51:76:41:12:27:d7:c0:03:07:c7:47:0e:61:58:4f:d7: // 33:42:24:72:7f:51:d6:90:bc:47:a9:df:35:4d:b0:f6:eb:25: // 95:5d:e1:89:3c:4d:d5:20:2b:24:a2:f3:e4:40:d2:74:b5:4e: // 1b:d3:76:26:9c:a9:62:89:b7:6e:ca:a4:10:90:e1:4f:3b:0a: // 94:2e const pemCACert = ` -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE-----` // pemCACertDuplicated contains two identical copies of the same test CA cert. const pemCACertDuplicated = ` -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE-----` // pemCACertBad is a PEM block containinng invalid data that should not decode. const pemCACertBad = ` -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBA!"£$%^&&**SDFSKJ$%%^%^%^%&^&^!"£$%%IRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE-----` // pemCACertMultiple is a PEM block containing a valid CA and intermediate // certificate, specifically pemCACert above and then: // // Data: // Version: 3 (0x2) // Serial Number: 9 (0x9) // Signature Algorithm: sha1WithRSAEncryption // Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen // Validity // Not Before: Jun 1 00:00:00 2012 GMT // Not After : Jun 1 00:00:00 2022 GMT // Subject: C=GB, O=Certificate Transparency Intermediate CA, ST=Wales, L=Erw Wen // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (1024 bit) // Modulus: // 00:d7:6a:67:8d:11:6f:52:2e:55:ff:82:1c:90:64: // 25:08:b7:07:4b:14:d7:71:15:90:64:f7:92:7e:fd: // ed:b8:71:35:a1:36:5e:e7:de:18:cb:d5:ce:86:5f: // 86:0c:78:f4:33:b4:d0:d3:d3:40:77:02:e7:a3:ef: // 54:2b:1d:fe:9b:ba:a7:cd:f9:4d:c5:97:5f:c7:29: // f8:6f:10:5f:38:1b:24:35:35:cf:9c:80:0f:5c:a7: // 80:c1:d3:c8:44:00:ee:65:d1:6e:e9:cf:52:db:8a: // df:fe:50:f5:c4:93:35:0b:21:90:bf:50:d5:bc:36: // f3:ca:c5:a8:da:ae:92:cd:8b // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 96:55:08:05:02:78:47:9E:87:73:76:41:31:BC:14:3A:47:E2:29:AB // X509v3 Authority Key Identifier: // keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 // DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen // serial:00 // // X509v3 Basic Constraints: // CA:TRUE // Signature Algorithm: sha1WithRSAEncryption // 22:06:da:b1:c6:6b:71:dc:e0:95:c3:f6:aa:2e:f7:2c:f7:76: // 1b:e7:ab:d7:fc:39:c3:1a:4c:fe:1b:d9:6d:67:34:ca:82:f2: // 2d:de:5a:0c:8b:bb:dd:82:5d:7b:6f:3e:76:12:ad:8d:b3:00: // a7:e2:11:69:88:60:23:26:22:84:c3:aa:5d:21:91:ef:da:10: // bf:92:35:d3:7b:3a:2a:34:0d:59:41:9b:94:a4:85:66:f3:fa: // c3:cd:8b:53:d5:a4:e9:82:70:ea:d2:97:b0:72:10:f9:ce:4a: // 21:38:b1:88:11:14:3b:93:fa:4e:7a:87:dd:37:e1:38:5f:2c: // 29:08 const pemCACertMultiple = ` -----BEGIN CERTIFICATE----- MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP OwqULg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIC3TCCAkagAwIBAgIBCTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw MDAwMDBaMGIxCzAJBgNVBAYTAkdCMTEwLwYDVQQKEyhDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgSW50ZXJtZWRpYXRlIENBMQ4wDAYDVQQIEwVXYWxlczEQMA4GA1UE BxMHRXJ3IFdlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA12pnjRFvUi5V /4IckGQlCLcHSxTXcRWQZPeSfv3tuHE1oTZe594Yy9XOhl+GDHj0M7TQ09NAdwLn o+9UKx3+m7qnzflNxZdfxyn4bxBfOBskNTXPnIAPXKeAwdPIRADuZdFu6c9S24rf /lD1xJM1CyGQv1DVvDbzysWo2q6SzYsCAwEAAaOBrzCBrDAdBgNVHQ4EFgQUllUI BQJ4R56Hc3ZBMbwUOkfiKaswfQYDVR0jBHYwdIAUX52IDchz5lTU+A3Y5rDBJLRH w1WhWaRXMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuggEA MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAIgbascZrcdzglcP2qi73 LPd2G+er1/w5wxpM/hvZbWc0yoLyLd5aDIu73YJde28+dhKtjbMAp+IRaYhgIyYi hMOqXSGR79oQv5I103s6KjQNWUGblKSFZvP6w82LU9Wk6YJw6tKXsHIQ+c5KITix iBEUO5P6TnqH3TfhOF8sKQg= -----END CERTIFICATE-----` // pemFakeCACert is a test CA cert for testing. // // Data: // Version: 3 (0x2) // Serial Number: // b6:31:d2:ac:21:ab:65:20 // Signature Algorithm: sha256WithRSAEncryption // Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Validity // Not Before: Jul 11 12:23:26 2016 GMT // Not After : Jul 11 12:23:26 2017 GMT // Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority // Subject Public Key Info: // Public Key Algorithm: rsaEncryption // Public-Key: (2048 bit) // Modulus: // 00:a5:41:9a:7a:2d:98:a3:b5:78:6f:15:21:db:0c: // c1:0e:a1:f8:26:f5:b3:b2:67:85:dc:a1:e6:b7:83: // 6d:da:63:da:d0:f6:a3:ff:bc:43:f5:2b:9f:00:19: // 6e:6b:60:4b:43:20:6e:e2:cb:2e:b6:65:ed:9b:dc: // 80:c3:e1:5a:96:af:60:78:0e:0e:fb:8f:ea:3e:3d: // c9:67:8f:a4:57:1c:ba:e4:f3:37:a9:2f:dd:11:9d: // 10:5d:e5:d6:ef:d4:3b:06:d9:34:43:42:bb:bb:be: // 43:40:2b:e3:b6:d1:b5:6c:58:12:34:96:14:d4:fc: // 49:79:c5:26:8c:24:7d:b3:12:f5:f6:3e:b7:41:46: // 6b:6d:3a:41:fd:7c:e3:b5:fc:96:6c:c6:cc:ad:8d: // 48:09:73:44:64:ea:4f:17:1d:0a:4b:14:5a:19:07: // 4a:32:0f:41:2e:e4:85:bd:a1:e1:9b:de:63:7c:3b: // bc:ec:aa:93:2a:0b:a8:c7:24:34:54:42:38:a5:d1: // 0c:c4:f9:9e:7c:69:42:71:77:d7:95:aa:bb:13:3d: // f3:cc:c7:5d:b3:fd:76:25:25:e3:da:14:0e:59:81: // e8:2c:58:e8:09:29:7d:22:02:91:95:81:eb:55:6f: // 2f:17:b9:af:4a:f3:84:8b:24:6e:ea:14:6b:bb:90: // 84:35 // Exponent: 65537 (0x10001) // X509v3 extensions: // X509v3 Subject Key Identifier: // 01:02:03:04 // X509v3 Authority Key Identifier: // keyid:01:02:03:04 // // X509v3 Basic Constraints: critical // CA:TRUE, pathlen:10 // X509v3 Key Usage: critical // Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only // Signature Algorithm: sha256WithRSAEncryption // 92:be:33:eb:d5:d4:32:e7:9e:4e:65:2a:e8:3f:67:b8:f4:d7: // 34:ab:95:11:6a:5d:ba:fd:57:9b:94:6e:8d:20:be:fb:7a:e1: // 49:ca:39:ea:92:d3:81:5a:b1:87:a3:9f:50:a4:e0:1e:11:de: // c4:d1:07:a1:ca:d1:97:1a:92:bd:73:9a:11:ec:6a:9a:52:11: // 2d:40:e1:3b:4f:3c:1f:81:3f:4c:ab:6a:02:84:4f:8b:18:36: // 7a:cc:5c:a9:0e:25:2b:cd:57:53:88:d9:eb:82:b1:ce:62:76: // 56:d4:23:9e:01:b3:6d:2b:49:ea:d4:3a:c2:f5:76:a7:b3:2d: // 24:97:6f:b4:1c:74:6b:95:85:f6:b5:41:56:82:3c:ed:be:96: // 1e:5e:6a:2d:7b:f7:fd:7d:6e:3f:fb:c2:ec:61:b3:7c:7f:3b: // f5:9c:64:61:5f:02:93:87:cd:81:f9:7e:53:3e:c1:f5:79:85: // f4:41:87:c7:ca:bd:af:ab:2b:a4:aa:a8:1d:2c:50:ad:23:8f: // db:13:1d:71:8a:85:bd:ac:59:6c:c4:53:c5:71:0c:90:91:f3: // 0b:41:ef:da:6e:27:bb:09:57:9c:97:b9:d7:fc:20:96:c5:75: // 96:ce:2e:6c:a8:b6:6e:b0:4d:0f:3e:01:95:ea:8b:cd:ae:47: // d0:d9:01:b7 const pemFakeCACert = ` -----BEGIN CERTIFICATE----- MIIDrDCCApSgAwIBAgIJALYx0qwhq2UgMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0GA1UE CgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZpY2F0 ZUF1dGhvcml0eTAeFw0xNjA3MTExMjIzMjZaFw0xNzA3MTExMjIzMjZaMHExCzAJ BgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0G A1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZp Y2F0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKVB mnotmKO1eG8VIdsMwQ6h+Cb1s7Jnhdyh5reDbdpj2tD2o/+8Q/UrnwAZbmtgS0Mg buLLLrZl7ZvcgMPhWpavYHgODvuP6j49yWePpFccuuTzN6kv3RGdEF3l1u/UOwbZ NENCu7u+Q0Ar47bRtWxYEjSWFNT8SXnFJowkfbMS9fY+t0FGa206Qf1847X8lmzG zK2NSAlzRGTqTxcdCksUWhkHSjIPQS7khb2h4ZveY3w7vOyqkyoLqMckNFRCOKXR DMT5nnxpQnF315WquxM988zHXbP9diUl49oUDlmB6CxY6AkpfSICkZWB61VvLxe5 r0rzhIskbuoUa7uQhDUCAwEAAaNHMEUwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgw BoAEAQIDBDASBgNVHRMBAf8ECDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwDQYJ KoZIhvcNAQELBQADggEBAJK+M+vV1DLnnk5lKug/Z7j01zSrlRFqXbr9V5uUbo0g vvt64UnKOeqS04FasYejn1Ck4B4R3sTRB6HK0Zcakr1zmhHsappSES1A4TtPPB+B P0yragKET4sYNnrMXKkOJSvNV1OI2euCsc5idlbUI54Bs20rSerUOsL1dqezLSSX b7QcdGuVhfa1QVaCPO2+lh5eai179/19bj/7wuxhs3x/O/WcZGFfApOHzYH5flM+ wfV5hfRBh8fKva+rK6SqqB0sUK0jj9sTHXGKhb2sWWzEU8VxDJCR8wtB79puJ7sJ V5yXudf8IJbFdZbOLmyotm6wTQ8+AZXqi82uR9DZAbc= -----END CERTIFICATE-----` google-certificate-transparency-go-2308f62/x509util/crlcheck/000077500000000000000000000000001462611535200240225ustar00rootroot00000000000000google-certificate-transparency-go-2308f62/x509util/crlcheck/crlcheck.go000066400000000000000000000132211462611535200261260ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // crlcheck is a utility to show and check the contents of certificate // revocation lists (CRLs). package main import ( "flag" "fmt" "io" "net/http" "os" "time" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) var ( caFile = flag.String("ca", "", "CA certificate file") strict = flag.Bool("strict", false, "Strict validation of CRL contents") expectCerts = flag.Bool("cert", false, "Input files are certificates not CRLs") ) func main() { klog.InitFlags(nil) flag.Parse() // Build a list of possible CA certs from command line arguments. var caCerts []*x509.Certificate if *caFile != "" { caDataList, err := x509util.ReadPossiblePEMFile(*caFile, "CERTIFICATE") if err != nil { klog.Exitf("%s: failed to read CA cert data: %v", *caFile, err) } for _, caData := range caDataList { certs, err := x509.ParseCertificates(caData) if err != nil { klog.Errorf("%s: %v", *caFile, err) } if len(certs) == 0 { klog.Errorf("%s: no certificates found", *caFile) } caCerts = append(caCerts, certs[0]) } } errored := false for _, arg := range flag.Args() { if *expectCerts { if err := processCertArg(arg, caCerts); err != nil { klog.Errorf("%s: failed to read certificate data: %v", arg, err) errored = true } } else { if err := processCRLArg(arg, caCerts); err != nil { klog.Errorf("%s: failed to read CRL data: %v", arg, err) errored = true } } } if errored { os.Exit(1) } } func processCRLArg(arg string, caCerts []*x509.Certificate) error { dataList, err := x509util.ReadPossiblePEMURL(arg, "X509 CRL") if err != nil { return err } for _, data := range dataList { if _, err := processCRL(data, caCerts); err != nil { return err } } return nil } func processCRL(data []byte, caCerts []*x509.Certificate) (*x509.CertificateList, error) { certList, err := x509.ParseCertificateListDER(data) if certList == nil { return nil, fmt.Errorf("CRL parse error: %v", err) } if err != nil && *strict { return nil, fmt.Errorf("strict CRL parse error: %v", err) } klog.Infof("Processing CRL:\n%s", x509util.CRLToString(certList)) verified := false if len(caCerts) == 0 { klog.Warningf("Skipping signature validation as no CA certs available") verified = true } var verifyErr error for _, caCert := range caCerts { if err := caCert.CheckCertificateListSignature(certList); err != nil { verifyErr = err } else { klog.Infof("CRL signature verified against CA cert %q", x509util.NameToString(caCert.Subject)) verifyErr = nil verified = true break } } if !verified { return nil, fmt.Errorf("verification error: %v", verifyErr) } return certList, nil } func processCertArg(filename string, caCerts []*x509.Certificate) error { dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { return err } if len(dataList) == 0 { return fmt.Errorf("no certs found in %s", filename) } if len(caCerts) == 0 { // No user-provided CA certs, so use any later entries in the file as possible issuers. for i := 1; i < len(dataList); i++ { issuer, err := x509.ParseCertificate(dataList[i]) if err != nil { klog.Warningf("Failed to parse [%d] in chain: %v", i, err) continue } klog.Infof("Treating cert [%d] with subject %q as potential issuer", i, x509util.NameToString(issuer.Subject)) caCerts = append(caCerts, issuer) } } return processCert(dataList[0], caCerts) } func processCert(data []byte, caCerts []*x509.Certificate) error { client := &http.Client{} cert, err := x509.ParseCertificate(data) if err != nil { return fmt.Errorf("certificate parse error: %v", err) } issuer, err := x509util.GetIssuer(cert, client) if err != nil { klog.Warningf("Failed to retrieve issuer for cert: %v", err) } if issuer != nil { klog.Infof("Using issuer %q", x509util.NameToString(issuer.Subject)) caCerts = append(caCerts, issuer) } expired := false if time.Now().After(cert.NotAfter) { klog.Errorf("Certificate is expired (since %v)", cert.NotAfter) expired = true } for _, crldp := range cert.CRLDistributionPoints { klog.Infof("Retrieving CRL from %q", crldp) rsp, err := client.Get(crldp) if err != nil || rsp.StatusCode != http.StatusOK { return fmt.Errorf("failed to get CRL from %q: %v", crldp, err) } body, err := io.ReadAll(rsp.Body) if err != nil { return fmt.Errorf("failed to read CRL from %q: %v", crldp, err) } rsp.Body.Close() certList, err := processCRL(body, caCerts) if err != nil { return err } if expired { continue } // Check the CRL for the presence of the original cert. for _, rev := range certList.TBSCertList.RevokedCertificates { if rev.SerialNumber.Cmp(cert.SerialNumber) == 0 { klog.Errorf("%s: certificate with serial number %v revoked at %v", crldp, cert.SerialNumber, rev.RevocationTime) if rev.RevocationReason != x509.Unspecified { klog.Errorf(" revocation reason: %s\v", x509util.RevocationReasonToString(rev.RevocationReason)) } break } } } return nil } google-certificate-transparency-go-2308f62/x509util/files.go000066400000000000000000000067531462611535200237100ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509util import ( "encoding/pem" "fmt" "io" "net/http" "net/url" "os" "strings" "github.com/google/certificate-transparency-go/x509" ) // ReadPossiblePEMFile loads data from a file which may be in DER format // or may be in PEM format (with the given blockname). func ReadPossiblePEMFile(filename, blockname string) ([][]byte, error) { data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("%s: failed to read data: %v", filename, err) } return dePEM(data, blockname), nil } // ReadPossiblePEMURL attempts to determine if the given target is a local file or a // URL, and return the file contents regardless. It also copes with either PEM or DER // format data. func ReadPossiblePEMURL(target, blockname string) ([][]byte, error) { if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") { // Assume it's a filename return ReadPossiblePEMFile(target, blockname) } rsp, err := http.Get(target) if err != nil { return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) } data, err := io.ReadAll(rsp.Body) if err != nil { return nil, fmt.Errorf("failed to io.ReadAll(%q): %v", target, err) } return dePEM(data, blockname), nil } func dePEM(data []byte, blockname string) [][]byte { var results [][]byte if strings.Contains(string(data), "BEGIN "+blockname) { rest := data for { var block *pem.Block block, rest = pem.Decode(rest) if block == nil { break } if block.Type == blockname { results = append(results, block.Bytes) } } } else { results = append(results, data) } return results } // ReadFileOrURL returns the data from a target which may be either a filename // or an HTTP(S) URL. func ReadFileOrURL(target string, client *http.Client) ([]byte, error) { u, err := url.Parse(target) if err != nil || (u.Scheme != "http" && u.Scheme != "https") { return os.ReadFile(target) } rsp, err := client.Get(u.String()) if err != nil { return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) } return io.ReadAll(rsp.Body) } // GetIssuer attempts to retrieve the issuer for a certificate, by examining // the cert's Authority Information Access extension (if present) for the // issuer's URL and retrieving from there. func GetIssuer(cert *x509.Certificate, client *http.Client) (*x509.Certificate, error) { if len(cert.IssuingCertificateURL) == 0 { return nil, nil } issuerURL := cert.IssuingCertificateURL[0] rsp, err := client.Get(issuerURL) if err != nil || rsp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get issuer from %q: %v", issuerURL, err) } defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) if err != nil { return nil, fmt.Errorf("failed to read issuer from %q: %v", issuerURL, err) } issuers, err := x509.ParseCertificates(body) if err != nil { return nil, fmt.Errorf("failed to parse issuer cert: %v", err) } return issuers[0], nil } google-certificate-transparency-go-2308f62/x509util/pem_cert_pool.go000066400000000000000000000074431462611535200254320ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509util import ( "crypto/sha256" "encoding/pem" "errors" "fmt" "os" "github.com/google/certificate-transparency-go/x509" "k8s.io/klog/v2" ) // String for certificate blocks in BEGIN / END PEM headers const pemCertificateBlockType string = "CERTIFICATE" // PEMCertPool is a wrapper / extension to x509.CertPool. It allows us to access the // raw certs, which we need to serve get-roots request and has stricter handling on loading // certs into the pool. CertPool ignores errors if at least one cert loads correctly but // PEMCertPool requires all certs to load. type PEMCertPool struct { // maps from sha-256 to certificate, used for dup detection fingerprintToCertMap map[[sha256.Size]byte]x509.Certificate rawCerts []*x509.Certificate certPool *x509.CertPool } // NewPEMCertPool creates a new, empty, instance of PEMCertPool. func NewPEMCertPool() *PEMCertPool { return &PEMCertPool{fingerprintToCertMap: make(map[[sha256.Size]byte]x509.Certificate), certPool: x509.NewCertPool()} } // AddCert adds a certificate to a pool. Uses fingerprint to weed out duplicates. // cert must not be nil. func (p *PEMCertPool) AddCert(cert *x509.Certificate) { fingerprint := sha256.Sum256(cert.Raw) _, ok := p.fingerprintToCertMap[fingerprint] if !ok { p.fingerprintToCertMap[fingerprint] = *cert p.certPool.AddCert(cert) p.rawCerts = append(p.rawCerts, cert) } } // Included indicates whether the given cert is included in the pool. func (p *PEMCertPool) Included(cert *x509.Certificate) bool { fingerprint := sha256.Sum256(cert.Raw) _, ok := p.fingerprintToCertMap[fingerprint] return ok } // AppendCertsFromPEM adds certs to the pool from a byte slice assumed to contain PEM encoded data. // Skips over non certificate blocks in the data. Returns true if all certificates in the // data were parsed and added to the pool successfully and at least one certificate was found. func (p *PEMCertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) { for len(pemCerts) > 0 { var block *pem.Block block, pemCerts = pem.Decode(pemCerts) if block == nil { break } if block.Type != pemCertificateBlockType || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if x509.IsFatal(err) { klog.Warningf("error parsing PEM certificate: %v", err) return false } p.AddCert(cert) ok = true } return } // AppendCertsFromPEMFile adds certs from a file that contains concatenated PEM data. func (p *PEMCertPool) AppendCertsFromPEMFile(pemFile string) error { pemData, err := os.ReadFile(pemFile) if err != nil { return fmt.Errorf("failed to load PEM certs file: %v", err) } if !p.AppendCertsFromPEM(pemData) { return errors.New("failed to parse PEM certs file") } return nil } // Subjects returns a list of the DER-encoded subjects of all of the certificates in the pool. func (p *PEMCertPool) Subjects() (res [][]byte) { return p.certPool.Subjects() } // CertPool returns the underlying CertPool. func (p *PEMCertPool) CertPool() *x509.CertPool { return p.certPool } // RawCertificates returns a list of the raw bytes of certificates that are in this pool func (p *PEMCertPool) RawCertificates() []*x509.Certificate { return p.rawCerts } google-certificate-transparency-go-2308f62/x509util/pem_cert_pool_test.go000066400000000000000000000061371462611535200264700ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509util_test import ( "encoding/pem" "testing" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) func TestLoadSingleCertFromPEMs(t *testing.T) { for _, p := range []string{pemCACert, pemCACertWithOtherStuff, pemCACertDuplicated} { pool := x509util.NewPEMCertPool() ok := pool.AppendCertsFromPEM([]byte(p)) if !ok { t.Fatal("Expected to append a certificate ok") } if got, want := len(pool.Subjects()), 1; got != want { t.Fatalf("Got %d cert(s) in the pool, expected %d", got, want) } } } func TestBadOrEmptyCertificateRejected(t *testing.T) { for _, p := range []string{pemUnknownBlockType, pemCACertBad} { pool := x509util.NewPEMCertPool() ok := pool.AppendCertsFromPEM([]byte(p)) if ok { t.Fatal("Expected appending no certs") } if got, want := len(pool.Subjects()), 0; got != want { t.Fatalf("Got %d cert(s) in pool, expected %d", got, want) } } } func TestLoadMultipleCertsFromPEM(t *testing.T) { pool := x509util.NewPEMCertPool() ok := pool.AppendCertsFromPEM([]byte(pemCACertMultiple)) if !ok { t.Fatal("Rejected valid multiple certs") } if got, want := len(pool.Subjects()), 2; got != want { t.Fatalf("Got %d certs in pool, expected %d", got, want) } } func TestIncluded(t *testing.T) { certs := [2]*x509.Certificate{parsePEM(t, pemCACert), parsePEM(t, pemFakeCACert)} // Note: tests are cumulative tests := []struct { cert *x509.Certificate want [2]bool }{ {cert: nil, want: [2]bool{false, false}}, {cert: nil, want: [2]bool{false, false}}, {cert: certs[0], want: [2]bool{true, false}}, {cert: nil, want: [2]bool{true, false}}, {cert: certs[0], want: [2]bool{true, false}}, {cert: certs[1], want: [2]bool{true, true}}, {cert: nil, want: [2]bool{true, true}}, {cert: certs[1], want: [2]bool{true, true}}, } pool := x509util.NewPEMCertPool() for _, test := range tests { if test.cert != nil { pool.AddCert(test.cert) } for i, cert := range certs { got := pool.Included(cert) if got != test.want[i] { t.Errorf("pool.Included(cert[%d])=%v, want %v", i, got, test.want[i]) } } } } func parsePEM(t *testing.T, pemCert string) *x509.Certificate { var block *pem.Block block, _ = pem.Decode([]byte(pemCert)) if block == nil || block.Type != "CERTIFICATE" || len(block.Headers) != 0 { t.Fatal("No PEM data found") } cert, err := x509.ParseCertificate(block.Bytes) if x509.IsFatal(err) { t.Fatalf("Failed to parse PEM certificate: %v", err) } return cert } google-certificate-transparency-go-2308f62/x509util/revoked.go000066400000000000000000000152161462611535200242370ustar00rootroot00000000000000// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509util import ( "bytes" "encoding/hex" "fmt" "strconv" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" ) // RevocationReasonToString generates a string describing a revocation reason code. func RevocationReasonToString(reason x509.RevocationReasonCode) string { switch reason { case x509.Unspecified: return "Unspecified" case x509.KeyCompromise: return "Key Compromise" case x509.CACompromise: return "CA Compromise" case x509.AffiliationChanged: return "Affiliation Changed" case x509.Superseded: return "Superseded" case x509.CessationOfOperation: return "Cessation Of Operation" case x509.CertificateHold: return "Certificate Hold" case x509.RemoveFromCRL: return "Remove From CRL" case x509.PrivilegeWithdrawn: return "Privilege Withdrawn" case x509.AACompromise: return "AA Compromise" default: return strconv.Itoa(int(reason)) } } // CRLToString generates a string describing the given certificate revocation list. // The output roughly resembles that from openssl crl -text. func CRLToString(crl *x509.CertificateList) string { var result bytes.Buffer var showCritical = func(critical bool) { if critical { result.WriteString(" critical") } result.WriteString("\n") } result.WriteString("Certificate Revocation List (CRL):\n") result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", crl.TBSCertList.Version+1, crl.TBSCertList.Version)) result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.TBSCertList.Signature))) var issuer pkix.Name issuer.FillFromRDNSequence(&crl.TBSCertList.Issuer) result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(issuer))) result.WriteString(fmt.Sprintf(" Last Update: %v\n", crl.TBSCertList.ThisUpdate)) result.WriteString(fmt.Sprintf(" Next Update: %v\n", crl.TBSCertList.NextUpdate)) if len(crl.TBSCertList.Extensions) > 0 { result.WriteString(" CRL extensions:\n") } count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 Authority Key Identifier:") showCritical(critical) result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(crl.TBSCertList.AuthorityKeyID))) } count, critical = OIDInExtensions(x509.OIDExtensionIssuerAltName, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 Issuer Alt Name:") showCritical(critical) result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuerAltNames))) } count, critical = OIDInExtensions(x509.OIDExtensionCRLNumber, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 CRLNumber:") showCritical(critical) result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.CRLNumber)) } count, critical = OIDInExtensions(x509.OIDExtensionDeltaCRLIndicator, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 Delta CRL Indicator:") showCritical(critical) result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.BaseCRLNumber)) } count, critical = OIDInExtensions(x509.OIDExtensionIssuingDistributionPoint, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 Issuing Distribution Point:") showCritical(critical) result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuingDPFullNames))) } count, critical = OIDInExtensions(x509.OIDExtensionFreshestCRL, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" X509v3 Freshest CRL:") showCritical(critical) result.WriteString(" Full Name:\n") var buf bytes.Buffer for _, pt := range crl.TBSCertList.FreshestCRLDistributionPoint { commaAppend(&buf, "URI:"+pt) } result.WriteString(fmt.Sprintf(" %v\n", buf.String())) } count, critical = OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, crl.TBSCertList.Extensions) if count > 0 { result.WriteString(" Authority Information Access:") showCritical(critical) var issuerBuf bytes.Buffer for _, issuer := range crl.TBSCertList.IssuingCertificateURL { commaAppend(&issuerBuf, "URI:"+issuer) } if issuerBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String())) } var ocspBuf bytes.Buffer for _, ocsp := range crl.TBSCertList.OCSPServer { commaAppend(&ocspBuf, "URI:"+ocsp) } if ocspBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String())) } // TODO(drysdale): Display other GeneralName types } result.WriteString("\n") result.WriteString("Revoked Certificates:\n") for _, c := range crl.TBSCertList.RevokedCertificates { result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", c.SerialNumber.Text(10), c.SerialNumber.Text(16))) result.WriteString(fmt.Sprintf(" Revocation Date : %v\n", c.RevocationTime)) count, critical = OIDInExtensions(x509.OIDExtensionCRLReasons, c.Extensions) if count > 0 { result.WriteString(" X509v3 CRL Reason Code:") showCritical(critical) result.WriteString(fmt.Sprintf(" %s\n", RevocationReasonToString(c.RevocationReason))) } count, critical = OIDInExtensions(x509.OIDExtensionInvalidityDate, c.Extensions) if count > 0 { result.WriteString(" Invalidity Date:") showCritical(critical) result.WriteString(fmt.Sprintf(" %s\n", c.InvalidityDate)) } count, critical = OIDInExtensions(x509.OIDExtensionCertificateIssuer, c.Extensions) if count > 0 { result.WriteString(" Issuer:") showCritical(critical) result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&c.Issuer))) } } result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.SignatureAlgorithm))) appendHexData(&result, crl.SignatureValue.Bytes, 18, " ") result.WriteString("\n") return result.String() } google-certificate-transparency-go-2308f62/x509util/x509util.go000066400000000000000000000746301462611535200242100ustar00rootroot00000000000000// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package x509util includes utility code for working with X.509 // certificates from the x509 package. package x509util import ( "bytes" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "encoding/base64" "encoding/hex" "encoding/pem" "errors" "fmt" "net" "strconv" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/gossip/minimal/x509ext" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" ) // OIDForStandardExtension indicates whether oid identifies a standard extension. // Standard extensions are listed in RFC 5280 (and other RFCs). func OIDForStandardExtension(oid asn1.ObjectIdentifier) bool { if oid.Equal(x509.OIDExtensionSubjectKeyId) || oid.Equal(x509.OIDExtensionKeyUsage) || oid.Equal(x509.OIDExtensionExtendedKeyUsage) || oid.Equal(x509.OIDExtensionAuthorityKeyId) || oid.Equal(x509.OIDExtensionBasicConstraints) || oid.Equal(x509.OIDExtensionSubjectAltName) || oid.Equal(x509.OIDExtensionCertificatePolicies) || oid.Equal(x509.OIDExtensionNameConstraints) || oid.Equal(x509.OIDExtensionCRLDistributionPoints) || oid.Equal(x509.OIDExtensionIssuerAltName) || oid.Equal(x509.OIDExtensionSubjectDirectoryAttributes) || oid.Equal(x509.OIDExtensionInhibitAnyPolicy) || oid.Equal(x509.OIDExtensionPolicyConstraints) || oid.Equal(x509.OIDExtensionPolicyMappings) || oid.Equal(x509.OIDExtensionFreshestCRL) || oid.Equal(x509.OIDExtensionSubjectInfoAccess) || oid.Equal(x509.OIDExtensionAuthorityInfoAccess) || oid.Equal(x509.OIDExtensionIPPrefixList) || oid.Equal(x509.OIDExtensionASList) || oid.Equal(x509.OIDExtensionCTPoison) || oid.Equal(x509.OIDExtensionCTSCT) { return true } return false } // OIDInExtensions checks whether the extension identified by oid is present in extensions // and returns how many times it occurs together with an indication of whether any of them // are marked critical. func OIDInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) (int, bool) { count := 0 critical := false for _, ext := range extensions { if ext.Id.Equal(oid) { count++ if ext.Critical { critical = true } } } return count, critical } // String formatting for various X.509/ASN.1 types func bitStringToString(b asn1.BitString) string { // nolint:deadcode,unused result := hex.EncodeToString(b.Bytes) bitsLeft := b.BitLength % 8 if bitsLeft != 0 { result += " (" + strconv.Itoa(8-bitsLeft) + " unused bits)" } return result } func publicKeyAlgorithmToString(algo x509.PublicKeyAlgorithm) string { // Use OpenSSL-compatible strings for the algorithms. switch algo { case x509.RSA: return "rsaEncryption" case x509.DSA: return "dsaEncryption" case x509.ECDSA: return "id-ecPublicKey" default: return strconv.Itoa(int(algo)) } } // appendHexData adds a hex dump of binary data to buf, with line breaks // after each set of count bytes, and with each new line prefixed with the // given prefix. func appendHexData(buf *bytes.Buffer, data []byte, count int, prefix string) { for ii, b := range data { if ii%count == 0 { if ii > 0 { buf.WriteString("\n") } buf.WriteString(prefix) } buf.WriteString(fmt.Sprintf("%02x:", b)) } } func curveOIDToString(oid asn1.ObjectIdentifier) (t string, bitlen int) { switch { case oid.Equal(x509.OIDNamedCurveP224): return "secp224r1", 224 case oid.Equal(x509.OIDNamedCurveP256): return "prime256v1", 256 case oid.Equal(x509.OIDNamedCurveP384): return "secp384r1", 384 case oid.Equal(x509.OIDNamedCurveP521): return "secp521r1", 521 case oid.Equal(x509.OIDNamedCurveP192): return "secp192r1", 192 } return fmt.Sprintf("%v", oid), -1 } func publicKeyToString(_ x509.PublicKeyAlgorithm, pub interface{}) string { var buf bytes.Buffer switch pub := pub.(type) { case *rsa.PublicKey: bitlen := pub.N.BitLen() buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen)) buf.WriteString(" Modulus:\n") data := pub.N.Bytes() appendHexData(&buf, data, 15, " ") buf.WriteString("\n") buf.WriteString(fmt.Sprintf(" Exponent: %d (0x%x)", pub.E, pub.E)) case *dsa.PublicKey: buf.WriteString(" pub:\n") appendHexData(&buf, pub.Y.Bytes(), 15, " ") buf.WriteString("\n") buf.WriteString(" P:\n") appendHexData(&buf, pub.P.Bytes(), 15, " ") buf.WriteString("\n") buf.WriteString(" Q:\n") appendHexData(&buf, pub.Q.Bytes(), 15, " ") buf.WriteString("\n") buf.WriteString(" G:\n") appendHexData(&buf, pub.G.Bytes(), 15, " ") case *ecdsa.PublicKey: data := elliptic.Marshal(pub.Curve, pub.X, pub.Y) oid, ok := x509.OIDFromNamedCurve(pub.Curve) if !ok { return " " } oidname, bitlen := curveOIDToString(oid) buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen)) buf.WriteString(" pub:\n") appendHexData(&buf, data, 15, " ") buf.WriteString("\n") buf.WriteString(fmt.Sprintf(" ASN1 OID: %s", oidname)) default: buf.WriteString(fmt.Sprintf("%v", pub)) } return buf.String() } func commaAppend(buf *bytes.Buffer, s string) { if buf.Len() > 0 { buf.WriteString(", ") } buf.WriteString(s) } func keyUsageToString(k x509.KeyUsage) string { var buf bytes.Buffer if k&x509.KeyUsageDigitalSignature != 0 { commaAppend(&buf, "Digital Signature") } if k&x509.KeyUsageContentCommitment != 0 { commaAppend(&buf, "Content Commitment") } if k&x509.KeyUsageKeyEncipherment != 0 { commaAppend(&buf, "Key Encipherment") } if k&x509.KeyUsageDataEncipherment != 0 { commaAppend(&buf, "Data Encipherment") } if k&x509.KeyUsageKeyAgreement != 0 { commaAppend(&buf, "Key Agreement") } if k&x509.KeyUsageCertSign != 0 { commaAppend(&buf, "Certificate Signing") } if k&x509.KeyUsageCRLSign != 0 { commaAppend(&buf, "CRL Signing") } if k&x509.KeyUsageEncipherOnly != 0 { commaAppend(&buf, "Encipher Only") } if k&x509.KeyUsageDecipherOnly != 0 { commaAppend(&buf, "Decipher Only") } return buf.String() } func extKeyUsageToString(u x509.ExtKeyUsage) string { switch u { case x509.ExtKeyUsageAny: return "Any" case x509.ExtKeyUsageServerAuth: return "TLS Web server authentication" case x509.ExtKeyUsageClientAuth: return "TLS Web client authentication" case x509.ExtKeyUsageCodeSigning: return "Signing of executable code" case x509.ExtKeyUsageEmailProtection: return "Email protection" case x509.ExtKeyUsageIPSECEndSystem: return "IPSEC end system" case x509.ExtKeyUsageIPSECTunnel: return "IPSEC tunnel" case x509.ExtKeyUsageIPSECUser: return "IPSEC user" case x509.ExtKeyUsageTimeStamping: return "Time stamping" case x509.ExtKeyUsageOCSPSigning: return "OCSP signing" case x509.ExtKeyUsageMicrosoftServerGatedCrypto: return "Microsoft server gated cryptography" case x509.ExtKeyUsageNetscapeServerGatedCrypto: return "Netscape server gated cryptography" case x509.ExtKeyUsageCertificateTransparency: return "Certificate transparency" default: return "Unknown" } } func attributeOIDToString(oid asn1.ObjectIdentifier) string { // nolint:deadcode,unused switch { case oid.Equal(pkix.OIDCountry): return "Country" case oid.Equal(pkix.OIDOrganization): return "Organization" case oid.Equal(pkix.OIDOrganizationalUnit): return "OrganizationalUnit" case oid.Equal(pkix.OIDCommonName): return "CommonName" case oid.Equal(pkix.OIDSerialNumber): return "SerialNumber" case oid.Equal(pkix.OIDLocality): return "Locality" case oid.Equal(pkix.OIDProvince): return "Province" case oid.Equal(pkix.OIDStreetAddress): return "StreetAddress" case oid.Equal(pkix.OIDPostalCode): return "PostalCode" case oid.Equal(pkix.OIDPseudonym): return "Pseudonym" case oid.Equal(pkix.OIDTitle): return "Title" case oid.Equal(pkix.OIDDnQualifier): return "DnQualifier" case oid.Equal(pkix.OIDName): return "Name" case oid.Equal(pkix.OIDSurname): return "Surname" case oid.Equal(pkix.OIDGivenName): return "GivenName" case oid.Equal(pkix.OIDInitials): return "Initials" case oid.Equal(pkix.OIDGenerationQualifier): return "GenerationQualifier" default: return oid.String() } } // NameToString creates a string description of a pkix.Name object. func NameToString(name pkix.Name) string { var result bytes.Buffer addSingle := func(prefix, item string) { if len(item) == 0 { return } commaAppend(&result, prefix) result.WriteString(item) } addList := func(prefix string, items []string) { for _, item := range items { addSingle(prefix, item) } } addList("C=", name.Country) addList("O=", name.Organization) addList("OU=", name.OrganizationalUnit) addList("L=", name.Locality) addList("ST=", name.Province) addList("streetAddress=", name.StreetAddress) addList("postalCode=", name.PostalCode) addSingle("serialNumber=", name.SerialNumber) addSingle("CN=", name.CommonName) for _, atv := range name.Names { value, ok := atv.Value.(string) if !ok { continue } t := atv.Type // All of the defined attribute OIDs are of the form 2.5.4.N, and OIDAttribute is // the 2.5.4 prefix ('id-at' in RFC 5280). if len(t) == 4 && t[0] == pkix.OIDAttribute[0] && t[1] == pkix.OIDAttribute[1] && t[2] == pkix.OIDAttribute[2] { // OID is 'id-at N', so check the final value to figure out which attribute. switch t[3] { case pkix.OIDCommonName[3], pkix.OIDSerialNumber[3], pkix.OIDCountry[3], pkix.OIDLocality[3], pkix.OIDProvince[3], pkix.OIDStreetAddress[3], pkix.OIDOrganization[3], pkix.OIDOrganizationalUnit[3], pkix.OIDPostalCode[3]: continue // covered by explicit fields case pkix.OIDPseudonym[3]: addSingle("pseudonym=", value) continue case pkix.OIDTitle[3]: addSingle("title=", value) continue case pkix.OIDDnQualifier[3]: addSingle("dnQualifier=", value) continue case pkix.OIDName[3]: addSingle("name=", value) continue case pkix.OIDSurname[3]: addSingle("surname=", value) continue case pkix.OIDGivenName[3]: addSingle("givenName=", value) continue case pkix.OIDInitials[3]: addSingle("initials=", value) continue case pkix.OIDGenerationQualifier[3]: addSingle("generationQualifier=", value) continue } } addSingle(t.String()+"=", value) } return result.String() } // OtherNameToString creates a string description of an x509.OtherName object. func OtherNameToString(other x509.OtherName) string { return fmt.Sprintf("%v=%v", other.TypeID, hex.EncodeToString(other.Value.Bytes)) } // GeneralNamesToString creates a string description of an x509.GeneralNames object. func GeneralNamesToString(gname *x509.GeneralNames) string { var buf bytes.Buffer for _, name := range gname.DNSNames { commaAppend(&buf, "DNS:"+name) } for _, email := range gname.EmailAddresses { commaAppend(&buf, "email:"+email) } for _, name := range gname.DirectoryNames { commaAppend(&buf, "DirName:"+NameToString(name)) } for _, uri := range gname.URIs { commaAppend(&buf, "URI:"+uri) } for _, ip := range gname.IPNets { if ip.Mask == nil { commaAppend(&buf, "IP Address:"+ip.IP.String()) } else { commaAppend(&buf, "IP Address:"+ip.IP.String()+"/"+ip.Mask.String()) } } for _, id := range gname.RegisteredIDs { commaAppend(&buf, "Registered ID:"+id.String()) } for _, other := range gname.OtherNames { commaAppend(&buf, "othername:"+OtherNameToString(other)) } return buf.String() } // CertificateToString generates a string describing the given certificate. // The output roughly resembles that from openssl x509 -text. func CertificateToString(cert *x509.Certificate) string { var result bytes.Buffer result.WriteString("Certificate:\n") result.WriteString(" Data:\n") result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", cert.Version, cert.Version-1)) result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", cert.SerialNumber.Text(10), cert.SerialNumber.Text(16))) result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm)) result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(cert.Issuer))) result.WriteString(" Validity:\n") result.WriteString(fmt.Sprintf(" Not Before: %v\n", cert.NotBefore)) result.WriteString(fmt.Sprintf(" Not After : %v\n", cert.NotAfter)) result.WriteString(fmt.Sprintf(" Subject: %v\n", NameToString(cert.Subject))) result.WriteString(" Subject Public Key Info:\n") result.WriteString(fmt.Sprintf(" Public Key Algorithm: %v\n", publicKeyAlgorithmToString(cert.PublicKeyAlgorithm))) result.WriteString(fmt.Sprintf("%v\n", publicKeyToString(cert.PublicKeyAlgorithm, cert.PublicKey))) if len(cert.Extensions) > 0 { result.WriteString(" X509v3 extensions:\n") } // First display the extensions that are already cracked out showAuthKeyID(&result, cert) showSubjectKeyID(&result, cert) showKeyUsage(&result, cert) showExtendedKeyUsage(&result, cert) showBasicConstraints(&result, cert) showSubjectAltName(&result, cert) showNameConstraints(&result, cert) showCertPolicies(&result, cert) showCRLDPs(&result, cert) showAuthInfoAccess(&result, cert) showSubjectInfoAccess(&result, cert) showRPKIAddressRanges(&result, cert) showRPKIASIdentifiers(&result, cert) showCTPoison(&result, cert) showCTSCT(&result, cert) showCTLogSTHInfo(&result, cert) showUnhandledExtensions(&result, cert) showSignature(&result, cert) return result.String() } func showCritical(result *bytes.Buffer, critical bool) { if critical { result.WriteString(" critical") } result.WriteString("\n") } func showAuthKeyID(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Authority Key Identifier:") showCritical(result, critical) result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.AuthorityKeyId))) } } func showSubjectKeyID(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionSubjectKeyId, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Subject Key Identifier:") showCritical(result, critical) result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.SubjectKeyId))) } } func showKeyUsage(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionKeyUsage, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Key Usage:") showCritical(result, critical) result.WriteString(fmt.Sprintf(" %v\n", keyUsageToString(cert.KeyUsage))) } } func showExtendedKeyUsage(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionExtendedKeyUsage, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Extended Key Usage:") showCritical(result, critical) var usages bytes.Buffer for _, usage := range cert.ExtKeyUsage { commaAppend(&usages, extKeyUsageToString(usage)) } for _, oid := range cert.UnknownExtKeyUsage { commaAppend(&usages, oid.String()) } result.WriteString(fmt.Sprintf(" %v\n", usages.String())) } } func showBasicConstraints(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionBasicConstraints, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Basic Constraints:") showCritical(result, critical) result.WriteString(fmt.Sprintf(" CA:%t", cert.IsCA)) if cert.MaxPathLen > 0 || cert.MaxPathLenZero { result.WriteString(fmt.Sprintf(", pathlen:%d", cert.MaxPathLen)) } result.WriteString("\n") } } func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Subject Alternative Name:") showCritical(result, critical) var buf bytes.Buffer for _, name := range cert.DNSNames { commaAppend(&buf, "DNS:"+name) } for _, email := range cert.EmailAddresses { commaAppend(&buf, "email:"+email) } for _, ip := range cert.IPAddresses { commaAppend(&buf, "IP Address:"+ip.String()) } result.WriteString(fmt.Sprintf(" %v\n", buf.String())) // TODO(drysdale): include other name forms } } func showNameConstraints(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionNameConstraints, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Name Constraints:") showCritical(result, critical) if len(cert.PermittedDNSDomains) > 0 { result.WriteString(" Permitted:\n") var buf bytes.Buffer for _, name := range cert.PermittedDNSDomains { commaAppend(&buf, "DNS:"+name) } result.WriteString(fmt.Sprintf(" %v\n", buf.String())) } // TODO(drysdale): include other name forms } } func showCertPolicies(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionCertificatePolicies, cert.Extensions) if count > 0 { result.WriteString(" X509v3 Certificate Policies:") showCritical(result, critical) for _, oid := range cert.PolicyIdentifiers { result.WriteString(fmt.Sprintf(" Policy: %v\n", oid.String())) // TODO(drysdale): Display any qualifiers associated with the policy } } } func showCRLDPs(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionCRLDistributionPoints, cert.Extensions) if count > 0 { result.WriteString(" X509v3 CRL Distribution Points:") showCritical(result, critical) result.WriteString(" Full Name:\n") var buf bytes.Buffer for _, pt := range cert.CRLDistributionPoints { commaAppend(&buf, "URI:"+pt) } result.WriteString(fmt.Sprintf(" %v\n", buf.String())) // TODO(drysdale): Display other GeneralNames types, plus issuer/reasons/relative-name } } func showAuthInfoAccess(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, cert.Extensions) if count > 0 { result.WriteString(" Authority Information Access:") showCritical(result, critical) var issuerBuf bytes.Buffer for _, issuer := range cert.IssuingCertificateURL { commaAppend(&issuerBuf, "URI:"+issuer) } if issuerBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String())) } var ocspBuf bytes.Buffer for _, ocsp := range cert.OCSPServer { commaAppend(&ocspBuf, "URI:"+ocsp) } if ocspBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String())) } // TODO(drysdale): Display other GeneralNames types } } func showSubjectInfoAccess(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionSubjectInfoAccess, cert.Extensions) if count > 0 { result.WriteString(" Subject Information Access:") showCritical(result, critical) var tsBuf bytes.Buffer for _, ts := range cert.SubjectTimestamps { commaAppend(&tsBuf, "URI:"+ts) } if tsBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" AD Time Stamping - %v\n", tsBuf.String())) } var repoBuf bytes.Buffer for _, repo := range cert.SubjectCARepositories { commaAppend(&repoBuf, "URI:"+repo) } if repoBuf.Len() > 0 { result.WriteString(fmt.Sprintf(" CA repository - %v\n", repoBuf.String())) } } } func showAddressRange(prefix x509.IPAddressPrefix, afi uint16) string { switch afi { case x509.IPv4AddressFamilyIndicator, x509.IPv6AddressFamilyIndicator: size := 4 if afi == x509.IPv6AddressFamilyIndicator { size = 16 } ip := make([]byte, size) copy(ip, prefix.Bytes) addr := net.IPNet{IP: ip, Mask: net.CIDRMask(prefix.BitLength, 8*size)} return addr.String() default: return fmt.Sprintf("%x/%d", prefix.Bytes, prefix.BitLength) } } func showRPKIAddressRanges(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionIPPrefixList, cert.Extensions) if count > 0 { result.WriteString(" sbgp-ipAddrBlock:") showCritical(result, critical) for _, blocks := range cert.RPKIAddressRanges { afi := blocks.AFI switch afi { case x509.IPv4AddressFamilyIndicator: result.WriteString(" IPv4") case x509.IPv6AddressFamilyIndicator: result.WriteString(" IPv6") default: result.WriteString(fmt.Sprintf(" %d", afi)) } if blocks.SAFI != 0 { result.WriteString(fmt.Sprintf(" SAFI=%d", blocks.SAFI)) } result.WriteString(":") if blocks.InheritFromIssuer { result.WriteString(" inherit\n") continue } result.WriteString("\n") for _, prefix := range blocks.AddressPrefixes { result.WriteString(fmt.Sprintf(" %s\n", showAddressRange(prefix, afi))) } for _, ipRange := range blocks.AddressRanges { result.WriteString(fmt.Sprintf(" [%s, %s]\n", showAddressRange(ipRange.Min, afi), showAddressRange(ipRange.Max, afi))) } } } } func showASIDs(result *bytes.Buffer, asids *x509.ASIdentifiers, label string) { if asids == nil { return } result.WriteString(fmt.Sprintf(" %s:\n", label)) if asids.InheritFromIssuer { result.WriteString(" inherit\n") return } for _, id := range asids.ASIDs { result.WriteString(fmt.Sprintf(" %d\n", id)) } for _, idRange := range asids.ASIDRanges { result.WriteString(fmt.Sprintf(" %d-%d\n", idRange.Min, idRange.Max)) } } func showRPKIASIdentifiers(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionASList, cert.Extensions) if count > 0 { result.WriteString(" sbgp-autonomousSysNum:") showCritical(result, critical) showASIDs(result, cert.RPKIASNumbers, "Autonomous System Numbers") showASIDs(result, cert.RPKIRoutingDomainIDs, "Routing Domain Identifiers") } } func showCTPoison(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions) if count > 0 { result.WriteString(" RFC6962 Pre-Certificate Poison:") showCritical(result, critical) result.WriteString(" .....\n") } } func showCTSCT(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionCTSCT, cert.Extensions) if count > 0 { result.WriteString(" RFC6962 Certificate Transparency SCT:") showCritical(result, critical) for i, sctData := range cert.SCTList.SCTList { result.WriteString(fmt.Sprintf(" SCT [%d]:\n", i)) var sct ct.SignedCertificateTimestamp _, err := tls.Unmarshal(sctData.Val, &sct) if err != nil { appendHexData(result, sctData.Val, 16, " ") result.WriteString("\n") continue } result.WriteString(fmt.Sprintf(" Version: %d\n", sct.SCTVersion)) result.WriteString(fmt.Sprintf(" LogID: %s\n", base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:]))) result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sct.Timestamp)) result.WriteString(fmt.Sprintf(" Signature: %s\n", sct.Signature.Algorithm)) result.WriteString(" Signature:\n") appendHexData(result, sct.Signature.Signature, 16, " ") result.WriteString("\n") } } } func showCTLogSTHInfo(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509ext.OIDExtensionCTSTH, cert.Extensions) if count > 0 { result.WriteString(" Certificate Transparency STH:") showCritical(result, critical) sthInfo, err := x509ext.LogSTHInfoFromCert(cert) if err != nil { result.WriteString(" Failed to decode STH:\n") return } result.WriteString(fmt.Sprintf(" LogURL: %s\n", string(sthInfo.LogURL))) result.WriteString(fmt.Sprintf(" Version: %d\n", sthInfo.Version)) result.WriteString(fmt.Sprintf(" TreeSize: %d\n", sthInfo.TreeSize)) result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sthInfo.Timestamp)) result.WriteString(" RootHash:\n") appendHexData(result, sthInfo.SHA256RootHash[:], 16, " ") result.WriteString("\n") result.WriteString(fmt.Sprintf(" TreeHeadSignature: %s\n", sthInfo.TreeHeadSignature.Algorithm)) appendHexData(result, sthInfo.TreeHeadSignature.Signature, 16, " ") result.WriteString("\n") } } func showUnhandledExtensions(result *bytes.Buffer, cert *x509.Certificate) { for _, ext := range cert.Extensions { // Skip extensions that are already cracked out if oidAlreadyPrinted(ext.Id) { continue } result.WriteString(fmt.Sprintf(" %v:", ext.Id)) showCritical(result, ext.Critical) appendHexData(result, ext.Value, 16, " ") result.WriteString("\n") } } func showSignature(result *bytes.Buffer, cert *x509.Certificate) { result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm)) appendHexData(result, cert.Signature, 18, " ") result.WriteString("\n") } // TODO(drysdale): remove this once all standard OIDs are parsed and printed. func oidAlreadyPrinted(oid asn1.ObjectIdentifier) bool { if oid.Equal(x509.OIDExtensionSubjectKeyId) || oid.Equal(x509.OIDExtensionKeyUsage) || oid.Equal(x509.OIDExtensionExtendedKeyUsage) || oid.Equal(x509.OIDExtensionAuthorityKeyId) || oid.Equal(x509.OIDExtensionBasicConstraints) || oid.Equal(x509.OIDExtensionSubjectAltName) || oid.Equal(x509.OIDExtensionCertificatePolicies) || oid.Equal(x509.OIDExtensionNameConstraints) || oid.Equal(x509.OIDExtensionCRLDistributionPoints) || oid.Equal(x509.OIDExtensionAuthorityInfoAccess) || oid.Equal(x509.OIDExtensionSubjectInfoAccess) || oid.Equal(x509.OIDExtensionIPPrefixList) || oid.Equal(x509.OIDExtensionASList) || oid.Equal(x509.OIDExtensionCTPoison) || oid.Equal(x509.OIDExtensionCTSCT) || oid.Equal(x509ext.OIDExtensionCTSTH) { return true } return false } // CertificateFromPEM takes a certificate in PEM format and returns the // corresponding x509.Certificate object. func CertificateFromPEM(pemBytes []byte) (*x509.Certificate, error) { block, rest := pem.Decode(pemBytes) if len(rest) != 0 { return nil, errors.New("trailing data found after PEM block") } if block == nil { return nil, errors.New("PEM block is nil") } if block.Type != "CERTIFICATE" { return nil, errors.New("PEM block is not a CERTIFICATE") } return x509.ParseCertificate(block.Bytes) } // CertificatesFromPEM parses one or more certificates from the given PEM data. // The PEM certificates must be concatenated. This function can be used for // parsing PEM-formatted certificate chains, but does not verify that the // resulting chain is a valid certificate chain. func CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) { var chain []*x509.Certificate for { var block *pem.Block block, pemBytes = pem.Decode(pemBytes) if block == nil { return chain, nil } if block.Type != "CERTIFICATE" { return nil, fmt.Errorf("PEM block is not a CERTIFICATE") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, errors.New("failed to parse certificate") } chain = append(chain, cert) } } // ParseSCTsFromSCTList parses each of the SCTs contained within an SCT list. func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.SignedCertificateTimestamp, error) { var scts []*ct.SignedCertificateTimestamp for i, data := range sctList.SCTList { sct, err := ExtractSCT(&data) if err != nil { return nil, fmt.Errorf("error extracting SCT number %d: %s", i, err) } scts = append(scts, sct) } return scts, nil } // ExtractSCT deserializes an SCT from a TLS-encoded SCT. func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, error) { if sctData == nil { return nil, errors.New("SCT is nil") } var sct ct.SignedCertificateTimestamp if rest, err := tls.Unmarshal(sctData.Val, &sct); err != nil { return nil, fmt.Errorf("error parsing SCT: %s", err) } else if len(rest) > 0 { return nil, fmt.Errorf("extra data (%d bytes) after serialized SCT", len(rest)) } return &sct, nil } // MarshalSCTsIntoSCTList serializes SCTs into SCT list. func MarshalSCTsIntoSCTList(scts []*ct.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) { var sctList x509.SignedCertificateTimestampList sctList.SCTList = []x509.SerializedSCT{} for i, sct := range scts { if sct == nil { return nil, fmt.Errorf("SCT number %d is nil", i) } encd, err := tls.Marshal(*sct) if err != nil { return nil, fmt.Errorf("error serializing SCT number %d: %s", i, err) } sctData := x509.SerializedSCT{Val: encd} sctList.SCTList = append(sctList.SCTList, sctData) } return &sctList, nil } var pemCertificatePrefix = []byte("-----BEGIN CERTIFICATE") // ParseSCTsFromCertificate parses any SCTs that are embedded in the // certificate provided. The certificate bytes provided can be either DER or // PEM, provided the PEM data starts with the PEM block marker (i.e. has no // leading text). func ParseSCTsFromCertificate(certBytes []byte) ([]*ct.SignedCertificateTimestamp, error) { var cert *x509.Certificate var err error if bytes.HasPrefix(certBytes, pemCertificatePrefix) { cert, err = CertificateFromPEM(certBytes) } else { cert, err = x509.ParseCertificate(certBytes) } if err != nil { return nil, fmt.Errorf("failed to parse certificate: %s", err) } return ParseSCTsFromSCTList(&cert.SCTList) }