├── .github ├── dependabot.yml └── workflows │ ├── build-deploy-beta.yml │ ├── build-pr.yml │ ├── build-publish-release.yml │ ├── codeql.yml │ └── psl-update.yml ├── .gitignore ├── COPYRIGHT ├── LICENSE ├── README-details.md ├── README.md ├── example-app ├── Dockerfile ├── FLOW.md ├── README.md ├── db-init │ ├── dcv-init.sql │ └── pdns-starter.sql ├── docker-compose.yml ├── nginx │ ├── nginx.conf │ └── www │ │ └── .well-known │ │ └── pki-validation │ │ └── placeholder.txt ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── digicert │ │ │ └── validation │ │ │ ├── ExampleDCVApplication.java │ │ │ ├── ExampleDCVConfiguration.java │ │ │ ├── controller │ │ │ ├── DcvController.java │ │ │ ├── DcvExceptionHandler.java │ │ │ └── resource │ │ │ │ ├── request │ │ │ │ ├── DcvRequest.java │ │ │ │ ├── DcvRequestType.java │ │ │ │ └── ValidateRequest.java │ │ │ │ └── response │ │ │ │ ├── DcvRequestStatus.java │ │ │ │ ├── DomainRandomValueDetails.java │ │ │ │ ├── DomainResource.java │ │ │ │ └── ValidationStateResource.java │ │ │ ├── exceptions │ │ │ ├── DcvBaseException.java │ │ │ ├── DomainNotFoundException.java │ │ │ ├── EmailFailedException.java │ │ │ ├── InvalidDcvRequestException.java │ │ │ ├── ValidationFailedException.java │ │ │ ├── ValidationStateParsingException.java │ │ │ └── resources │ │ │ │ └── DcvErrorResponse.java │ │ │ ├── psl │ │ │ └── CustomPslOverrideSupplier.java │ │ │ ├── repository │ │ │ ├── AccountsRepository.java │ │ │ ├── DomainsRepository.java │ │ │ └── entity │ │ │ │ ├── AccountsEntity.java │ │ │ │ ├── DomainEntity.java │ │ │ │ └── DomainRandomValue.java │ │ │ └── service │ │ │ └── DcvService.java │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── com │ └── digicert │ └── validation │ ├── CustomPslOverrideSupplierTest.java │ ├── DnsRandomValueMethodIT.java │ ├── DnsTokenValueMethodIT.java │ ├── EmailMethodIT.java │ ├── FileMethodIT.java │ ├── FileTokenMethodIT.java │ ├── client │ ├── ExampleAppClient.java │ └── PdnsClient.java │ └── utils │ ├── CSRGenerator.java │ ├── DomainUtils.java │ └── FileUtils.java ├── library ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── digicert │ │ │ └── validation │ │ │ ├── DcvConfiguration.java │ │ │ ├── DcvContext.java │ │ │ ├── DcvManager.java │ │ │ ├── challenges │ │ │ ├── BasicRandomValueValidator.java │ │ │ ├── BasicRequestTokenData.java │ │ │ ├── BasicRequestTokenUtils.java │ │ │ ├── BasicRequestTokenValidator.java │ │ │ ├── ChallengeValidationResponse.java │ │ │ ├── RandomValueValidator.java │ │ │ ├── RequestTokenData.java │ │ │ └── RequestTokenValidator.java │ │ │ ├── client │ │ │ ├── dns │ │ │ │ ├── DnsClient.java │ │ │ │ └── DnsData.java │ │ │ └── file │ │ │ │ ├── CustomDnsResolver.java │ │ │ │ ├── CustomRedirectStrategy.java │ │ │ │ ├── FileClient.java │ │ │ │ └── FileClientResponse.java │ │ │ ├── common │ │ │ ├── DomainValidationEvidence.java │ │ │ └── ValidationState.java │ │ │ ├── enums │ │ │ ├── ChallengeType.java │ │ │ ├── DcvError.java │ │ │ ├── DcvMethod.java │ │ │ ├── DnsType.java │ │ │ └── LogEvents.java │ │ │ ├── exceptions │ │ │ ├── DcvException.java │ │ │ ├── InputException.java │ │ │ ├── PreparationException.java │ │ │ └── ValidationException.java │ │ │ ├── methods │ │ │ ├── dns │ │ │ │ ├── DnsValidator.java │ │ │ │ ├── prepare │ │ │ │ │ ├── DnsPreparation.java │ │ │ │ │ └── DnsPreparationResponse.java │ │ │ │ └── validate │ │ │ │ │ ├── DnsValidationHandler.java │ │ │ │ │ ├── DnsValidationRequest.java │ │ │ │ │ └── DnsValidationResponse.java │ │ │ ├── email │ │ │ │ ├── EmailValidator.java │ │ │ │ ├── prepare │ │ │ │ │ ├── EmailPreparation.java │ │ │ │ │ ├── EmailPreparationResponse.java │ │ │ │ │ ├── EmailSource.java │ │ │ │ │ ├── EmailWithRandomValue.java │ │ │ │ │ └── provider │ │ │ │ │ │ ├── ConstructedEmailProvider.java │ │ │ │ │ │ ├── DnsTxtEmailProvider.java │ │ │ │ │ │ └── EmailProvider.java │ │ │ │ └── validate │ │ │ │ │ └── EmailValidationRequest.java │ │ │ └── file │ │ │ │ ├── FileValidator.java │ │ │ │ ├── prepare │ │ │ │ ├── FilePreparationRequest.java │ │ │ │ └── FilePreparationResponse.java │ │ │ │ └── validate │ │ │ │ ├── FileValidationHandler.java │ │ │ │ ├── FileValidationRequest.java │ │ │ │ └── FileValidationResponse.java │ │ │ ├── psl │ │ │ ├── DcvDomainName.java │ │ │ ├── PslData.java │ │ │ ├── PslDataParser.java │ │ │ ├── PslDataProvider.java │ │ │ ├── PublicSuffixType.java │ │ │ ├── Trie.java │ │ │ └── TrieNode.java │ │ │ ├── random │ │ │ ├── BasicRandomValueGenerator.java │ │ │ ├── RandomValueGenerator.java │ │ │ └── RandomValueVerifier.java │ │ │ └── utils │ │ │ ├── DomainNameUtils.java │ │ │ ├── FilenameUtils.java │ │ │ ├── NoopPslOverrideSupplier.java │ │ │ ├── PslOverrideSupplier.java │ │ │ └── StateValidationUtils.java │ └── resources │ │ └── public_suffix_list.dat │ └── test │ ├── java │ └── com │ │ └── digicert │ │ └── validation │ │ ├── DcvConfigurationTest.java │ │ ├── DcvManagerTest.java │ │ ├── challenges │ │ ├── BasicRandomValueValidatorTest.java │ │ ├── BasicRequestTokenValidatorTest.java │ │ └── CSRGenerator.java │ │ ├── client │ │ ├── dns │ │ │ └── DnsClientTest.java │ │ └── file │ │ │ ├── CustomDnsResolverTest.java │ │ │ ├── CustomRedirectStrategyTest.java │ │ │ └── FileClientTest.java │ │ ├── methods │ │ ├── dns │ │ │ ├── DnsValidatorTest.java │ │ │ └── validate │ │ │ │ └── DnsValidationHandlerTest.java │ │ ├── email │ │ │ ├── EmailValidatorTest.java │ │ │ ├── prepare │ │ │ │ └── provider │ │ │ │ │ ├── ConstructedEmailProviderTest.java │ │ │ │ │ └── DnsTxtEmailProviderTest.java │ │ │ └── validate │ │ │ │ └── EmailValidationRequestTest.java │ │ └── file │ │ │ ├── FileValidatorTest.java │ │ │ └── validate │ │ │ └── FileValidationHandlerTest.java │ │ ├── psl │ │ ├── DcvDomainNameTest.java │ │ ├── PslDataParserTest.java │ │ └── PslDataProviderTest.java │ │ ├── random │ │ ├── BasicRandomValueGeneratorTest.java │ │ └── RandomValueVerifierTest.java │ │ └── utils │ │ ├── DomainNameUtilsTest.java │ │ └── StateValidationUtilsTest.java │ └── resources │ ├── logback.xml │ └── public_suffix_list_test.dat ├── pom.xml └── release-process.md /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # For Maven (Java) dependencies 5 | - package-ecosystem: "maven" 6 | directory: "/" # Location of pom.xml 7 | schedule: 8 | interval: "daily" # Frequency of update checks 9 | groups: 10 | all-dependencies: 11 | patterns: 12 | - "*" 13 | -------------------------------------------------------------------------------- /.github/workflows/build-deploy-beta.yml: -------------------------------------------------------------------------------- 1 | name: Beta Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Set up JDK 21 13 | uses: actions/setup-java@v4 14 | with: 15 | java-version: '21' 16 | distribution: 'temurin' 17 | cache: maven 18 | 19 | - name: Set up Docker Build 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Install Docker Compose 23 | run: | 24 | sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 25 | sudo chmod +x /usr/local/bin/docker-compose 26 | docker-compose --version 27 | 28 | - name: Run MVN Verify 29 | run: | 30 | mvn -P coverage -B verify 31 | 32 | deploy: 33 | runs-on: ubuntu-22.04 34 | needs: [build] 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Set up JDK 21 38 | uses: actions/setup-java@v4 39 | with: 40 | java-version: '21' 41 | distribution: 'temurin' 42 | cache: maven 43 | 44 | - name: Determine New Version 45 | id: new_version 46 | run: | 47 | # Fetch all tags to ensure they are available 48 | git fetch --tags 49 | 50 | # Get the latest tag 51 | latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` || echo "v0.0.0") 52 | 53 | echo "Latest tag: $latest_tag" 54 | 55 | # Remove 'v' prefix and split into components 56 | latest_version=${latest_tag#"v"} 57 | IFS='.' read -r major minor patch_prerelease <<< "${latest_version}" 58 | 59 | # Separate patch and prerelease if any 60 | if [[ "$patch_prerelease" == *"-"* ]]; then 61 | IFS='-' read -r patch prerelease <<< "$patch_prerelease" 62 | else 63 | patch="$patch_prerelease" 64 | prerelease="" 65 | fi 66 | 67 | echo "Current version: $latest_version" 68 | echo "Major: $major, Minor: $minor, Patch: $patch, Prerelease: $prerelease" 69 | 70 | # Determine the new version 71 | if [[ "$prerelease" == beta.* ]]; then 72 | # Latest tag is a beta tag, increment beta version 73 | beta_version=${prerelease#beta.} 74 | beta_version=$((beta_version + 1)) 75 | new_version="${major}.${minor}.${patch}-beta.${beta_version}" 76 | else 77 | # Latest tag is not a beta tag, start beta.1 78 | beta_version=1 79 | new_version="${major}.${minor}.${patch}-beta.${beta_version}" 80 | fi 81 | 82 | echo "New version: $new_version" 83 | 84 | # Set outputs 85 | echo "new_version=$new_version" >> $GITHUB_OUTPUT 86 | echo "tag_name=v$new_version" >> $GITHUB_OUTPUT 87 | 88 | - name: Configure Git 89 | run: | 90 | git config --global user.email "${{ secrets.GIT_USERNAME }}@digicert.com" 91 | git config --global user.name "${{ secrets.GIT_USERNAME }}" 92 | 93 | - name: Update Version in pom.xml 94 | run: | 95 | releaseVersion=${{ steps.new_version.outputs.new_version }} 96 | releaseVersion=${releaseVersion#v} 97 | mvn -pl library versions:set -DnewVersion=${releaseVersion} -DgenerateBackupPoms=false 98 | 99 | - name: Generate Javadocs 100 | run: mvn -pl library javadoc:javadoc 101 | 102 | - name: Create Tag 103 | run: | 104 | git tag -a v${{ steps.new_version.outputs.new_version }} -m "Tagging v${{ steps.new_version.outputs.new_version }}" 105 | git push origin v${{ steps.new_version.outputs.new_version }} 106 | 107 | - name: Configure GPG 108 | run: | 109 | echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --yes --import 110 | 111 | # Setup the central server credentials in settings.xml 112 | - name: Configure Server Credentials for Maven Central 113 | uses: whelk-io/maven-settings-xml-action@v20 114 | with: 115 | servers: > 116 | [ 117 | { 118 | "id": "central", 119 | "username": "${{ secrets.MVN_CENTRAL_USERNAME }}", 120 | "password": "${{ secrets.MVN_CENTRAL_USER_TOKEN }}" 121 | } 122 | ] 123 | 124 | - name: Deploy to Maven Central (no publish) 125 | run: | 126 | mvn -pl library javadoc:jar 127 | mvn -pl library deploy -P deploy-to-maven-central-beta -DskipTests -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} --batch-mode -------------------------------------------------------------------------------- /.github/workflows/build-pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java Build and Test with Maven 10 | 11 | on: 12 | pull_request: 13 | branches: [ "master" ] 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-22.04 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up JDK 21 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: '21' 26 | distribution: 'temurin' 27 | cache: maven 28 | 29 | - name: Set up Docker Build 30 | uses: docker/setup-buildx-action@v2 31 | 32 | - name: Install Docker Compose 33 | run: | 34 | sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 35 | sudo chmod +x /usr/local/bin/docker-compose 36 | docker-compose --version 37 | 38 | - name: Run MVN Verify 39 | run: | 40 | mvn -P coverage -B verify --file pom.xml 41 | -------------------------------------------------------------------------------- /.github/workflows/build-publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Maven Central 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Set up JDK 21 13 | uses: actions/setup-java@v4 14 | with: 15 | java-version: '21' 16 | distribution: 'temurin' 17 | cache: maven 18 | 19 | - name: Set up Docker Build 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Install Docker Compose 23 | run: | 24 | sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 25 | sudo chmod +x /usr/local/bin/docker-compose 26 | docker-compose --version 27 | 28 | - name: Run MVN Verify 29 | run: | 30 | mvn -P coverage -B verify 31 | 32 | publish: 33 | runs-on: ubuntu-22.04 34 | needs: [build] 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Set up JDK 21 38 | uses: actions/setup-java@v4 39 | with: 40 | java-version: '21' 41 | distribution: 'temurin' 42 | cache: maven 43 | - name: Configure Git 44 | run: | 45 | git config --global user.email "${{ secrets.GIT_USERNAME }}@digicert.com" 46 | git config --global user.name "${{ secrets.GIT_USERNAME }}" 47 | 48 | - name: Update Version in pom.xml 49 | run: | 50 | releaseVersion=$(git tag --points-at HEAD | grep 'release' | sort -V | tail -n 1) 51 | releaseVersion=${releaseVersion#v} 52 | mvn -pl library versions:set -DnewVersion=${releaseVersion} -DgenerateBackupPoms=false 53 | 54 | - name: Generate Javadocs 55 | run: mvn -pl library javadoc:javadoc 56 | 57 | - name: Configure GPG 58 | run: | 59 | echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --yes --import 60 | 61 | # Setup the central server credentials in settings.xml 62 | - name: Configure Server Credentials for Maven Central 63 | uses: whelk-io/maven-settings-xml-action@v20 64 | with: 65 | servers: > 66 | [ 67 | { 68 | "id": "central", 69 | "username": "${{ secrets.MVN_CENTRAL_USERNAME }}", 70 | "password": "${{ secrets.MVN_CENTRAL_USER_TOKEN }}" 71 | } 72 | ] 73 | 74 | - name: Deploy to Maven Central 75 | run: | 76 | mvn -pl library javadoc:jar 77 | mvn -pl library deploy -P deploy-to-maven-central-beta -DskipTests -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} -DautoReleaseAfterClose=false --batch-mode 78 | 79 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | schedule: 20 | - cron: '31 19 * * 4' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: java-kotlin 47 | build-mode: none # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too. 48 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 49 | # Use `c-cpp` to analyze code written in C, C++ or both 50 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 51 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 52 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 53 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 54 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 55 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | - name: Setup Java 61 | uses: actions/setup-java@v3 62 | with: 63 | distribution: 'temurin' 64 | java-version: '21' 65 | 66 | # Initializes the CodeQL tools for scanning. 67 | - name: Initialize CodeQL 68 | uses: github/codeql-action/init@v3 69 | with: 70 | languages: ${{ matrix.language }} 71 | build-mode: ${{ matrix.build-mode }} 72 | # If you wish to specify custom queries, you can do so here or in a config file. 73 | # By default, queries listed here will override any specified in a config file. 74 | # Prefix the list here with "+" to use these queries and those in the config file. 75 | 76 | # For more 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 77 | queries: security-extended,security-and-quality 78 | 79 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 80 | # If this step fails, then you should remove it and run the build manually (see below) 81 | - name: Autobuild 82 | uses: github/codeql-action/autobuild@v2 83 | 84 | # If the analyze step fails for one of the languages you are analyzing with 85 | # "We were unable to automatically build your code", modify the matrix above 86 | # to set the build mode to "manual" for that language. Then modify this step 87 | # to build your code. 88 | # ℹ️ Command-line programs to run using the OS shell. 89 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 90 | #- if: matrix.build-mode == 'manual' 91 | # shell: bash 92 | # run: | 93 | # echo 'If you are using a "manual" build mode for one or more of the' \ 94 | # 'languages you are analyzing, replace this with the commands to build' \ 95 | # 'your code, for example:' 96 | # echo ' make bootstrap' 97 | # echo ' make release' 98 | # exit 1 99 | 100 | - name: Perform CodeQL Analysis 101 | uses: github/codeql-action/analyze@v3 102 | with: 103 | category: "/language:${{matrix.language}}" 104 | -------------------------------------------------------------------------------- /.github/workflows/psl-update.yml: -------------------------------------------------------------------------------- 1 | name: Periodic PSL Update 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 1 * *' # Run at midnight on the first of every month 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update_psl: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Checkout Code 13 | uses: actions/checkout@v3 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '21' 19 | distribution: 'temurin' 20 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 21 | settings-path: ${{ github.workspace }} # location for the settings.xml file 22 | 23 | - name: Download PSL 24 | run: | 25 | curl -o ./library/src/main/resources/public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat 26 | 27 | - name: Check for Changes 28 | id: check_changes 29 | run: | 30 | if git diff --exit-code ./library/src/main/resources/public_suffix_list.dat; then 31 | echo "No changes in PSL data." 32 | echo "changes_detected=false" >> $GITHUB_ENV 33 | else 34 | echo "Changes detected in PSL data." 35 | echo "changes_detected=true" >> $GITHUB_ENV 36 | fi 37 | 38 | - name: Push Changes to Branch 39 | if: ${{ env.changes_detected == 'true' }} 40 | run: | 41 | git config --global user.email "${{ secrets.GIT_USERNAME }}@digicert.com" 42 | git config --global user.name "${{ secrets.GIT_USERNAME }}" 43 | git add ./library/src/main/resources/public_suffix_list.dat 44 | git commit -m "Update PSL" 45 | git push origin HEAD:psl-update 46 | 47 | - name: Create Pull Request 48 | if: ${{ env.changes_detected == 'true' }} 49 | uses: peter-evans/create-pull-request@v3 50 | with: 51 | branch: psl-update 52 | title: "Update PSL" 53 | body: "This PR updates the PSL to the latest version." 54 | base: master 55 | 56 | 57 | slack_notification: 58 | needs: [update_psl] 59 | if: always() 60 | runs-on: self-hosted 61 | steps: 62 | - name: Checkout code 63 | uses: actions/checkout@v3 64 | 65 | - name: Run Slack Notification 66 | uses: rohammosalli/slack-action@master 67 | env: 68 | SLACK_BOT_TOKEN: ${{ secrets.PKI_SLACK_TOKEN }} 69 | SLACK_CHANNEL: "dvm-alerts" 70 | GITHUB_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} 71 | GITHUB_RUN_ID: ${{ github.run_id }} 72 | REPO_OWNER: ${{ github.repository_owner }} 73 | REPO_NAME: ${{ github.event.repository.name }} 74 | RUN_ID: ${{ github.run_id }} 75 | SEND_SUCCESS_MESSAGE: "false" 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | /example-app/.sql-data/ 7 | /example-app/nginx/www/.well-known/pki-validation/* 8 | 9 | ### IntelliJ IDEA ### 10 | .idea/* 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store 39 | 40 | ### github conf files ### 41 | settings.xml 42 | toolchains.xml -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | COPYRIGHT AND LICENSE INFORMATION 2 | ================================= 3 | 4 | Copyright (c) 2024, Digicert, Inc. 5 | All rights reserved. 6 | 7 | This library is licensed under the MIT License (MIT) - see the LICENSE file for details. 8 | 9 | Contributors 10 | ------------ 11 | Digicert Engineering Team 12 | 13 | Third-Party Libraries 14 | --------------------- 15 | This project may include third-party libraries, each licensed under their respective terms. Please refer to the LICENSE or NOTICE file for more information on third-party licenses. 16 | 17 | Attribution 18 | ----------- 19 | If using this library in an open-source or proprietary project, attribution to Digicert, Inc., is appreciated, but not required. 20 | 21 | For more information, please visit: 22 | https://github.com/digicert/domain-control-validation -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Digicert Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /example-app/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official OpenJDK 21 slim image 2 | FROM openjdk:21-jdk-slim 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Copy the application JAR file to the container 8 | COPY target/example-app-1.0-SNAPSHOT.jar /app/ 9 | 10 | # Install curl (if your application depends on it) 11 | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* 12 | 13 | # Download the public suffix list (if needed by your application) 14 | RUN mkdir -p /usr/local/share && \ 15 | curl -o /usr/local/share/public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat 16 | 17 | # Expose the port your application listens on 18 | EXPOSE 8080 19 | 20 | # Run the JAR file 21 | CMD ["java", "-jar", "/app/example-app-1.0-SNAPSHOT.jar"] 22 | -------------------------------------------------------------------------------- /example-app/README.md: -------------------------------------------------------------------------------- 1 | # example-app 2 | The example-app is a reference implementation of the domain-control-validation library. This can be used as an example 3 | of how to call the API is domain-control-validation. This example-app includes a REST API implementation that uses the 4 | domain-control-validation and full flow integration tests of the BRs implemented in domain-control validation. 5 | 6 | > The example-app is not a production ready app. It is a reference implementation and should be used as such. 7 | 8 | 9 | ## Running the example-app 10 | The example-app can be run using the following command: 11 | ```mvn spring-boot:run``` 12 | 13 | The example-app will start on port 8080. The API can be accessed at `http://localhost:8080/validate-domain-control` 14 | 15 | ## API 16 | The example-app exposes a REST API that can be used to validate domain control for a domain. The API can be accessed at 17 | `http://localhost:8080/validate-domain-control` 18 | 19 | 20 | ## Integration Tests 21 | The integration tests are located in the `src/test/java/com/digicert/validation` directory. These have a 'IT' suffix. 22 | The integration tests rely on various subsystems such as mysql, powerdns to be running. A docker-compose file is used to 23 | start these services and an instance of the example-app. The integration tests will run against the example-app instance. 24 | 25 | The Maven Failsafe Plugin is used to run the integration tests (code snippet from the pom.xml for the example-app): 26 | ``` 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-failsafe-plugin 31 | ``` 32 | 33 | ### Running Integration Tests 34 | These can be run using the maven test goal. 35 | ```mvn clean verify``` 36 | 37 | ### Debugging or Executing the Integration Tests in an IDE 38 | 1. first start the docker-compose file 39 | ``` docker-compose -f ./example-app/docker-compose.yml up -d``` 40 | 2. Execute or debug the integration tests normally in your IDE. -------------------------------------------------------------------------------- /example-app/db-init/dcv-init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS dcv; 2 | 3 | CREATE USER IF NOT EXISTS 'dcv_user'@'%' IDENTIFIED BY 'dcv_password'; 4 | GRANT ALL PRIVILEGES ON *.* TO 'dcv_user'@'%'; 5 | 6 | FLUSH PRIVILEGES; 7 | -------------------------------------------------------------------------------- /example-app/db-init/pdns-starter.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS pdns; 2 | 3 | CREATE USER IF NOT EXISTS 'pdns'@'%' IDENTIFIED BY 'pdns'; 4 | GRANT ALL PRIVILEGES ON pdns.* TO 'pdns'@'%'; 5 | FLUSH PRIVILEGES; 6 | -------------------------------------------------------------------------------- /example-app/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | mysqldb: 4 | image: mysql:latest 5 | container_name: mysqldb 6 | environment: 7 | MYSQL_ROOT_PASSWORD: root_password 8 | MYSQL_DATABASE: example_app 9 | MYSQL_USER: example_user 10 | MYSQL_PASSWORD: example_password 11 | ports: 12 | - "3306:3306" 13 | volumes: 14 | - ./db-init:/docker-entrypoint-initdb.d 15 | 16 | nginx: 17 | image: nginx:latest 18 | ports: 19 | - "80:80" 20 | volumes: 21 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro 22 | - ./nginx/www/:/var/www/:ro 23 | 24 | dnsService: 25 | image: pschiffe/pdns-mysql 26 | container_name: dnsService 27 | hostname: pdns.localhost.com 28 | ports: 29 | - "10000:53/tcp" 30 | - "10000:53/udp" 31 | - "8081:8081" 32 | depends_on: 33 | - mysqldb 34 | environment: 35 | - TZ=America/Denver 36 | - PDNS_gmysql_host=mysqldb 37 | - PDNS_gmysql_port=3306 38 | - PDNS_gmysql_user=pdns 39 | - PDNS_gmysql_password=pdns 40 | - PDNS_gmysql_dbname=pdns 41 | # - PDNS_master=yes 42 | - PDNS_api=yes 43 | - PDNS_api_key=secret 44 | - PDNS_webserver=yes 45 | - PDNS_webserver_address=0.0.0.0 46 | - PDNS_webserver_allow_from=172.0.0.0/8,127.0.0.0/8,192.0.0.0/8,10.0.0.0/8 47 | - PDNS_webserver_password=nothing 48 | - PDNS_version_string=anonymous 49 | - PDNS_default_ttl=1500 50 | - PDNS_allow_axfr_ips=172.0.0.0/8,127.0.0.0/8,192.0.0.0/8,10.0.0.0/8 51 | - PDNS_allow_dnsupdate_from=172.0.0.0/8,127.0.0.0/8,192.0.0.0/8,10.0.0.0/8 52 | - PDNS_only_notify=0.0.0.0 53 | -------------------------------------------------------------------------------- /example-app/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | server { 9 | listen 80; 10 | root /var/www; 11 | 12 | location / { 13 | try_files $uri $uri/ =404; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /example-app/nginx/www/.well-known/pki-validation/placeholder.txt: -------------------------------------------------------------------------------- 1 | _sbdz2g3h23dw8abfqklh33et4e4t1c1 -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/ExampleDCVApplication.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ExampleDCVApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ExampleDCVApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/ExampleDCVConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import com.digicert.validation.psl.CustomPslOverrideSupplier; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.util.List; 8 | 9 | @Configuration 10 | public class ExampleDCVConfiguration { 11 | 12 | @Bean 13 | DcvManager dcvManager(CustomPslOverrideSupplier customPslOverrideSupplier) { 14 | DcvConfiguration dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 15 | // set the DNS servers to use for DNS lookups 16 | .dnsServers(List.of("localhost:10000")) 17 | // Configured to match the value configured in powerdns docker container 18 | .pslOverrideSupplier(customPslOverrideSupplier) 19 | .build(); 20 | 21 | return new DcvManager.Builder() 22 | .withDcvConfiguration(dcvConfiguration) 23 | .build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/DcvController.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller; 2 | 3 | import com.digicert.validation.controller.resource.request.DcvRequest; 4 | import com.digicert.validation.controller.resource.response.DomainResource; 5 | import com.digicert.validation.controller.resource.request.ValidateRequest; 6 | import com.digicert.validation.exceptions.DcvBaseException; 7 | import com.digicert.validation.service.DcvService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | @RestController 14 | @Slf4j 15 | public class DcvController { 16 | 17 | private final DcvService dcvService; 18 | 19 | @Autowired 20 | public DcvController(DcvService dcvService) { 21 | this.dcvService = dcvService; 22 | } 23 | 24 | @PostMapping("/domains") 25 | @ResponseStatus(HttpStatus.CREATED) 26 | public DomainResource submitDomain(@RequestBody DcvRequest dcvRequest) throws DcvBaseException { 27 | return dcvService.submitDomain(dcvRequest); 28 | } 29 | 30 | @GetMapping("/domains/{domainId}") 31 | @ResponseStatus(HttpStatus.OK) 32 | public DomainResource getDomains(@PathVariable("domainId") Long domainId) throws DcvBaseException{ 33 | return new DomainResource(dcvService.getDomainEntity(domainId)); 34 | } 35 | 36 | @PutMapping("/domains/{domainId}") 37 | public void validateDomain(@PathVariable("domainId") Long domainId, 38 | @RequestBody ValidateRequest dcvRequest) throws DcvBaseException { 39 | dcvService.validateDomain(domainId, dcvRequest); 40 | } 41 | 42 | @PostMapping("/accounts/{accountId}/tokens") 43 | @ResponseStatus(HttpStatus.CREATED) 44 | public void createAccountToken(@PathVariable("accountId") long accountId, @RequestParam("tokenKey") String tokenKey) { 45 | dcvService.createTokenForAccount(accountId, tokenKey); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/DcvExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller; 2 | 3 | import com.digicert.validation.exceptions.DcvBaseException; 4 | import com.digicert.validation.exceptions.resources.DcvErrorResponse; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | 9 | @ControllerAdvice 10 | public class DcvExceptionHandler { 11 | @ExceptionHandler(DcvBaseException.class) 12 | public ResponseEntity handleDomainNotFoundException(DcvBaseException ex) { 13 | DcvErrorResponse errorResponse = new DcvErrorResponse(ex.getMessage()); 14 | return new ResponseEntity<>(errorResponse, ex.getHttpStatusCode()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/request/DcvRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.request; 2 | 3 | public record DcvRequest(String domain, 4 | String filename, 5 | long accountId, 6 | DcvRequestType dcvRequestType) { 7 | public DcvRequest(String domain, long accountId, DcvRequestType dcvRequestType) { 8 | this(domain, null, accountId, dcvRequestType); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/request/DcvRequestType.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.request; 2 | 3 | public enum DcvRequestType { 4 | EMAIL_DNS_TXT, 5 | EMAIL_CONSTRUCTED, 6 | DNS_TXT, 7 | DNS_CNAME, 8 | DNS_TXT_TOKEN, 9 | FILE_VALIDATION, 10 | FILE_VALIDATION_TOKEN 11 | } 12 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/request/ValidateRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Builder 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class ValidateRequest { 11 | public String domain; 12 | public String filename; 13 | public String emailAddress; 14 | public String randomValue; 15 | 16 | public String tokenValue; 17 | 18 | public DcvRequestType dcvRequestType; 19 | } 20 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/response/DcvRequestStatus.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.response; 2 | 3 | public enum DcvRequestStatus { 4 | PENDING, 5 | VALID, 6 | FAILED 7 | } 8 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/response/DomainRandomValueDetails.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.response; 2 | 3 | import com.digicert.validation.repository.entity.DomainRandomValue; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | public class DomainRandomValueDetails { 12 | private String randomValue; 13 | private String email; 14 | 15 | public DomainRandomValueDetails(DomainRandomValue domainRandomValue) { 16 | this.randomValue = domainRandomValue.randomValue; 17 | this.email = domainRandomValue.email; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/response/DomainResource.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.response; 2 | 3 | import com.digicert.validation.controller.resource.request.DcvRequestType; 4 | import com.digicert.validation.repository.entity.DomainEntity; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Getter 15 | public class DomainResource { 16 | private long id; 17 | private String domainName; 18 | private long accountId; 19 | private DcvRequestType dcvType; 20 | private DcvRequestStatus status; 21 | private List randomValueDetails = new ArrayList<>(); 22 | 23 | public DomainResource(DomainEntity domain) { 24 | this.id = domain.getId(); 25 | this.domainName = domain.getDomainName(); 26 | this.accountId = domain.getAccountId(); 27 | this.dcvType = DcvRequestType.valueOf(domain.getDcvType()); 28 | this.status = DcvRequestStatus.valueOf(domain.getStatus()); 29 | randomValueDetails = domain.getDomainRandomValues().stream() 30 | .map(DomainRandomValueDetails::new) 31 | .toList(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/controller/resource/response/ValidationStateResource.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.controller.resource.response; 2 | 3 | import com.digicert.validation.enums.DcvMethod; 4 | import com.digicert.validation.common.ValidationState; 5 | 6 | import java.time.Instant; 7 | 8 | public class ValidationStateResource { 9 | String domain; 10 | Instant prepareTime; 11 | DcvMethod dcvMethod; 12 | public ValidationState toValidationState() { 13 | return new ValidationState(domain, prepareTime, dcvMethod); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/DcvBaseException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import lombok.Getter; 4 | import org.springframework.http.HttpStatus; 5 | 6 | /** 7 | * Base exception for all exceptions thrown by the example-app. 8 | */ 9 | @Getter 10 | public class DcvBaseException extends Exception { 11 | 12 | /** 13 | * The HTTP status code associated with the exception. 14 | */ 15 | private final HttpStatus httpStatusCode; 16 | 17 | public DcvBaseException(String message, HttpStatus httpStatusCode) { 18 | super(message); 19 | this.httpStatusCode = httpStatusCode; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/DomainNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class DomainNotFoundException extends DcvBaseException { 6 | 7 | public DomainNotFoundException(String message) { 8 | super(message, HttpStatus.NOT_FOUND); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/EmailFailedException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class EmailFailedException extends DcvBaseException { 6 | public EmailFailedException(String message) { 7 | super(message, HttpStatus.BAD_REQUEST); 8 | } 9 | } -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/InvalidDcvRequestException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class InvalidDcvRequestException extends DcvBaseException { 6 | 7 | public InvalidDcvRequestException(String message) { 8 | super(message, HttpStatus.BAD_REQUEST); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/ValidationFailedException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class ValidationFailedException extends DcvBaseException { 6 | 7 | public ValidationFailedException(String message) { 8 | super(message, HttpStatus.BAD_REQUEST); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/ValidationStateParsingException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class ValidationStateParsingException extends DcvBaseException { 6 | 7 | public ValidationStateParsingException(String message) { 8 | super(message, HttpStatus.INTERNAL_SERVER_ERROR); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/exceptions/resources/DcvErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions.resources; 2 | 3 | public record DcvErrorResponse(String message) { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/psl/CustomPslOverrideSupplier.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.digicert.validation.utils.PslOverrideSupplier; 9 | 10 | @Service 11 | public class CustomPslOverrideSupplier implements PslOverrideSupplier { 12 | 13 | // Map of domain suffixes to override 14 | private final Map overridePublicSuffixes = Map.of("ac.gov.br","gov.br"); 15 | 16 | @Override 17 | public Optional getPublicSuffixOverride(String domain) { 18 | 19 | 20 | // first check if the domain is in the override list 21 | return overridePublicSuffixes.entrySet().stream() 22 | .filter(entry -> domain.endsWith(entry.getKey())) 23 | .filter(entry -> domain.equals(entry.getKey()) || 24 | domain.endsWith("." + entry.getKey())) 25 | .map(Map.Entry::getValue) 26 | .findFirst(); 27 | } 28 | } -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/repository/AccountsRepository.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.repository; 2 | 3 | import com.digicert.validation.repository.entity.AccountsEntity; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface AccountsRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/repository/DomainsRepository.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.repository; 2 | 3 | import com.digicert.validation.repository.entity.DomainEntity; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface DomainsRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/repository/entity/AccountsEntity.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.Id; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | @Entity 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Getter 15 | @Setter 16 | public class AccountsEntity { 17 | @Id 18 | @Column(name = "account_id") 19 | public long accountId; 20 | 21 | @Column(name = "token_key") 22 | public String tokenKey; 23 | } 24 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/repository/entity/DomainEntity.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.repository.entity; 2 | 3 | import com.digicert.validation.controller.resource.request.DcvRequest; 4 | import com.digicert.validation.controller.resource.response.DcvRequestStatus; 5 | import jakarta.persistence.*; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | import java.util.List; 11 | 12 | @Entity 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | public class DomainEntity { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.AUTO) 19 | public long id; 20 | 21 | @Column(name = "domain_name") 22 | public String domainName; 23 | 24 | @Column(name = "account_id") 25 | public long accountId; 26 | 27 | @Column(name = "dcv_type") 28 | public String dcvType; 29 | 30 | @Column(name = "status") 31 | public String status; 32 | 33 | @OneToMany(mappedBy = "domain", cascade = CascadeType.ALL, orphanRemoval = true) 34 | public List domainRandomValues; 35 | 36 | @Column(name = "validation_state") 37 | public String validationState; 38 | 39 | public DomainEntity(DcvRequest request) { 40 | this.domainName = request.domain(); 41 | this.accountId = request.accountId(); 42 | this.dcvType = request.dcvRequestType().name(); 43 | this.status = DcvRequestStatus.PENDING.name(); 44 | this.domainRandomValues = List.of(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example-app/src/main/java/com/digicert/validation/repository/entity/DomainRandomValue.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.repository.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.NoArgsConstructor; 5 | 6 | @Entity 7 | @NoArgsConstructor 8 | public class DomainRandomValue { 9 | @Id 10 | @GeneratedValue(strategy = GenerationType.AUTO) 11 | public long id; 12 | 13 | @Column(name = "random_value") 14 | public String randomValue; 15 | 16 | @Column(name = "email") 17 | public String email; 18 | 19 | @ManyToOne 20 | @JoinColumn(name = "domain_id", referencedColumnName = "id") 21 | public DomainEntity domain; 22 | 23 | public DomainRandomValue(String randomValue, String email, DomainEntity domain) { 24 | this.randomValue = randomValue; 25 | this.email = email; 26 | this.domain = domain; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example-app/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://localhost:3306/dcv 4 | driverClassName: com.mysql.cj.jdbc.Driver 5 | username: dcv_user 6 | password: dcv_password 7 | hikari: 8 | initializationFailTimeout: -1 # Set to -1 to disable fail-fast on startup 9 | 10 | jpa: 11 | database-platform: org.hibernate.dialect.MySQLDialect 12 | hibernate: 13 | ddl-auto: update -------------------------------------------------------------------------------- /example-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/CustomPslOverrideSupplierTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.Arguments; 11 | import org.junit.jupiter.params.provider.MethodSource; 12 | 13 | import com.digicert.validation.psl.CustomPslOverrideSupplier; 14 | 15 | class CustomPslOverrideSupplierTest { 16 | 17 | private CustomPslOverrideSupplier customPslOverrideSupplier; 18 | 19 | @BeforeEach 20 | void setUp() { 21 | customPslOverrideSupplier = new CustomPslOverrideSupplier(); 22 | } 23 | 24 | @ParameterizedTest 25 | @MethodSource("providePublicSuffixOverrideTestCases") 26 | void testGetPublicSuffixOverride(String domain, boolean expectedPresent, String expectedSuffix) { 27 | // Test cases for public suffix overrides 28 | Optional override = customPslOverrideSupplier.getPublicSuffixOverride(domain); 29 | assertEquals(expectedPresent, override.isPresent(), "Override presence mismatch for " + domain); 30 | if (expectedPresent) { 31 | assertEquals(expectedSuffix, override.orElseThrow(IllegalArgumentException::new), "Override mismatch for " + domain); 32 | } 33 | } 34 | 35 | private static Stream providePublicSuffixOverrideTestCases() { 36 | return Stream.of( 37 | Arguments.of("subdomain.example.ac.gov.br", true, "gov.br"), 38 | Arguments.of("ac.gov.br", true, "gov.br"), 39 | Arguments.of("example.gov.br", false, null), 40 | Arguments.of("example.com", false, null) 41 | ); 42 | } 43 | } -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/DnsTokenValueMethodIT.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import com.digicert.validation.challenges.BasicRequestTokenData; 4 | import com.digicert.validation.client.ExampleAppClient; 5 | import com.digicert.validation.client.PdnsClient; 6 | import com.digicert.validation.controller.resource.request.DcvRequest; 7 | import com.digicert.validation.controller.resource.request.DcvRequestType; 8 | import com.digicert.validation.controller.resource.request.ValidateRequest; 9 | import com.digicert.validation.controller.resource.response.DcvRequestStatus; 10 | import com.digicert.validation.controller.resource.response.DomainResource; 11 | import com.digicert.validation.challenges.BasicRequestTokenUtils; 12 | import com.digicert.validation.utils.CSRGenerator; 13 | import com.digicert.validation.utils.DomainUtils; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | 19 | import java.time.Instant; 20 | import java.time.ZoneId; 21 | import java.time.ZonedDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | 24 | import static org.junit.jupiter.api.Assertions.*; 25 | 26 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 27 | @AutoConfigureWebTestClient 28 | class DnsTokenValueMethodIT { 29 | 30 | @Autowired 31 | private ExampleAppClient exampleAppClient; 32 | private final PdnsClient pdnsClient = new PdnsClient(); 33 | private final Long defaultAccountId = 1234L; 34 | private final CSRGenerator csrGenerator = new CSRGenerator(); 35 | 36 | @Test 37 | void verifyDnsTokenTxt_HappyPath() throws Exception { 38 | String domainName = DomainUtils.getRandomDomainName(2, "com"); 39 | 40 | String hashingKey = "token-key"; 41 | // Set the token key for the account 42 | exampleAppClient.submitAccountTokenKey(defaultAccountId, hashingKey); 43 | 44 | String hashingValue = csrGenerator.generateCSR(domainName); 45 | ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("UTC")); 46 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); 47 | String formattedDate = zonedDateTime.format(formatter); 48 | 49 | BasicRequestTokenUtils basicRequestTokenUtils = new BasicRequestTokenUtils(); 50 | String dnsTxtTokenValue = basicRequestTokenUtils.generateRequestToken(new BasicRequestTokenData(hashingKey, hashingValue), formattedDate).orElseThrow(); 51 | 52 | DcvRequest dcvRequest = createDcvRequest(domainName); 53 | DomainResource createdDomain = submitDnsDomain(dcvRequest); 54 | assertCreatedDomain(dcvRequest, createdDomain); 55 | 56 | // Create PDNS DNS Text Entry for the domain with the random value 57 | pdnsClient.addRandomValueToRecord(domainName, dnsTxtTokenValue, PdnsClient.PdnsRecordType.TXT); 58 | 59 | // Validate the domain 60 | ValidateRequest validateRequest = getValidateRequest(dcvRequest.domain(), hashingValue); 61 | exampleAppClient.validateDomain(validateRequest, createdDomain.getId()); 62 | 63 | // Get and assert that the domain is now valid 64 | DomainResource verifiedDomain = getDomainResource(createdDomain.getId()); 65 | assertEquals(DcvRequestStatus.VALID, verifiedDomain.getStatus()); 66 | } 67 | 68 | private DcvRequest createDcvRequest(String domainName) { 69 | return new DcvRequest(domainName, defaultAccountId, DcvRequestType.DNS_TXT_TOKEN); 70 | } 71 | 72 | private static ValidateRequest getValidateRequest(String domain, 73 | String tokenValue) { 74 | return ValidateRequest.builder() 75 | .dcvRequestType(DcvRequestType.DNS_TXT_TOKEN) 76 | .domain(domain) 77 | .tokenValue(tokenValue) 78 | .build(); 79 | } 80 | 81 | private DomainResource submitDnsDomain(DcvRequest dcvRequest) { 82 | return exampleAppClient.submitDnsDomain(dcvRequest); 83 | } 84 | 85 | private DomainResource getDomainResource(long domainId) { 86 | return exampleAppClient.getDomainResource(domainId); 87 | } 88 | 89 | private void assertCreatedDomain(DcvRequest dcvRequest, DomainResource createdDomain) { 90 | assertNotNull(createdDomain); 91 | assertNotEquals(0, createdDomain.getId()); 92 | assertEquals(dcvRequest.domain(), createdDomain.getDomainName()); 93 | assertEquals(dcvRequest.accountId(), createdDomain.getAccountId()); 94 | assertEquals(dcvRequest.dcvRequestType(), createdDomain.getDcvType()); 95 | assertEquals(DcvRequestStatus.PENDING, createdDomain.getStatus()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/client/ExampleAppClient.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client; 2 | 3 | import com.digicert.validation.controller.resource.request.DcvRequest; 4 | import com.digicert.validation.controller.resource.request.ValidateRequest; 5 | import com.digicert.validation.controller.resource.response.DcvRequestStatus; 6 | import com.digicert.validation.controller.resource.response.DomainResource; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.web.client.TestRestTemplate; 9 | import org.springframework.http.HttpEntity; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.stereotype.Service; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | @Service 17 | public class ExampleAppClient { 18 | 19 | @Autowired 20 | private TestRestTemplate testRestTemplate; 21 | 22 | public DomainResource submitDnsDomain(DcvRequest dcvRequest) { 23 | ResponseEntity createResponse = testRestTemplate.exchange("/domains", HttpMethod.POST, new HttpEntity<>(dcvRequest), DomainResource.class); 24 | 25 | assertTrue(createResponse.getStatusCode().is2xxSuccessful()); 26 | DomainResource domainResource = createResponse.getBody(); 27 | 28 | assertNotNull(domainResource); 29 | assertNotEquals(0, domainResource.getId()); 30 | return domainResource; 31 | } 32 | 33 | public boolean submitDnsDomainExpectingFail(DcvRequest dcvRequest) { 34 | ResponseEntity createResponse = testRestTemplate.exchange("/domains", HttpMethod.POST, new HttpEntity<>(dcvRequest), DomainResource.class); 35 | 36 | assertFalse(createResponse.getStatusCode().is2xxSuccessful()); 37 | return true; 38 | } 39 | 40 | public void validateDomain(ValidateRequest validateRequest, long domainId) { 41 | testRestTemplate.put("/domains/{domainId}", validateRequest, domainId); 42 | 43 | // Verify that the domain is showing as valid 44 | DomainResource domainResource = getDomainResource(domainId); 45 | assertEquals(DcvRequestStatus.VALID, domainResource.getStatus()); 46 | } 47 | 48 | public DomainResource getDomainResource(long domainId) { 49 | ResponseEntity getResponse = testRestTemplate.getForEntity("/domains/" + domainId, DomainResource.class); 50 | 51 | assertTrue(getResponse.getStatusCode().is2xxSuccessful()); 52 | return getResponse.getBody(); 53 | } 54 | 55 | public void submitAccountTokenKey(long accountId, String tokenKey) { 56 | testRestTemplate.postForEntity("/accounts/{accountId}/tokens?tokenKey={tokenKey}", null, Void.class, accountId, tokenKey); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/utils/CSRGenerator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.bouncycastle.asn1.x500.X500Name; 5 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 6 | import org.bouncycastle.openssl.jcajce.JcaPEMWriter; 7 | import org.bouncycastle.operator.ContentSigner; 8 | import org.bouncycastle.operator.OperatorCreationException; 9 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 10 | import org.bouncycastle.pkcs.PKCS10CertificationRequest; 11 | import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; 12 | import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; 13 | 14 | import java.io.IOException; 15 | import java.io.StringWriter; 16 | import java.security.KeyPair; 17 | import java.security.KeyPairGenerator; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.security.Security; 20 | 21 | /** 22 | * Generates a CSR using the BouncyCastle library 23 | */ 24 | @Slf4j 25 | public class CSRGenerator { 26 | 27 | public CSRGenerator(){ 28 | Security.addProvider(new BouncyCastleProvider()); 29 | } 30 | 31 | /** 32 | * Generates a key pair if one has not already been generated. This is not in the constructor because of the exception handling. 33 | */ 34 | KeyPair generateKeyPair() throws NoSuchAlgorithmException { 35 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 36 | keyPairGenerator.initialize(2048); 37 | return keyPairGenerator.generateKeyPair(); 38 | } 39 | 40 | /*** 41 | * Generates a CSR using the provided common name 42 | * @param commonName the common name to use in the CSR 43 | * @return a CSR in PEM format 44 | */ 45 | public String generateCSR(String commonName) throws NoSuchAlgorithmException, IOException, OperatorCreationException { 46 | 47 | KeyPair keyPair = generateKeyPair(); 48 | StringWriter writer = new StringWriter(); 49 | try (JcaPEMWriter pemWriter = new JcaPEMWriter(writer)) { 50 | 51 | X500Name subject = new X500Name("CN=" + commonName); 52 | PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); 53 | JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA"); 54 | ContentSigner signer = csBuilder.build(keyPair.getPrivate()); 55 | PKCS10CertificationRequest certificationRequest = p10Builder.build(signer); 56 | 57 | pemWriter.writeObject(certificationRequest); 58 | } 59 | 60 | return writer.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/utils/DomainUtils.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.List; 5 | import java.util.Random; 6 | import java.util.stream.IntStream; 7 | 8 | public class DomainUtils { 9 | private static final Random random = new SecureRandom(); 10 | 11 | private static final List firstPiece = List.of( 12 | "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", 13 | "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", 14 | "phi", "chi", "psi", "omega", "aqua", "terra", "sol", "luna", "nova", "stella", 15 | "astro", "cosmo", "galaxy", "nebula", "quasar", "pulsar", "comet", "meteor", "orbit", "eclipse", 16 | "zenith", "horizon", "vertex", "apex", "nadir", "zenith", "aphelion", "perihelion", "solstice", "equinox" 17 | ); 18 | 19 | private static final List secondPiece = List.of( 20 | "tech", "web", "cloud", "data", "byte", "bit", "code", "dev", "app", 21 | "soft", "ware", "sys", "info", "logic", "matrix", "node", "hub", "link", "port", 22 | "gate", "path", "route", "stream", "flow", "wave", "pulse", "signal", "band", "freq", 23 | "mod", "trans", "form", "gen", "sync", "core", "prime", "edge", "point", "zone", 24 | "field", "grid", "mesh", "web", "site", "page", "host", "serverWithData", "domain" 25 | ); 26 | 27 | public static String getRandomDomainName(int numLabels, String tld) { 28 | StringBuilder domainName = new StringBuilder(); 29 | 30 | // Include the TLD as a label, so go one less than the number of labels 31 | IntStream.rangeClosed(1, numLabels - 1).forEach(i -> { 32 | domainName.append(firstPiece.get(random.nextInt(firstPiece.size()))); 33 | if (i == numLabels - 1) { 34 | domainName.append("-"); 35 | domainName.append(secondPiece.get(random.nextInt(secondPiece.size()))); 36 | } 37 | domainName.append("."); 38 | }); 39 | return domainName.append(tld).toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example-app/src/test/java/com/digicert/validation/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | 7 | public class FileUtils { 8 | 9 | private static final String DIRECTORY_PATH = "./nginx/www/.well-known/pki-validation/"; 10 | 11 | public static void writeNginxStaticFileWithContent(String fileName, String content) throws IOException { 12 | // Create the file 13 | File file = new File(DIRECTORY_PATH + fileName); 14 | try (FileWriter writer = new FileWriter(file)) { 15 | writer.write(content); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/DcvContext.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import com.digicert.validation.challenges.BasicRequestTokenValidator; 4 | import com.digicert.validation.challenges.RequestTokenValidator; 5 | import lombok.Getter; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | 15 | /** 16 | * The DcvContext provides dependency injection, allowing for easier testing of the various pieces 17 | * of the library. It initializes and holds references to various classes used throughout the application. 18 | * It is possible to have multiple contexts side by side with different configuration. 19 | */ 20 | @Slf4j 21 | public class DcvContext { 22 | 23 | /** The DCV Configuration used by the context. */ 24 | @Getter 25 | private final DcvConfiguration dcvConfiguration; 26 | 27 | /** A map of instances that have been created by the context. */ 28 | private final HashMap, Object> instances = new HashMap<>(); 29 | 30 | /** 31 | * This map is used for basic implementations of interfaces to only load if no custom implementation is provided. 32 | *

33 | * When the BasicRequestTokenValidator class is instantiated, it also instantiates a BasicRequestTokenUtils 34 | * instance. That utils class requires a BouncyCastleProvider to be added as a security provider. By lazily loading 35 | * the validator, users of this library can avoid adding the BouncyCastleProvider as a security provider if they 36 | * provide their own implementation of the RequestTokenValidator interface. 37 | */ 38 | private final Map, Class> lazyLoadImplementations = Map.of( 39 | RequestTokenValidator.class, BasicRequestTokenValidator.class 40 | ); 41 | 42 | /** 43 | * Retrieves an instance of the specified class. If an instance already exists, it returns the cached instance. 44 | * If the class is found in the DCV Configuration, it caches and returns it. Otherwise, it instantiates the class, 45 | * caches it, and returns it. 46 | * 47 | * @param classType the class to retrieve or instantiate 48 | * @param the type of the class 49 | * @return an instance of the specified class 50 | * @throws RuntimeException if the class cannot be instantiated 51 | */ 52 | @SuppressWarnings("unchecked") 53 | public T get(Class classType) { 54 | try { 55 | // If we already have an instance of the desired class, return it 56 | if (instances.containsKey(classType)) { 57 | return (T) instances.get(classType); 58 | } 59 | 60 | // If the desired class should come from the DCV Configuration, cache it and return it 61 | Optional fieldOpt = Arrays.stream(DcvConfiguration.class.getDeclaredFields()) 62 | .filter(f -> f.getType().equals(classType)) 63 | .findFirst(); 64 | 65 | if (fieldOpt.isPresent()) { 66 | Field field = fieldOpt.get(); 67 | field.setAccessible(true); 68 | 69 | Object configObject = field.get(dcvConfiguration); 70 | if (configObject != null) { 71 | instances.put(classType, configObject); 72 | return (T) configObject; 73 | } 74 | } 75 | 76 | // With the lazyLoadImplementations map as an override, instantiate the desired class, cache it, and return it 77 | Object foundClass; 78 | if (lazyLoadImplementations.containsKey(classType)) { 79 | foundClass = lazyLoadImplementations.getOrDefault(classType, classType).getConstructor().newInstance(); 80 | } else { 81 | foundClass = classType.getConstructor(DcvContext.class).newInstance(this); 82 | } 83 | instances.put(classType, foundClass); 84 | return (T) foundClass; 85 | 86 | } catch (IllegalAccessException | 87 | NoSuchMethodException | 88 | InvocationTargetException | 89 | InstantiationException e) { 90 | throw new IllegalStateException("Unable to instantiate the DCV Context", e); 91 | } 92 | } 93 | 94 | /** Default constructor that initializes the context with a default DCV Configuration. */ 95 | public DcvContext() { 96 | this(new DcvConfiguration.DcvConfigurationBuilder().build()); 97 | } 98 | 99 | /** 100 | * Constructor that initializes the context with the provided DCV Configuration. 101 | * 102 | * @param dcvConfiguration the DCV Configuration to use 103 | */ 104 | public DcvContext(DcvConfiguration dcvConfiguration) { 105 | this.dcvConfiguration = dcvConfiguration; 106 | } 107 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/DcvManager.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import com.digicert.validation.client.dns.DnsClient; 4 | import com.digicert.validation.methods.dns.DnsValidator; 5 | import com.digicert.validation.methods.email.EmailValidator; 6 | import com.digicert.validation.methods.file.FileValidator; 7 | import lombok.Getter; 8 | 9 | /** 10 | * DcvManager is responsible for managing the Domain Control Validation (DCV) process. 11 | * It initializes and holds references to various validators such as DNS, Email, and File Validation validators, 12 | * as well as the Dns Client. 13 | *

14 | * The DcvManager class acts as a central point for coordinating the DCV process. It ensures that the appropriate 15 | * validation method is used based on the configuration and the type of validation required. 16 | */ 17 | @Getter 18 | public class DcvManager { 19 | 20 | /** 21 | * The DNS validator used for DCV. 22 | *

23 | * This validator is responsible for performing DNS-based domain control validation. It is able to check for the 24 | * presence of a request token or random value in a DNS TXT or DNS CNAME record. 25 | *

26 | * Handles 3.2.2.4.7 DNS Change 27 | */ 28 | private final DnsValidator dnsValidator; 29 | 30 | /** 31 | * The Email validator used for DCV. 32 | *

33 | * This validator assists with email-based domain control validation. It is able to perform DNS lookups 34 | * to determine usable email addresses for a given domain. It is not able to send out emails. 35 | *

36 | * Handles 37 | *

    38 | *
  • 3.2.2.4.2 Email, Fax, SMS, or Postal Mail to Domain Contact
  • 39 | *
  • 3.2.2.4.4 Constructed Email to Domain Contact
  • 40 | *
  • 3.2.2.4.14 Email to DNS TXT Contact
  • 41 | *
42 | */ 43 | private final EmailValidator emailValidator; 44 | 45 | /** 46 | * The File Validation validator used for DCV. 47 | *

48 | * This validator is responsible for performing file-based domain control validation. It checks for the presence 49 | * of a request token or random value in a file to verify FQDN ownership. File validation cannot be used for 50 | * wildcard domain names. 51 | *

52 | * Handles 3.2.2.4.18 Agreed-Upon Change to Website v2 53 | */ 54 | private final FileValidator fileValidator; 55 | 56 | /** 57 | * The DNS client used for DNS queries. Exposed to allow for use outside the library. 58 | */ 59 | private final DnsClient dnsClient; 60 | 61 | /** 62 | * Private constructor to enforce the use of the Builder for object creation. 63 | * 64 | * @param dcvConfiguration the configuration for DCV 65 | */ 66 | private DcvManager(DcvConfiguration dcvConfiguration) { 67 | DcvContext dcvContext = new DcvContext(dcvConfiguration); 68 | 69 | this.dnsValidator = dcvContext.get(DnsValidator.class); 70 | this.emailValidator = dcvContext.get(EmailValidator.class); 71 | this.fileValidator = dcvContext.get(FileValidator.class); 72 | this.dnsClient = dcvContext.get(DnsClient.class); 73 | } 74 | 75 | /** Builder class for constructing DcvManager instances. */ 76 | public static class Builder { 77 | 78 | /** The DcvConfiguration for the DcvManager. */ 79 | private DcvConfiguration dcvConfiguration; 80 | 81 | /** Default constructor for the Builder class. */ 82 | public Builder() { 83 | // Default constructor 84 | } 85 | 86 | /** 87 | * Validates and sets the DcvConfiguration for the DcvManager. 88 | * 89 | * @param dcvConfiguration the configuration for DCV 90 | * @return the Builder instance 91 | */ 92 | public Builder withDcvConfiguration(DcvConfiguration dcvConfiguration) { 93 | validateDcvConfiguration(dcvConfiguration); 94 | this.dcvConfiguration = dcvConfiguration; 95 | return this; 96 | } 97 | 98 | /** 99 | * Validates the provided DcvConfiguration. 100 | * 101 | * @param dcvConfiguration the configuration to validate 102 | * @throws IllegalArgumentException if the configuration is null or invalid 103 | */ 104 | private void validateDcvConfiguration(DcvConfiguration dcvConfiguration) { 105 | if (dcvConfiguration == null) { 106 | throw new IllegalArgumentException("DcvConfiguration cannot be null"); 107 | } 108 | if (dcvConfiguration.getDnsServers() == null || dcvConfiguration.getDnsServers().isEmpty()) { 109 | throw new IllegalArgumentException("DnsServers cannot be empty"); 110 | } 111 | } 112 | 113 | /** 114 | * Builds and returns a DcvManager instance using the provided configuration. 115 | * 116 | * @return the constructed DcvManager instance 117 | */ 118 | public DcvManager build() { 119 | return new DcvManager(dcvConfiguration); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/BasicRandomValueValidator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.Optional; 7 | import java.util.Set; 8 | 9 | /** 10 | * A basic implementation of the {@link RandomValueValidator} interface. 11 | * This validator checks if the supplied random value is present in the provided text body. 12 | *

13 | * The BasicRandomValueValidator class provides a straightforward implementation of the RandomValueValidator interface. 14 | */ 15 | public class BasicRandomValueValidator implements RandomValueValidator { 16 | 17 | /** Default constructor for BasicRandomValueValidator. */ 18 | public BasicRandomValueValidator() { 19 | // Default constructor 20 | } 21 | 22 | /** 23 | * Validates the presence of the random value in the text body. 24 | * 25 | * @param randomValue the random value 26 | * @param textBody the text body in which to search for the random value 27 | * @return a {@link ChallengeValidationResponse} containing the validation result 28 | */ 29 | @Override 30 | public ChallengeValidationResponse validate(String randomValue, String textBody) { 31 | if (StringUtils.isEmpty(textBody)) { 32 | return new ChallengeValidationResponse(Optional.empty(), Set.of(DcvError.RANDOM_VALUE_EMPTY_TEXT_BODY)); 33 | } 34 | 35 | if (!textBody.contains(randomValue)) { 36 | return new ChallengeValidationResponse(Optional.empty(), Set.of(DcvError.RANDOM_VALUE_NOT_FOUND)); 37 | } 38 | 39 | return new ChallengeValidationResponse(Optional.of(randomValue), Set.of()); 40 | } 41 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/BasicRequestTokenData.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | /** 4 | * An implementation of the {@link RequestTokenData} interface to be used with 5 | * the BasicRequestTokenValidator. 6 | * 7 | * @param hashingKey The hashing key used to generate request tokens. 8 | * @param hashingValue The value to hash to generate request tokens. 9 | */ 10 | public record BasicRequestTokenData(String hashingKey, String hashingValue) implements RequestTokenData{ 11 | } 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/ChallengeValidationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | 5 | import java.util.EnumSet; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | 9 | /** 10 | * A record to hold the results of a response validator. 11 | *

12 | * The `challengeValue` field is an `Optional` that holds the validated random value or request token if the validation 13 | * is successful and is empty if the validation fails. The `errors` field is a `Set` of {@link DcvError} that allows for 14 | * providing a comprehensive list of issues that occurred during validation. 15 | * 16 | * @param challengeValue an Optional containing the validated challenge value, or an empty Optional if validation fails 17 | * @param errors a Set of DcvError indicating the errors encountered during validation 18 | */ 19 | public record ChallengeValidationResponse(Optional challengeValue, 20 | Set errors) { 21 | 22 | /** 23 | * A convenience method to merge two challenge validation responses. 24 | *

25 | * If either response is successful, the challenge value from the first successful response is returned. If both 26 | * responses are not valid, the errors from both responses are combined. 27 | * @param other the second response to merge with this response 28 | * @return a new ChallengeValidationResponse containing the merged results 29 | */ 30 | public ChallengeValidationResponse merge(ChallengeValidationResponse other) { 31 | if (challengeValue().isPresent()) { 32 | return this; 33 | } else if (other.challengeValue().isPresent()) { 34 | return other; 35 | } 36 | Set allErrors = EnumSet.copyOf(errors); 37 | allErrors.addAll(other.errors()); 38 | return new ChallengeValidationResponse(challengeValue, allErrors); 39 | } 40 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/RandomValueValidator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | /** 4 | * Interface for validating random values. 5 | *

6 | * The RandomValueValidator interface defines the contract for implementing classes that are responsible for validating 7 | * a given text body against a random value. 8 | */ 9 | public interface RandomValueValidator { 10 | 11 | /** 12 | * Validates the provided text body against the given random value. 13 | *

14 | * This method takes a random value and a text body as input parameters and performs validation to determine if the 15 | * random value is found within the provided text body. 16 | * 17 | * @param randomValue the random value to validate against 18 | * @param textBody the text body to check for the presence of the random value 19 | * @return a {@link ChallengeValidationResponse} containing the validation result 20 | */ 21 | ChallengeValidationResponse validate(String randomValue, String textBody); 22 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/RequestTokenData.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | /** 4 | * Interface used to provide a common type for request token data. An implementation 5 | * of this interface should be created to handle the needs of custom {@link RequestTokenValidator} 6 | * implementations. 7 | */ 8 | public interface RequestTokenData { 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/challenges/RequestTokenValidator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | /** 4 | * Interface for validating request tokens. 5 | *

6 | * This interface defines the contract for request token validation within the application. Implementations of this 7 | * interface are responsible for providing the logic to validate request tokens based on the supplied request token 8 | * data and the discovered text body. 9 | */ 10 | public interface RequestTokenValidator { 11 | 12 | /** 13 | * Attempts to find a valid request token using the given request token data and text body. 14 | *

15 | * This method takes in a request token data and text body to perform the validation process. The text body comes 16 | * from either a DNS record or a http request, and the request token data contains whatever data is necessary to 17 | * determine if the text body contains a valid token. 18 | * 19 | * @param requestTokenData the data necessary to determine if a valid request token is present 20 | * @param textBody the text body that may contain a request token 21 | * @return a {@link ChallengeValidationResponse} indicating the result of the validation 22 | */ 23 | ChallengeValidationResponse validate(RequestTokenData requestTokenData, String textBody); 24 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/client/dns/DnsData.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.dns; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import com.digicert.validation.enums.DnsType; 5 | import org.xbill.DNS.Record; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** 11 | * DnsData is a record that encapsulates the results of a DNS query. 12 | *

13 | * This record is designed to hold comprehensive information about a DNS query operation. It includes details about the 14 | * DNS servers that were queried, the domain name that was the subject of the query, the type of DNS record that was 15 | * requested, the actual DNS records that were retrieved, any errors that were encountered during the query process, and 16 | * the specific DNS server that provided the data. This encapsulation allows for easy management and access to all 17 | * relevant data pertaining to a DNS query. 18 | * 19 | * @param servers The list of DNS servers queried. 20 | *

21 | * This parameter holds the list of DNS servers that were contacted during the query process. 22 | * 23 | * @param domain The domain name that was queried. 24 | *

25 | * This parameter specifies the domain name that was the target of the DNS query. It 26 | * is used in conjunction with the DNS record type to form the complete query. 27 | * 28 | * @param dnsType The type of DNS record queried. 29 | *

30 | * This parameter indicates the type of DNS record that was requested, such as A, CNAME, TXT, etc. 31 | * 32 | * @param records The list of DNS records retrieved. 33 | *

34 | * This parameter contains the actual DNS records that were returned in response to the query. These 35 | * records provide the requested information and are the primary result of the DNS query operation. 36 | * 37 | * @param errors The list of errors encountered during the DNS query. 38 | *

39 | * This can be any of the {@link DcvError} values, but will generally be limited to DNS_LOOKUP* errors. 40 | * 41 | * @param serverWithData The DNS server that provided the data. 42 | *

43 | * This parameter identifies the specific DNS server that ultimately provided the data for the 44 | * query. 45 | */ 46 | public record DnsData(List servers, 47 | String domain, 48 | DnsType dnsType, 49 | List records, 50 | Set errors, 51 | String serverWithData) { 52 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/client/file/CustomDnsResolver.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.file; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import com.digicert.validation.client.dns.DnsClient; 5 | import com.digicert.validation.enums.DnsType; 6 | import org.apache.hc.client5.http.SystemDefaultDnsResolver; 7 | import org.xbill.DNS.ARecord; 8 | 9 | import java.net.InetAddress; 10 | import java.net.UnknownHostException; 11 | import java.util.List; 12 | 13 | /** 14 | * CustomDnsResolver is an implementation of the DnsResolver interface that utilizes a DnsClient 15 | * to perform DNS queries. It specifically processes A records and converts them into InetAddress objects. 16 | *

17 | * According to the BR's, DNS resolution must be conducted by DNS servers managed by the CA. This library 18 | * facilitates this requirement by using the DNS servers specified in the DCV configuration. 19 | */ 20 | public class CustomDnsResolver extends SystemDefaultDnsResolver { 21 | 22 | /** The DnsClient used to resolve DNS queries. */ 23 | private final DnsClient dnsClient; 24 | 25 | /** 26 | * Constructs a CustomDnsResolver with the specified DcvContext. The Context is used to retrieve the 27 | * DnsClient dependency needed to perform DNS queries. 28 | * 29 | * @param dcvContext context where we can find the needed dependencies / configuration 30 | */ 31 | public CustomDnsResolver(DcvContext dcvContext) { 32 | dnsClient = dcvContext.get(DnsClient.class); 33 | } 34 | 35 | /** 36 | * Resolves the given host name to an array of InetAddress objects using the DnsClient. 37 | *

38 | * This method overrides the resolve method of the SystemDefaultDnsResolver to provide custom DNS 39 | * resolution logic. It uses the DnsClient to perform the A record DNS lookup. 40 | * The A records are then converted to InetAddress objects, which are returned as the 41 | * result. If the DNS lookup fails or an error occurs, an UnknownHostException is thrown with a detailed 42 | * error message. 43 | * 44 | * @param host The host name to resolve. 45 | * @return An array of InetAddress objects for the given host name. 46 | * @throws UnknownHostException If the host name cannot be resolved. 47 | */ 48 | @Override 49 | public InetAddress[] resolve(String host) throws UnknownHostException { 50 | try { 51 | return dnsClient.getDnsData(List.of(host), DnsType.A) 52 | .records() 53 | .stream() 54 | .filter(ARecord.class::isInstance) 55 | .map(ARecord.class::cast) 56 | .map(ARecord::getAddress) 57 | .toArray(InetAddress[]::new); 58 | } catch (Exception e) { 59 | throw new UnknownHostException("Failed to resolve host: " + host + " due to " + e.getMessage()); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/client/file/FileClientResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.file; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * Represents the response from a file validation client. 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | @Setter 15 | @ToString 16 | public class FileClientResponse { 17 | /** The URL of the file requested. */ 18 | private String fileUrl; 19 | 20 | /** The content of the requested file. */ 21 | private String fileContent; 22 | 23 | /** The final HTTP status code after all redirects have been processed. */ 24 | private int statusCode; 25 | 26 | /** The exception that occurred during the file validation process, if any. */ 27 | private Exception exception; 28 | 29 | /** 30 | * The DCV error that occurred during the file validation process, if any. 31 | *

32 | * If set this field will be one of the following: 33 | *

    34 | *
  • FILE_VALIDATION_CLIENT_ERROR
  • 35 | *
  • FILE_VALIDATION_INVALID_CONTENT
  • 36 | *
37 | */ 38 | private DcvError dcvError; 39 | 40 | /** 41 | * Constructs a new FileClientResponse with the specified file URL. 42 | * The content, status code, exception, and DCV error are set to null. 43 | * 44 | * @param fileUrl the URL of the file 45 | */ 46 | public FileClientResponse(String fileUrl) { 47 | this.fileUrl = fileUrl; 48 | } 49 | 50 | /** 51 | * Constructs a new FileClientResponse with the specified file URL and content. 52 | * The exception is set to null. 53 | * 54 | * @param fileUrl the URL of the file 55 | * @param fileContent the content of the file 56 | * @param statusCode the status code of the HTTP response 57 | */ 58 | public FileClientResponse(String fileUrl, String fileContent, int statusCode) { 59 | this.fileUrl = fileUrl; 60 | this.fileContent = fileContent; 61 | this.statusCode = statusCode; 62 | this.exception = null; 63 | } 64 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/common/DomainValidationEvidence.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.common; 2 | 3 | import com.digicert.validation.enums.DcvMethod; 4 | import com.digicert.validation.enums.DnsType; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | 9 | import java.time.Instant; 10 | 11 | /** 12 | * Represents the evidence of domain validation. 13 | * This class contains information about how the domain was validated, 14 | * including the method used, the date of validation, and various details 15 | * specific to the validation method. It is used to maintain a record of 16 | * domain validation activities for auditing and compliance purposes. 17 | */ 18 | @Getter 19 | @Builder 20 | @ToString 21 | public class DomainValidationEvidence { 22 | 23 | /** The domain being validated. */ 24 | private final String domain; 25 | 26 | /** The method used for domain control validation. */ 27 | private final DcvMethod dcvMethod; 28 | 29 | /** 30 | * Including the BR version number in the evidence is important for auditing purposes. 31 | * As specified in section 3.2.2.4 of the BRs: 32 | *
 33 |      *      CAs SHALL maintain a record of which domain validation method, including relevant BR version
 34 |      *      number, they used to validate every domain.
 35 |      * 
36 | */ 37 | public static final String BR_VERSION = "v2.1.1"; 38 | 39 | /** The instant when the domain validation was completed. */ 40 | private final Instant validationDate; 41 | 42 | /** 43 | * EMAIL: The email address used for validation. 44 | *

45 | * Only populated when the DCV method is an email type otherwise NULL 46 | * (BR_3_2_2_4_2 / BR_3_2_2_4_4 / BR_3_2_2_4_14) 47 | */ 48 | private final String emailAddress; 49 | 50 | /** 51 | * FILE Validation: The URL of the file used for validation. 52 | *

53 | * Only populated when the DCV method is FILE_VALIDATION (BR_3_2_2_4_18) otherwise NULL 54 | */ 55 | private final String fileUrl; 56 | 57 | /** 58 | * DNS: The type of DNS record used for validation. 59 | *

60 | * Only populated when the DCV method is DNS_CHANGE (BR_3_2_2_4_7) otherwise NULL 61 | */ 62 | private final DnsType dnsType; 63 | 64 | /** 65 | * DNS: The DNS server used for validation. 66 | *

67 | * Only populated when the DCV method is DNS_CHANGE (BR_3_2_2_4_7) otherwise NULL 68 | */ 69 | private final String dnsServer; 70 | 71 | /** 72 | * DNS: The DNS record name used for validation. 73 | *

74 | * Only populated when the DCV method is DNS_CHANGE (BR_3_2_2_4_7) otherwise NULL 75 | */ 76 | private final String dnsRecordName; 77 | 78 | /** 79 | * TOKEN: The valid request token found during validation. 80 | *

81 | * Only populated when using the REQUEST_TOKEN challenge type, which can only be used with the DNS_CHANGE and 82 | * FILE_VALIDATION DCV methods; otherwise NULL. 83 | */ 84 | private final String requestToken; 85 | 86 | /** 87 | * RANDOM: The random value used for validation. 88 | *

89 | * Populated when a random value is used for validation otherwise NULL 90 | */ 91 | private final String randomValue; 92 | 93 | /** 94 | * Constructs a new DomainValidationEvidence with the specified parameters. 95 | *

96 | * This constructor is private to enforce the use of the builder pattern for creating 97 | * instances of DomainValidationEvidence. It initializes all fields with the provided 98 | * values. 99 | * 100 | * @param domain The domain being validated. 101 | * @param dcvMethod The dcv method used to complete domain validation. 102 | * @param validationDate The date when the validation was complete. 103 | * @param emailAddress The email address used for validation, if an email dcv method was used 104 | * @param fileUrl The URL of the file used for validation, if a file dcv method was used. 105 | * @param dnsType The type of DNS record used for validation, if a DNS dcv method was used. 106 | * @param dnsServer The DNS server used for validation, if a DNS dcv method was used. 107 | * @param dnsRecordName The DNS record name used for validation, if a DNS dcv method was used. 108 | * @param requestToken The request token found during validation, if applicable. 109 | * @param randomValue The random value used for validation, if applicable. 110 | */ 111 | private DomainValidationEvidence(String domain, DcvMethod dcvMethod, Instant validationDate, String emailAddress, String fileUrl, DnsType dnsType, String dnsServer, String dnsRecordName, String requestToken, String randomValue) { 112 | this.domain = domain; 113 | this.dcvMethod = dcvMethod; 114 | this.validationDate = validationDate; 115 | this.emailAddress = emailAddress; 116 | this.fileUrl = fileUrl; 117 | this.dnsType = dnsType; 118 | this.dnsServer = dnsServer; 119 | this.dnsRecordName = dnsRecordName; 120 | this.requestToken = requestToken; 121 | this.randomValue = randomValue; 122 | } 123 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/common/ValidationState.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.common; 2 | 3 | import com.digicert.validation.enums.DcvMethod; 4 | 5 | import java.time.Instant; 6 | 7 | /** 8 | * Represents the state of a domain control validation (DCV) process. 9 | *

10 | * The ValidationState class is a simple data holder that encapsulates the state of a DCV process. 11 | * 12 | * @param domain The domain being validated. 13 | * @param prepareTime The time when the preparation for DCV was done. This is sent back in the validate request to determine validity. 14 | * @param dcvMethod The dcv method used for domain control validation. 15 | */ 16 | public record ValidationState(String domain, Instant prepareTime, DcvMethod dcvMethod) { 17 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/enums/ChallengeType.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.enums; 2 | 3 | /** 4 | * This enum represents the options for types of information that can be used for domain control validation challenges. 5 | * A random value is provided by the CA to the domain owner, and a request token is a value generated by the 6 | * domain owner according to the CA's specifications. 7 | */ 8 | public enum ChallengeType { 9 | 10 | /** 11 | * Specify that the domain control validation method will use a random value. 12 | *

13 | * Random values are required to have at least 112 bits of entropy, and will be generated by the CA and provided to 14 | * the domain owner. For email domain control validation methods, the value is sent to the domain owner via email 15 | * and knowledge of the random value is sufficient to prove control over the domain. For other domain control 16 | * validation methods, the random value must be placed in a specific location (either a DNS record or a file on the 17 | * web server) to prove control. 18 | */ 19 | RANDOM_VALUE, 20 | 21 | /** 22 | * Specify that the domain control validation method will expect a request token. 23 | *

24 | * A request token is a domain owner–generated token that is created according to a format specified by the 25 | * CA. Each request token must incorporate the key used in the certificate request and can only be reused if the 26 | * token includes a timestamp. The request token must be placed in a specific location (either a DNS record or a 27 | * file on the web server) to prove control. Request tokens cannot be used for email domain control validation 28 | * methods. 29 | *

30 | * It is useful to note that because request tokens are not supplied by the CA, it is possible to skip the 31 | * preparation step when using request tokens. 32 | */ 33 | REQUEST_TOKEN 34 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/enums/DcvMethod.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.enums; 2 | 3 | import com.digicert.validation.methods.file.FileValidator; 4 | import lombok.Getter; 5 | 6 | /** 7 | * Enum representing the possible domain validation methods. 8 | *

9 | * Brief Description of the DCV Methods: 10 | *

    11 | *
  • 3.2.2.4.1 -> NOT Allowed
  • 12 | *
  • 3.2.2.4.2 -> Email (whois) - Not Supported
  • 13 | *
  • 3.2.2.4.3 -> Phone - NOT Allowed
  • 14 | *
  • 3.2.2.4.4 -> Email to constructed email address
  • 15 | *
  • 3.2.2.4.5 -> NOT Allowed
  • 16 | *
  • 3.2.2.4.6 -> NOT Allowed
  • 17 | *
  • 3.2.2.4.7 - DNS Change
  • 18 | *
  • 3.2.2.4.8 -> IP Address - Not Supported
  • 19 | *
  • 3.2.2.4.9 -> NOT Allowed
  • 20 | *
  • 3.2.2.4.10 -> NOT Allowed
  • 21 | *
  • 3.2.2.4.11 -> NOT Allowed
  • 22 | *
  • 3.2.2.4.12 -> Validating applicant as a Domain Contact - Not Supported
  • 23 | *
  • 3.2.2.4.13 -> Email to DNS CAA Contact - Not Supported
  • 24 | *
  • 3.2.2.4.14 -> Email to DNS TXT Contact
  • 25 | *
  • 3.2.2.4.15 / 16 / 17 -> Phone Contact - Not Supported
  • 26 | *
  • 3.2.2.4.18 -> File Validation
  • 27 | *
  • 3.2.2.4.19 -> ACME details - Not Supported
  • 28 | *
  • 3.2.2.4.20 -> TLS
  • 29 | *
30 | * 31 | * @see com.digicert.validation.methods.dns.DnsValidator 32 | * @see com.digicert.validation.methods.email.EmailValidator 33 | * @see FileValidator 34 | */ 35 | @Getter 36 | public enum DcvMethod { 37 | 38 | /** 39 | * Constructed Email to Domain Contact. 40 | *

41 | * This method involves sending an email to a constructed address based on the domain, such as admin@domain.com. 42 | * The email will contain a random value that the recipient can use to confirm control over the domain. 43 | *

44 | * The constructed emails are: 45 | *

    46 | *
  • admin@
  • 47 | *
  • administrator@
  • 48 | *
  • webmaster@
  • 49 | *
  • hostmaster@
  • 50 | *
  • postmaster@
  • 51 | *
52 | */ 53 | BR_3_2_2_4_4("3.2.2.4.4"), 54 | 55 | /** 56 | * DNS Change. 57 | *

58 | * This method requires the domain owner to create a specific DNS record. The presence of a random value or 59 | * request token in the record is then verified to confirm control over the domain. 60 | */ 61 | BR_3_2_2_4_7("3.2.2.4.7"), 62 | 63 | /** 64 | * Email to DNS Txt Contact. 65 | *

66 | * This method involves sending an email to an address specified in a DNS TXT record. The DNS TXT record must be 67 | * located at _validation-contactemail.<domain>. The email will contain a random value that the recipient 68 | * can use to confirm control over the domain. 69 | */ 70 | BR_3_2_2_4_14("3.2.2.4.14"), 71 | 72 | /** 73 | * Agreed-Upon Change to Website v2. 74 | *

75 | * This method requires the domain owner to place a random value or request token in a specific file at a 76 | * predetermined location on their web server. The presence of this file containing the given random value or 77 | * request token confirms control over the FQDN. 78 | */ 79 | BR_3_2_2_4_18("3.2.2.4.18"); 80 | 81 | /** The DCV method string. 82 | */ 83 | private final String brMethod; 84 | 85 | /** 86 | * Constructs a new DcvMethod with the specified DCV method string. 87 | * 88 | * @param brMethod the DCV method string 89 | */ 90 | DcvMethod(String brMethod) { 91 | this.brMethod = brMethod; 92 | } 93 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/enums/DnsType.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.enums; 2 | 3 | /** 4 | * Enum representing the DNS record types that can be requested. 5 | *

6 | * DNS records are essential components of the Domain Name System (DNS), which is responsible for translating human-readable domain names into IP addresses. 7 | * Each DNS record type serves a specific purpose and provides different types of information about a domain. 8 | */ 9 | public enum DnsType { 10 | /** Alias of one name to another. */ 11 | CNAME, 12 | 13 | /** Specifies freeform supplemental text data. */ 14 | TXT, 15 | 16 | /** Specifies which certificate authorities (CAs) are allowed to issue certificates for the domain. */ 17 | CAA, 18 | 19 | /** Address record, which is used to map hostnames to their IP address. */ 20 | A, 21 | 22 | /** Email exchange record, which points to the IP Addresses of a domain's mail server */ 23 | MX, 24 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/enums/LogEvents.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.enums; 2 | 3 | /** 4 | * Enumeration representing various events that can occur during the validation process. 5 | */ 6 | public enum LogEvents { 7 | /** Log event indicating a redirect was attempted but the host information was missing. */ 8 | BAD_REDIRECT_NO_HOST, 9 | 10 | /** Log event indicating an invalid URL was found during a file validation redirect. */ 11 | BAD_REDIRECT_URL, 12 | 13 | /** 14 | * Log event indicating an incorrect port was used during a file validation redirect. 15 | * Only ports 80 and 443 are allowed. 16 | */ 17 | BAD_REDIRECT_PORT, 18 | 19 | /** Log event indicating there an unknown error that occurred during a file validation redirect attempt. */ 20 | REDIRECT_ERROR, 21 | 22 | /** Log event indicating that the system failed to generate a hash value. */ 23 | CANNOT_GENERATE_HASH, 24 | 25 | /** This log event indicates that the system successfully retrieved the DNS data for a domain. */ 26 | DNS_LOOKUP_SUCCESS, 27 | 28 | /** Log event indicates that the DNS lookup was unable to find the host or the data returned was not parseable. */ 29 | DNS_LOOKUP_ERROR, 30 | 31 | /** Log event indicates that the DNS validation cannot be completed. */ 32 | DNS_VALIDATION_FAILED, 33 | 34 | /** Log event indicating that the validation request is malformed. */ 35 | DNS_VALIDATION_MALFORMED_REQUEST, 36 | 37 | /** Log event indicating that control over the domain was successfully proven. */ 38 | DNS_VALIDATION_SUCCESSFUL, 39 | 40 | /** Log event indicating that the DNS server is not configured. */ 41 | DNS_SERVERS_NOT_CONFIGURED, 42 | 43 | /** Log event indicating that a response was received */ 44 | FILE_VALIDATION_RESPONSE, 45 | 46 | /** Log event indicating that the response was not parseable or otherwise does not meet with validation requirements. */ 47 | FILE_VALIDATION_BAD_RESPONSE, 48 | 49 | /** Log event indicating that a connection error occurred with the client. */ 50 | FILE_VALIDATION_CLIENT_ERROR, 51 | 52 | /** Log event indicating that the file validation cannot be completed. */ 53 | FILE_VALIDATION_FAILED, 54 | 55 | /** Log event indicating that control over the FQDN was successfully proven. */ 56 | FILE_VALIDATION_SUCCESSFUL, 57 | 58 | /** Log event indicating that the domain is invalid due to its length. (Max 255 characters) */ 59 | INVALID_DOMAIN_LENGTH, 60 | 61 | /** Log event indicating that the domain name does not match the regex used to validate the domain name syntax. */ 62 | INVALID_DOMAIN_NAME, 63 | 64 | /** 65 | * Reserved labels, which are two alphanumeric characters followed by two hyphens, must follow a specific 66 | * standards which this domain does not follow. 67 | */ 68 | INVALID_RESERVED_LDH_LABEL, 69 | 70 | /** Log event indicating that no properly formatted DNS TXT records containing a contact were found. */ 71 | NO_DNS_TXT_CONTACT_FOUND, 72 | 73 | /** Security provider used for calculating hashes was unable to load. The default token validator will not be usable. */ 74 | SECURITY_PROVIDER_LOAD_ERROR, 75 | 76 | /** Log event indicating a failure in creating the SSL context. Should not be reachable and would indicate a Java library mismatch. */ 77 | SSL_CONTEXT_CREATION_ERROR; 78 | 79 | /** 80 | * Returns the lowercase string representation of the event. 81 | * 82 | * @return the string representation of the event 83 | */ 84 | @Override 85 | public String toString() { 86 | return this.name().toLowerCase(); 87 | } 88 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/exceptions/DcvException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import lombok.Getter; 5 | 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | /** 10 | * Base Exception class for DCV (Domain Control Validation) related exceptions. 11 | *

12 | * This class serves as the foundational exception for all errors related to Domain Control Validation (DCV). 13 | * It encapsulates a set of DCV errors, providing a structured way to handle and report issues encountered 14 | * during the DCV process. 15 | */ 16 | @Getter 17 | public class DcvException extends Exception { 18 | 19 | /** 20 | * The set of DCV errors. 21 | *

22 | * This field holds a set of `DcvError` enums that represent the specific errors encountered during 23 | * the DCV process. 24 | */ 25 | private final Set errors; 26 | 27 | /** 28 | * Constructs a new DcvException with the specified DcvError. 29 | * 30 | * @param dcvError the DCV error that caused the exception to be thrown 31 | */ 32 | public DcvException(DcvError dcvError) { 33 | this(Set.of(dcvError)); 34 | } 35 | 36 | /** 37 | * Constructs a new DcvException with a set of specified DcvErrors. 38 | * 39 | * @param errors the set of DCV errors that caused the exception to be thrown 40 | */ 41 | public DcvException(Set errors) { 42 | this(errors, null); 43 | } 44 | 45 | /** 46 | * Constructs a new DcvException with a set of specified DcvErrors and an optional cause. 47 | * 48 | * @param dcvErrors the set of DCV errors that caused the exception to be thrown 49 | * @param cause the cause of the exception 50 | */ 51 | public DcvException(Set dcvErrors, Throwable cause) { 52 | super("DcvException with errors = " + dcvErrors.stream().map(DcvError::toString).collect(Collectors.joining(",")), cause); 53 | this.errors = dcvErrors; 54 | } 55 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/exceptions/InputException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * Exception thrown when there is an issue with the input supplied to the library. 9 | *

10 | * The `InputException` class is a specialized exception that extends the `DcvException` class. It is used to signal 11 | * problems related to the input provided to the library. This exception is typically thrown when the input does not 12 | * meet the expected format, contains invalid values, or fails validation checks. 13 | */ 14 | public class InputException extends DcvException { 15 | 16 | /** 17 | * Constructs a new InputException `InputException` with a specific `DcvError` instance. 18 | * 19 | * @param dcvError the DCV error that caused the exception to be thrown 20 | */ 21 | public InputException(DcvError dcvError) { 22 | this(dcvError, null); 23 | } 24 | 25 | /** 26 | * Constructs a new `InputException` with a specific `DcvError` instance and a cause. 27 | * 28 | * @param dcvError the DCV error that caused the exception to be thrown 29 | * @param cause the cause of the exception 30 | */ 31 | public InputException(DcvError dcvError, Throwable cause) { 32 | super(Set.of(dcvError), cause); 33 | } 34 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/exceptions/PreparationException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import lombok.ToString; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * Exception thrown when there is an issue with the preparation of the validation. 10 | *

11 | * This exception is specifically used to indicate problems that occur during the preparation phase of the validation process. 12 | * Issues in this phase can stem from various sources such as misconfigurations, missing data, or other pre-validation errors. 13 | */ 14 | @ToString 15 | public class PreparationException extends DcvException { 16 | 17 | /** 18 | * Constructs a new PreparationException with a set of the specified DcvErrors. 19 | * 20 | * @param errors the set of DCV errors that caused the exception to be thrown. 21 | */ 22 | public PreparationException(Set errors) { 23 | super(errors); 24 | } 25 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/exceptions/ValidationException.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.exceptions; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import com.digicert.validation.methods.file.FileValidator; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | 8 | import java.util.Set; 9 | 10 | /** 11 | * Exception thrown when Validation fails. 12 | *

13 | * The `ValidationException` class is a custom exception that is thrown when a validation process fails. 14 | * It extends the `DcvException` class, inheriting its properties and methods. This exception is specifically 15 | * designed to handle validation errors that occur during the validation of DNS, email, or file validation 16 | * methods. 17 | * 18 | * @see com.digicert.validation.methods.dns.DnsValidator 19 | * @see com.digicert.validation.methods.email.EmailValidator 20 | * @see FileValidator 21 | */ 22 | @Getter 23 | @ToString 24 | public class ValidationException extends DcvException { 25 | 26 | /** 27 | * Constructs a new ValidationException with the specified DcvError. 28 | * 29 | * @param dcvError the DCV error 30 | */ 31 | public ValidationException(DcvError dcvError) { 32 | super(dcvError); 33 | } 34 | 35 | /** 36 | * Constructs a new ValidationException with a set of specified DcvErrors. 37 | * 38 | * @param errors the set of DCV errors 39 | */ 40 | public ValidationException(Set errors) { 41 | super(errors); 42 | } 43 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/dns/prepare/DnsPreparation.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.dns.prepare; 2 | 3 | import com.digicert.validation.enums.DnsType; 4 | import com.digicert.validation.enums.ChallengeType; 5 | 6 | /** 7 | * Represents the preparation details required for DNS validation. 8 | * This class is a record that holds the domain, DNS type, and challenge type. 9 | * 10 | * @param domain the domain to be validated 11 | * @param dnsType the type of DNS record 12 | * @param challengeType the type of challenge used for validation 13 | */ 14 | public record DnsPreparation(String domain, DnsType dnsType, ChallengeType challengeType) { 15 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/dns/prepare/DnsPreparationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.dns.prepare; 2 | 3 | import com.digicert.validation.common.ValidationState; 4 | import com.digicert.validation.enums.DnsType; 5 | import com.digicert.validation.enums.ChallengeType; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Represents the response for DNS preparation. 14 | * This class contains information about the domain, allowed FQDNs, DNS type, challenge type, and validation state. 15 | *

16 | * The `DnsPreparationResponse` class is designed to encapsulate all the necessary details required for DNS preparation. 17 | * It includes the domain for which the DNS entry is being prepared, a list of allowed fully qualified domain names (FQDNs), 18 | * the type of DNS entry, the type of challenge used for DNS validation, a random value for certain challenge types, and 19 | * the validation state. This class uses the builder pattern to ensure immutability and ease of object creation. 20 | */ 21 | @Getter 22 | @Builder 23 | @ToString 24 | public class DnsPreparationResponse { 25 | /** The domain for which the DNS validation process is being prepared. */ 26 | private final String domain; 27 | 28 | /** 29 | * List of allowed fully qualified domain names (FQDNs) where the DNS entry can be placed. 30 | *

31 | * The baseline requirements specify that "Once the FQDN has been validated using this method, the CA MAY also issue 32 | * Certificates for other FQDNs that end with all the Domain Labels of the validated FQDN." This allows for a 33 | * certificate to be issued for a.b.c.d.example.com if c.d.example.com is validated. This field contains the list of 34 | * FQDNs for which a certificate for the desired domain can be issued if validation is performed for any of these 35 | * FQDNs. 36 | */ 37 | private final List allowedFqdns; 38 | 39 | /** 40 | * The type of DNS entry in which the customer should place the challenge value. 41 | *

42 | * BR Section 3.2.2.4.7 (DNS Change) allows for the challenge value to be placed in a CNAME, TXT or CAA record. 43 | */ 44 | private final DnsType dnsType; 45 | 46 | /** The type of challenge to be used for DNS validation - either RANDOM_VALUE or REQUEST_TOKEN. */ 47 | private final ChallengeType challengeType; 48 | 49 | /** The random value to be placed in the DNS entry. Only used for RANDOM_VALUE challenge type. */ 50 | private final String randomValue; 51 | 52 | /** The current validation state of the DNS validation process. */ 53 | private final ValidationState validationState; 54 | 55 | /** 56 | * Private constructor to prevent instantiation without using the builder. 57 | * Constructs a new DnsPreparationResponse with the specified parameters. 58 | * 59 | * @param domain The domain for which the DNS entry is being prepared. 60 | * @param allowedFqdns List of allowed fully qualified domain names (FQDNs) where the DNS entry should be placed. 61 | * @param dnsType The type of DNS entry. 62 | * @param challengeType The type of secret used for DNS validation. 63 | * @param randomValue The random value to be placed in the DNS entry. 64 | * @param validationState The validation state of the DNS preparation. 65 | */ 66 | private DnsPreparationResponse(String domain, List allowedFqdns, DnsType dnsType, ChallengeType challengeType, String randomValue, ValidationState validationState) { 67 | this.domain = domain; 68 | this.allowedFqdns = allowedFqdns; 69 | this.dnsType = dnsType; 70 | this.challengeType = challengeType; 71 | this.randomValue = randomValue; 72 | this.validationState = validationState; 73 | } 74 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/dns/validate/DnsValidationRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.dns.validate; 2 | 3 | import com.digicert.validation.challenges.RequestTokenData; 4 | import com.digicert.validation.common.ValidationState; 5 | import com.digicert.validation.enums.DnsType; 6 | import com.digicert.validation.enums.ChallengeType; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | /** 12 | * Represents a DNS validation request. 13 | *

14 | * This class is used to create a request for validating a domain using DNS. 15 | * It encapsulates all the necessary information required to perform DNS validation, 16 | * including the domain name, the type of DNS record, the type of challenge used for validation, 17 | * and the current state of the validation process. This class is immutable and uses the 18 | * builder pattern. 19 | */ 20 | @Getter 21 | @Builder 22 | @ToString 23 | public class DnsValidationRequest { 24 | 25 | /** The domain to be validated. */ 26 | private final String domain; 27 | 28 | /** The type of DNS record to be used for validation. */ 29 | private final DnsType dnsType; 30 | 31 | /** The type of challenge to be used for validation. */ 32 | private final ChallengeType challengeType; 33 | 34 | /** A random value used for validation. Only used for RANDOM_VALUE challenge type. */ 35 | private final String randomValue; 36 | 37 | /** The request token data to be used for file validation. Only used for REQUEST_TOKEN challenge type. */ 38 | private final RequestTokenData requestTokenData; 39 | 40 | /** The current state of the validation process. */ 41 | private final ValidationState validationState; 42 | 43 | /** 44 | * Private constructor to prevent instantiation without using the builder. 45 | * 46 | * @param domain The domain to be validated. 47 | * @param dnsType The type of DNS record to be used for validation. 48 | * @param challengeType The type of challenge to be used for validation. 49 | * @param randomValue A random value used for validation. 50 | * @param requestTokenData The data necessary to validate request tokens. 51 | * @param validationState The current validation state. 52 | */ 53 | private DnsValidationRequest(String domain, DnsType dnsType, ChallengeType challengeType, String randomValue, RequestTokenData requestTokenData, ValidationState validationState) { 54 | // Default constructor 55 | this.domain = domain; 56 | this.dnsType = dnsType; 57 | this.challengeType = challengeType; 58 | this.randomValue = randomValue; 59 | this.requestTokenData = requestTokenData; 60 | this.validationState = validationState; 61 | } 62 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/dns/validate/DnsValidationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.dns.validate; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import com.digicert.validation.enums.DnsType; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * Represents the response of a DNS validation process. 10 | *

11 | * This record encapsulates the results of a DNS validation request. It includes details such as whether the validation 12 | * was successful,the DNS server used, the domain and DNS type involved, the valid random value or request token used, 13 | * and any errors that occurred during the validation. 14 | * 15 | * @param isValid Indicates whether the validation is successful. 16 | * @param server The DNS server used for validation. 17 | * @param domain The domain associated with the validation. 18 | * @param dnsType The type of DNS used in the validation. 19 | * @param validRandomValue The valid random value used in the validation. 20 | * @param validRequestToken The valid request token found in the validation. 21 | * @param errors The list of errors that occurred during the validation. 22 | */ 23 | public record DnsValidationResponse(boolean isValid, 24 | String server, 25 | String domain, 26 | DnsType dnsType, 27 | String validRandomValue, 28 | String validRequestToken, 29 | Set errors 30 | ) { 31 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/EmailPreparation.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare; 2 | 3 | /** 4 | * Represents the preparation details for email validation. 5 | *

6 | * This record encapsulates the necessary details required for preparing an email validation process. 7 | * The domain field represents the domain name that needs to be validated, ensuring that the email 8 | * originates from a legitimate source. The emailSource field indicates the source of the email, 9 | * which could be used to determine the authenticity and origin of the email for validation purposes. 10 | * 11 | * @param domain The domain to be validated. 12 | * @param emailSource The source of the email for validation. 13 | */ 14 | public record EmailPreparation(String domain, EmailSource emailSource) { 15 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/EmailPreparationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare; 2 | 3 | import com.digicert.validation.common.ValidationState; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Represents the response for email preparation. 9 | *

10 | * This record encapsulates the response details for an email preparation process. It includes the domain associated with the 11 | * email preparation, the source of the email, a list of emails with their associated random values, and the validation state 12 | * of the email preparation. The domain field represents the domain name that was validated. The emailSource field indicates the source of the email, 13 | * which could be used to determine the authenticity and origin of the email for validation purposes. The emailWithRandomValue 14 | * field contains a list of emails along with their associated random values, which are used for validation purposes. 15 | * The validationState field represents the current state of the email preparation process, indicating whether the 16 | * validation was successful, failed, or is still in progress. 17 | * 18 | * @param domain The domain associated with the email preparation. 19 | * @param emailSource The source of the email. 20 | * @param emailWithRandomValue A list of emails with their associated random values. 21 | * @param validationState The validation state of the email preparation. 22 | */ 23 | public record EmailPreparationResponse(String domain, 24 | EmailSource emailSource, 25 | List emailWithRandomValue, 26 | ValidationState validationState) { 27 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/EmailSource.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare; 2 | 3 | import com.digicert.validation.enums.DcvMethod; 4 | import lombok.Getter; 5 | 6 | /** 7 | * Enum representing the possible sources of email addresses for DCV. 8 | *

9 | * The EmailSource enum defines the various sources from which email addresses can be obtained for Domain Control Validation (DCV). 10 | * Each source corresponds to a specific method outlined in the Baseline Requirements (BR) for the issuance and management of publicly-trusted certificates. 11 | * These sources are critical in ensuring that the email addresses used for validation are legitimate and verifiable. 12 | * 13 | * @see DcvMethod 14 | */ 15 | 16 | @Getter 17 | public enum EmailSource { 18 | 19 | /** 20 | * The email addresses are constructed from the domain name. 21 | *

22 | * This source generates email addresses by combining common administrative prefixes with the domain name. 23 | * These constructed email addresses, such as "admin@domain.com" or "webmaster@domain.com", are used to ensure that the domain is properly configured. 24 | * This method is specified under BR 3.2.2.4.4 and helps in automating the validation process by providing a consistent set of email addresses. 25 | *

26 | * BR 3.2.2.4.4 27 | */ 28 | CONSTRUCTED(DcvMethod.BR_3_2_2_4_4), // 3.2.2.4.4 29 | 30 | /** 31 | * The email addresses are found in a DNS TXT record for the domain. 32 | *

33 | * This source involves querying the DNS TXT records of the domain to find email addresses. 34 | * DNS TXT records can contain various types of information, including email addresses for domain validation. 35 | * This method is specified under BR 3.2.2.4.14 and provides a flexible way to verify domain ownership through DNS configurations. 36 | *

37 | * BR 3.2.2.4.14 38 | */ 39 | DNS_TXT(DcvMethod.BR_3_2_2_4_14) // 3.2.2.4.14 40 | ; 41 | 42 | /** 43 | * The {@link DcvMethod} associated with the email source. 44 | *

45 | * Each email source is linked to a specific DCV method as defined in the Baseline Requirements. 46 | * The DcvMethod enum provides a standardized way to reference these methods, ensuring consistency and clarity in the validation process. 47 | * This association helps in identifying the exact method used for obtaining email addresses for DCV. 48 | */ 49 | private final DcvMethod dcvMethod; 50 | 51 | /** 52 | * Constructor for EmailSource 53 | *

54 | * Initializes the EmailSource enum with the corresponding {@link DcvMethod}. 55 | * This constructor ensures that each email source is properly linked to its respective DCV method, facilitating accurate and reliable domain validation. 56 | * 57 | * @param dcvMethod The {@link DcvMethod} associated with the email source. 58 | */ 59 | EmailSource(DcvMethod dcvMethod) { 60 | this.dcvMethod = dcvMethod; 61 | } 62 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/EmailWithRandomValue.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare; 2 | 3 | /** 4 | * This class is used to store the email address and the random value that was sent to the email address. 5 | *

6 | * The `EmailWithRandomValue` class is a record that encapsulates an email address and a corresponding random value. This random 7 | * value is used in the email validation process in order verify the ownership of an email address. 8 | * 9 | * @param email The email address. 10 | *

11 | * The `email` parameter represents the email address that is being validated. It is a string that should conform to standard 12 | * email format rules. 13 | * 14 | * @param randomValue The random value that was sent to the email address. 15 | *

16 | * The `randomValue` parameter is a string that contains a randomly generated value sent to the email address. This value is 17 | * used as a verification code to confirm that the email address is valid and accessible by the intended recipient. 18 | */ 19 | public record EmailWithRandomValue(String email, String randomValue) { } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/provider/ConstructedEmailProvider.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare.provider; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Set; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * ConstructedEmailProvider is an implementation of EmailProvider that constructs email addresses 10 | * that can be used for domain validation. 11 | *

12 | * This class provides a mechanism to generate a set of email addresses based on common administrative 13 | * prefixes and a given domain. 14 | */ 15 | public class ConstructedEmailProvider implements EmailProvider { 16 | /** 17 | * These prefixes can be used in combination with the FQDN 18 | * to form email addresses that can be used for domain validation. 19 | *

20 | * The list of base email address prefixes includes common administrative email addresses, specifically 21 | * "admin@", "administrator@", "webmaster@", "hostmaster@", and "postmaster@". 22 | */ 23 | private static final List BASE_EMAIL_ADDRESS_PREFIXES = Arrays.asList( 24 | "admin@", 25 | "administrator@", 26 | "webmaster@", 27 | "hostmaster@", 28 | "postmaster@" 29 | ); 30 | 31 | /** 32 | * The default constructor for the ConstructedEmailProvider class 33 | */ 34 | public ConstructedEmailProvider() { 35 | // Default constructor 36 | } 37 | 38 | /** 39 | * Returns a set of constructed email addresses that can be used for domain validation. 40 | * These are prefixed with {@link ConstructedEmailProvider#BASE_EMAIL_ADDRESS_PREFIXES} and suffixed with the domain. 41 | *

42 | * This method generates a set of email addresses by combining each prefix from the {@link ConstructedEmailProvider#BASE_EMAIL_ADDRESS_PREFIXES} 43 | * list with the provided domain. 44 | * 45 | * @param domain The domain to construct email addresses for. 46 | * @return A set of constructed email addresses that can be used for domain validation. 47 | */ 48 | @Override 49 | public Set findEmailsForDomain(String domain) { 50 | return BASE_EMAIL_ADDRESS_PREFIXES.stream() 51 | .map(prefix -> prefix + domain) 52 | .collect(Collectors.toUnmodifiableSet()); 53 | } 54 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/provider/DnsTxtEmailProvider.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare.provider; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import com.digicert.validation.client.dns.DnsClient; 5 | import com.digicert.validation.client.dns.DnsData; 6 | import com.digicert.validation.enums.DnsType; 7 | import com.digicert.validation.enums.LogEvents; 8 | import com.digicert.validation.exceptions.PreparationException; 9 | import com.digicert.validation.utils.DomainNameUtils; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.xbill.DNS.TXTRecord; 12 | 13 | import java.util.List; 14 | import java.util.Set; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * EmailDnsTxtProvider is an implementation of EmailProvider that retrieves email contacts for a domain by querying the 19 | * DNS TXT records of the domain. 20 | *

21 | * This provider is designed to facilitate the process of domain validation by extracting email addresses from DNS TXT records. 22 | * The DNS TXT records are queried using a specific prefix defined in the Baseline Requirements (BRs), which ensures that the 23 | * email addresses retrieved are intended for validation purposes. 24 | */ 25 | @Slf4j 26 | public class DnsTxtEmailProvider implements EmailProvider { 27 | 28 | /** 29 | * This prefix is used to identify the DNS TXT record that contains the email address for validation. 30 | * It is defined in the BRs as "_validation-contactemail" (section A.2.1). 31 | *

32 | * The prefix "_validation-contactemail" is a standardized identifier used in DNS TXT records to denote email addresses 33 | * that are specifically set up for domain validation. 34 | */ 35 | public static final String DNS_TXT_EMAIL_AUTHORIZATION_PREFIX = "_validation-contactemail"; 36 | 37 | /** 38 | * Error message used to indicate that the provider was unable to find any email addresses in the DNS TXT records 39 | * for the specified domain. 40 | */ 41 | static final String NO_CONTACTS_ERROR_MESSAGE = "Failed to find any DNS TXT Record Email Contact for domain %s"; 42 | 43 | /** 44 | * The DNS client used to query DNS records. 45 | */ 46 | private final DnsClient dnsClient; 47 | 48 | /** 49 | * Constructs a new EmailDnsTxtProvider with the given DcvContext. 50 | *

51 | * This constructor initializes the DnsTxtEmailProvider with the necessary dependencies and configuration provided by the 52 | * DcvContext. 53 | * 54 | * @param dcvContext context where we can find the needed dependencies and configuration 55 | */ 56 | public DnsTxtEmailProvider(DcvContext dcvContext) { 57 | dnsClient = dcvContext.get(DnsClient.class); 58 | } 59 | 60 | /** 61 | * Retrieves email contacts for the given domain by querying the DNS TXT records of the domain. 62 | *

63 | * This method performs a DNS query to retrieve emails found in the domain TXT records, using the 64 | * BR specified "_validation-contactemail" prefix. 65 | * 66 | * @param domain the domain to retrieve email contacts for 67 | * @return a set of email contacts for the domain 68 | * @throws PreparationException if an error occurs while retrieving email contacts for the domain 69 | */ 70 | @Override 71 | public Set findEmailsForDomain(String domain) throws PreparationException { 72 | List domains = List.of(String.format("%s.%s", DNS_TXT_EMAIL_AUTHORIZATION_PREFIX, domain)); 73 | DnsData dnsData = dnsClient.getDnsData(domains, DnsType.TXT); 74 | 75 | Set emails = dnsData.records().stream() 76 | .flatMap(dnsRecord -> ((TXTRecord) dnsRecord).getStrings().stream()) 77 | .filter(DomainNameUtils::isValidEmailAddress) 78 | .collect(Collectors.toUnmodifiableSet()); 79 | 80 | if (emails.isEmpty()) { 81 | log.info("event_id={} domain={} records={}", LogEvents.NO_DNS_TXT_CONTACT_FOUND, domain, dnsData.records().size()); 82 | throw new PreparationException(dnsData.errors()); 83 | } 84 | 85 | return emails; 86 | } 87 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/prepare/provider/EmailProvider.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare.provider; 2 | 3 | import com.digicert.validation.exceptions.PreparationException; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * EmailProvider is an interface for classes that provide email contacts for domain validation. 9 | *

10 | * This interface defines the contract for any class that aims to retrieve email contacts associated with a specific domain. 11 | * Implementations of this interface are expected to provide the logic for querying and extracting email addresses from various 12 | * sources, such as DNS TXT records, databases, or other external services. 13 | */ 14 | public interface EmailProvider { 15 | 16 | /** 17 | * Retrieves email contacts for the given domain. 18 | *

19 | * This method is responsible for obtaining a set of email addresses associated with the specified domain. The implementation 20 | * should ensure that the email addresses returned are valid and relevant for the domain validation process. 21 | * 22 | * @param domain the domain to retrieve email contacts for 23 | * @return a set of email contacts for the domain 24 | * @throws PreparationException if an error occurs while retrieving email contacts for the domain 25 | */ 26 | Set findEmailsForDomain(String domain) throws PreparationException; 27 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/email/validate/EmailValidationRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.validate; 2 | 3 | import com.digicert.validation.methods.email.prepare.EmailSource; 4 | import com.digicert.validation.common.ValidationState; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | /** 9 | * Represents the verification details required for email validation. 10 | * This class is used to hold the domain, email source, random value, email address, and validation state. 11 | *

12 | * The `EmailValidationRequest` class encapsulates all the necessary information required to perform 13 | * an email validation. This includes the domain to be validated, the source of the email, a random value 14 | * used for validation, the email address itself, and the current state of the validation process. 15 | * 16 | * @see EmailSource 17 | * @see ValidationState 18 | */ 19 | @Getter 20 | @Builder 21 | public class EmailValidationRequest { 22 | /** 23 | * The domain to be validated. 24 | *

25 | * The `domain` field represents the domain part of the email address that is being validated 26 | */ 27 | private final String domain; 28 | 29 | /** 30 | * The source of the email used for validation. 31 | *

32 | * The `emailSource` field indicates the origin of the email address being validated. This could 33 | * be a constructed list, or a DNS TXT record. 34 | */ 35 | private final EmailSource emailSource; 36 | 37 | /** 38 | * The random value used for validation. 39 | *

40 | * The `randomValue` field contains a randomly generated value that is sent to the email address 41 | * as part of the validation process. This value acts as a verification code that the user must enter 42 | * to confirm their ownership of the email address. 43 | */ 44 | private final String randomValue; 45 | 46 | /** 47 | * The email address submitted for validation. 48 | *

49 | * The `emailAddress` field holds the actual email address that is being validated. 50 | */ 51 | private final String emailAddress; 52 | 53 | /** 54 | * The current state of the validation process. 55 | *

56 | * The `validationState` field represents the current status of the email validation process. . 57 | */ 58 | private final ValidationState validationState; 59 | 60 | /** 61 | * Private constructor to prevent instantiation without using the builder. 62 | * 63 | * @param domain The domain to be validated. 64 | * @param emailSource The source of the email used for validation. 65 | * @param randomValue The random value used for validation. 66 | * @param emailAddress The email address used for validation. 67 | * @param validationState The current state of the validation process. 68 | */ 69 | private EmailValidationRequest(String domain, EmailSource emailSource, String randomValue, String emailAddress, ValidationState validationState) { 70 | this.domain = domain; 71 | this.emailSource = emailSource; 72 | this.randomValue = randomValue; 73 | this.emailAddress = emailAddress; 74 | this.validationState = validationState; 75 | } 76 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/file/prepare/FilePreparationRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.file.prepare; 2 | 3 | import com.digicert.validation.DcvConfiguration; 4 | import com.digicert.validation.enums.ChallengeType; 5 | 6 | /** 7 | * Represents a request for file validation preparation. 8 | *

9 | * This class encapsulates the details required to prepare for file validation. File validation is a method used to 10 | * verify the ownership or control of a domain by placing a specific file at a predetermined location on the domain's 11 | * web server. The FilePreparationRequest class includes the domain for which the authentication is being prepared, the 12 | * type of challenge used for validation, and an optional filename if the default will not be used. 13 | * 14 | * @param domain the domain for which the file validation preparation is requested 15 | *

16 | * This field specifies the domain name that is the subject of the file validation request. The domain name 17 | * must be a valid fully qualified domain name (FQDN). 18 | * @param filename the filename where the challenge value will be stored and queried on the server 19 | *

20 | * This field specifies the filename where the challenge value will be stored and queried on the server. 21 | * The file must be placed in the /.well-known/pki-validation/ directory of the domain's web server. 22 | * This field is optional, and will default to the value configured in {@link DcvConfiguration} if 23 | * not provided. 24 | * @param challengeType the type of challenge to use for validation 25 | *

26 | * This field indicates the type of challenge that will be used in the file validation process. The 27 | * challenge type is either RANDOM_VALUE or REQUEST_TOKEN. The RANDOM_VALUE challenge type requires 28 | * the customer to use a CA-generated random value, while the REQUEST_TOKEN challenge type requires 29 | * the customer to use a request token generated themselves according to the CA's specifications. 30 | */ 31 | public record FilePreparationRequest(String domain, String filename, ChallengeType challengeType) { 32 | 33 | /** 34 | * Constructs a new FilePreparationRequest with the specified domain and a default challenge type of RANDOM_VALUE. 35 | *

36 | * This constructor initializes a new instance of FilePreparationRequest using the provided domain name and sets the 37 | * challenge type to RANDOM_VALUE by default. Using the REQUEST_TOKEN challenge type does not require any 38 | * domain-specific data, so calling prepare for a domain name using RANDOM_VALUE is the most common use case for 39 | * file validation preparation. 40 | * 41 | * @param domain the domain for which the file validation preparation is requested 42 | *

43 | * This field specifies the domain name that is the subject of the file validation request. The domain name 44 | * must be a valid fully qualified domain name (FQDN). 45 | */ 46 | public FilePreparationRequest(String domain) { 47 | this(domain, null, ChallengeType.RANDOM_VALUE); 48 | } 49 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/file/prepare/FilePreparationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.file.prepare; 2 | 3 | import com.digicert.validation.common.ValidationState; 4 | import com.digicert.validation.enums.ChallengeType; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * Represents the response for a file validation preparation request. 11 | *

12 | * This response contains the necessary information to proceed with the file validation process. 13 | * It includes details such as the domain for which the authentication is being prepared, the type of challenge used, 14 | * the random value to be placed in the file, and the validation state of the preparation response. 15 | */ 16 | @Builder 17 | @Getter 18 | @ToString 19 | public class FilePreparationResponse { 20 | 21 | /** 22 | * The domain for which the file validation is being prepared. 23 | */ 24 | private final String domain; 25 | 26 | /** The type of challenge used for validation. */ 27 | private final ChallengeType challengeType; 28 | 29 | /** 30 | * The random value to be placed in the file. 31 | */ 32 | private final String randomValue; 33 | 34 | /** 35 | * The location of the file to be placed on the server. 36 | */ 37 | private final String fileLocation; 38 | 39 | /** 40 | * The validation state of the preparation response. 41 | */ 42 | private final ValidationState validationState; 43 | 44 | /** 45 | * Private constructor to prevent instantiation without using the builder. 46 | * 47 | * @param domain The domain for which the file validation is being prepared. 48 | * @param challengeType The type of challenge to use for validation. 49 | * @param randomValue The random value to be placed in the file. 50 | * @param fileLocation The location of the file to be placed on the server. 51 | * @param validationState The validation state of the preparation response. 52 | */ 53 | private FilePreparationResponse(String domain, ChallengeType challengeType, String randomValue, String fileLocation, ValidationState validationState) { 54 | this.domain = domain; 55 | this.challengeType = challengeType; 56 | this.randomValue = randomValue; 57 | this.fileLocation = fileLocation; 58 | this.validationState = validationState; 59 | } 60 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/file/validate/FileValidationRequest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.file.validate; 2 | 3 | import com.digicert.validation.challenges.RequestTokenData; 4 | import com.digicert.validation.common.ValidationState; 5 | import com.digicert.validation.enums.ChallengeType; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | 10 | /** 11 | * Represents a request for file-based authentication validation. 12 | *

13 | * This class contains information about the domain, filename, challenge type, random value, token key, token value, and validation state. 14 | * It is used to encapsulate all the necessary details required to perform a file-based domain control validation (DCV) request. 15 | */ 16 | @Getter 17 | @Builder 18 | @ToString 19 | public class FileValidationRequest { 20 | /** 21 | * The domain for which the file validation is being requested. 22 | */ 23 | private final String domain; 24 | 25 | /** 26 | * Optional: The filename to be used for file validation. 27 | */ 28 | private final String filename; 29 | 30 | /** 31 | * The type of challenge used for file validation. 32 | */ 33 | private final ChallengeType challengeType; 34 | 35 | /** The random value to be used for file validation. Only used for RANDOM_VALUE challenge type. */ 36 | private final String randomValue; 37 | 38 | /** The request token data to be used for file validation. Only used for REQUEST_TOKEN challenge type. */ 39 | private final RequestTokenData requestTokenData; 40 | 41 | /** 42 | * The validation state of the file validation request. 43 | */ 44 | private final ValidationState validationState; 45 | 46 | /** 47 | * Private constructor to prevent instantiation without using the builder. 48 | * Constructs a new FileValidationRequest with the specified parameters. 49 | * 50 | * @param domain The domain for which the file validation is being requested. 51 | * @param filename The filename to check for. 52 | * @param challengeType The type of challenge (random value or request token). 53 | * @param randomValue The random value to be used for file validation. 54 | * @param requestTokenData The data necessary to validate request tokens. 55 | * @param validationState The validation state of the file validation request. 56 | */ 57 | private FileValidationRequest(String domain, String filename, ChallengeType challengeType, String randomValue, RequestTokenData requestTokenData, ValidationState validationState) { 58 | this.domain = domain; 59 | this.filename = filename; 60 | this.challengeType = challengeType; 61 | this.randomValue = randomValue; 62 | this.requestTokenData = requestTokenData; 63 | this.validationState = validationState; 64 | } 65 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/methods/file/validate/FileValidationResponse.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.file.validate; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import com.digicert.validation.enums.ChallengeType; 5 | import lombok.Builder; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * Represents the response of a file validation process. 11 | *

12 | * This record encapsulates the outcome of a file validation request, including the validation success, 13 | * domain, file URL, challenge type, and any errors encountered. 14 | * 15 | * @param isValid Indicates whether the validation is successful. 16 | * @param domain The domain associated with the validation. 17 | * @param fileUrl The URL of the file used for validation. 18 | * @param challengeType The type of challenge used in the validation (RANDOM_VALUE or REQUEST_TOKEN). 19 | * @param validRandomValue The valid random value used in the validation (null if not RANDOM_VALUE). 20 | * @param validRequestToken The valid request token used in the validation (null if not REQUEST_TOKEN). 21 | * @param errors The errors found during the validation process (null if no errors). 22 | */ 23 | @Builder 24 | public record FileValidationResponse(boolean isValid, 25 | String domain, 26 | String fileUrl, 27 | ChallengeType challengeType, 28 | String validRandomValue, 29 | String validRequestToken, 30 | Set errors) { } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/PslData.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * This class holds the data structures used for Public Suffix List (PSL) validation. 7 | * It contains tries for registry suffixes, wildcards, and exceptions, as well as private suffixes and wildcards. 8 | *

9 | * The Public Suffix List (PSL) is a critical component in domain name validation, helping to determine the boundaries 10 | * of registrable domains. This class encapsulates the necessary data structures to efficiently manage and query 11 | * the PSL, ensuring accurate validation of domain names against the list of known suffixes, wildcards, and exceptions. 12 | */ 13 | @Getter 14 | public class PslData { 15 | /** 16 | * Trie for registry suffixes. 17 | *

18 | * This trie holds the standard registry suffixes, which are the top-level domains (TLDs) and other recognized 19 | * suffixes that are part of the public registry. It is used to validate whether a given domain name ends with 20 | * a recognized suffix, ensuring compliance with the public suffix rules. 21 | */ 22 | private final Trie registrySuffixTrie = new Trie(); 23 | 24 | /** 25 | * Trie for registry wildcards. 26 | *

27 | * This trie contains wildcard entries for registry suffixes, allowing for the validation of domains that match 28 | * wildcard patterns. Wildcards are used to represent multiple possible domain endings, providing flexibility 29 | * in the validation process for domains that fall under wildcard rules. 30 | */ 31 | private final Trie registryWildcardTrie = new Trie(); 32 | 33 | /** 34 | * Trie for registry exceptions. 35 | *

36 | * This trie stores exceptions to the standard registry suffix rules. Exceptions are specific domain names that 37 | * do not follow the general suffix patterns and need special handling. This trie ensures that such exceptions 38 | * are correctly identified and processed during domain validation. 39 | */ 40 | private final Trie registryExceptionTrie = new Trie(); 41 | 42 | /** 43 | * Trie for private suffixes. 44 | *

45 | * This trie holds private suffixes, which are domain suffixes managed by private entities rather than public 46 | * registries. These suffixes are used to validate domains that fall under private management, ensuring that 47 | * they are correctly recognized and validated according to the private suffix rules. 48 | */ 49 | private final Trie privateSuffixTrie = new Trie(); 50 | 51 | /** 52 | * Trie for private wildcards. 53 | *

54 | * This trie contains wildcard entries for private suffixes, similar to the registry wildcards but for privately 55 | * managed domains. It allows for the validation of domains that match wildcard patterns within the private 56 | * suffix space, providing flexibility and accuracy in the validation process. 57 | */ 58 | private final Trie privateWildcardTrie = new Trie(); 59 | 60 | /** 61 | * Default constructor for PslData. 62 | * Initializes the tries for registry suffixes, wildcards, exceptions, private suffixes, and wildcards. 63 | *

64 | * The constructor sets up the necessary data structures for PSL validation, ensuring that each trie is 65 | * properly initialized and ready for use. This setup is crucial for the efficient and accurate validation 66 | * of domain names against the public and private suffix lists, including handling of wildcards and exceptions. 67 | */ 68 | public PslData() { 69 | // Default constructor 70 | } 71 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/PslDataParser.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.Reader; 6 | import java.net.IDN; 7 | 8 | /** 9 | * Parser for public suffix list data. 10 | *

11 | * This class provides functionality to parse public suffix list (PSL) data files. The PSL data is used to determine 12 | * the domain suffixes that are recognized as public or private, including handling of wildcard and exception rules. 13 | * The parser reads the PSL data file line by line, categorizing each line into the appropriate trie structure 14 | * based on whether it is a public or private suffix, and whether it is an exact match, wildcard, or exception rule. 15 | * The parsed data is then stored in a `PslData` object for further use in domain validation processes. 16 | */ 17 | public class PslDataParser { 18 | 19 | /** 20 | * Private constructor to prevent instantiation of this class. 21 | *

22 | * This class is designed to be used in a static context, and therefore, instantiation is not necessary. 23 | * The private constructor ensures that no instances of this class can be created, enforcing the static 24 | * nature of its methods and usage. 25 | */ 26 | private PslDataParser() {} 27 | 28 | /** 29 | * Parse the given public suffix list data file and return the parsed data. 30 | *

31 | * This method takes a `Reader` object as input, which is expected to provide the contents of a valid public 32 | * suffix list data file. The method processes the file line by line, categorizing each line into the appropriate 33 | * trie structure within a `PslData` object. The file format is defined by the public suffix list project and 34 | * includes rules for exact matches, wildcards, and exceptions. The method handles both public and private 35 | * suffixes, ensuring that all relevant data is parsed and stored correctly. 36 | * 37 | * @param reader Reader for the public suffix list data file 38 | * @return `PslData` object containing the parsed data 39 | * @throws IllegalStateException if an I/O error occurs while reading the file 40 | */ 41 | public static PslData parsePslData(Reader reader) { 42 | PslData pslData = new PslData(); 43 | 44 | boolean isPublicList = true; 45 | // Maybe hove a Trie provider that can be injected per line 46 | try (BufferedReader bufferedReader = new BufferedReader(reader)) { 47 | String line; 48 | while ((line = bufferedReader.readLine()) != null) { 49 | line = line.trim(); 50 | 51 | // Skip comments and empty lines 52 | if (line.isEmpty()) { 53 | continue; 54 | } 55 | 56 | if (line.startsWith("//")) { 57 | // Check to see if the next line begins the private suffix list 58 | if (line.contains("===BEGIN PRIVATE DOMAINS===")) { 59 | isPublicList = false; 60 | } 61 | continue; 62 | } 63 | 64 | if (isPublicList) { 65 | if (line.startsWith("!")) { 66 | // Exception rule 67 | addToTrie(line.substring(1), pslData.getRegistryExceptionTrie()); 68 | } else if (line.startsWith("*.")) { 69 | // Wildcard rule 70 | addToTrie(line.substring(2), pslData.getRegistryWildcardTrie()); 71 | } else { 72 | // Exact rule 73 | addToTrie(line, pslData.getRegistrySuffixTrie()); 74 | } 75 | } else { 76 | // Private domain rule 77 | if (line.startsWith("*.")) { 78 | // Wildcard rule 79 | addToTrie(line.substring(2), pslData.getPrivateWildcardTrie()); 80 | } else { 81 | // Exact rule 82 | addToTrie(line, pslData.getPrivateSuffixTrie()); 83 | } 84 | } 85 | } 86 | 87 | return pslData; 88 | } catch (IOException e) { 89 | throw new IllegalStateException("Unable to parse given psl data file", e); 90 | } 91 | } 92 | 93 | /** 94 | * Add the given substring to the given trie. 95 | *

96 | * This helper method inserts a substring into the specified `Trie` object. It also handles the conversion 97 | * of Unicode domain names to their ASCII-compatible encoding (punycode) and inserts the punycode representation 98 | * into the trie as well. This ensures that both Unicode and punycode versions of the domain names are recognized 99 | * and validated correctly. 100 | * 101 | * @param substring Substring to add to the trie 102 | * @param trie Trie to add the substring to 103 | */ 104 | private static void addToTrie(String substring, Trie trie) { 105 | trie.insert(substring); 106 | 107 | // Unicode domains are also converted to punycode and inserted into the trie 108 | String punycode = IDN.toASCII(substring); 109 | if (!punycode.equals(substring)) { 110 | trie.insert(punycode); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/PslDataProvider.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import lombok.Getter; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Reader; 9 | import java.util.Objects; 10 | 11 | /** 12 | * Provides access to Public Suffix List (PSL) data. 13 | * This class is implemented as a singleton. 14 | *

15 | * The `PslDataProvider` class is responsible for managing and providing access to the Public Suffix List (PSL) data. 16 | * The PSL data is used to determine the domain suffixes that are considered public, which is essential for various 17 | * domain validation and security operations. This class ensures that only one instance of the PSL data is loaded 18 | * and provides methods to load, access, and reset the PSL data. 19 | */ 20 | @Getter 21 | @Slf4j 22 | public class PslDataProvider { 23 | 24 | /** 25 | * The singleton instance of the PslDataProvider. 26 | */ 27 | private static final PslDataProvider INSTANCE = new PslDataProvider(); 28 | 29 | /** 30 | * Private constructor to prevent instantiation. 31 | */ 32 | private PslDataProvider() {} 33 | 34 | /** 35 | * Returns the singleton instance of the PslDataProvider. 36 | *

37 | * This method provides access to the single instance of the `PslDataProvider` class. It ensures that the same 38 | * instance is returned every time it is called, maintaining the Singleton pattern. This method is thread-safe 39 | * and guarantees that only one instance of the class is created, even in a multithreaded environment. 40 | * 41 | * @return the singleton instance 42 | */ 43 | public static PslDataProvider getInstance() { 44 | return INSTANCE; 45 | } 46 | 47 | /** 48 | * The PSL data. 49 | *

50 | * This field holds the Public Suffix List (PSL) data that is loaded by the `PslDataProvider`. The PSL data is 51 | * essential for determining the public suffixes of domain names. This field is initialized when the PSL data 52 | * is loaded using the `loadPslData` or `loadDefaultData` methods. 53 | */ 54 | private PslData pslData; 55 | 56 | /** 57 | * Loads PSL data from the provided reader. 58 | *

59 | * This method loads the Public Suffix List (PSL) data from the provided `Reader` object. It uses the `PslDataParser` 60 | * to parse the data and initialize the `pslData` field. This method allows for flexibility in loading PSL data 61 | * from various sources, such as files, network streams, or other input sources. 62 | * 63 | * @param reader the reader to load PSL data from 64 | */ 65 | public void loadPslData(Reader reader) { 66 | pslData = PslDataParser.parsePslData(reader); 67 | } 68 | 69 | /** 70 | * Loads the default PSL data from the resource file. 71 | * If PSL data is already loaded, this method does nothing. 72 | *

73 | * This method loads the default Public Suffix List (PSL) data from a resource file included in the application's 74 | * classpath. If the PSL data is already loaded, the method does nothing to avoid reloading the data. If the 75 | * resource file cannot be found or an error occurs during loading, an `IllegalStateException` is thrown. 76 | * 77 | * @throws IllegalStateException if the default PSL data cannot be loaded 78 | */ 79 | public void loadDefaultData() { 80 | if (pslData != null) { 81 | return; 82 | } 83 | 84 | InputStream resourceAsStream = Objects.requireNonNull(this.getClass().getClassLoader().getResourceAsStream("public_suffix_list.dat")); 85 | try (InputStreamReader reader = new InputStreamReader(resourceAsStream)) { 86 | loadPslData(reader); 87 | } catch (Exception e) { 88 | throw new IllegalStateException("Failed to load default PSL data", e); 89 | } 90 | } 91 | 92 | /** 93 | * Resets the PSL data, clearing any loaded data. 94 | *

95 | * This method clears the currently loaded Public Suffix List (PSL) data by setting the `pslData` field to `null`. 96 | * This can be useful in scenarios where the PSL data needs to be reloaded or refreshed. After calling this method, 97 | * the `PslDataProvider` will need to load the PSL data again before it can be used. 98 | */ 99 | void resetPslData() { 100 | pslData = null; 101 | } 102 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/PublicSuffixType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package com.digicert.validation.psl; 16 | 17 | /** 18 | * Enum representing the type of public suffix. 19 | *

20 | * This enum defines the types of public suffixes that can be encountered. Public suffixes are domain names under which 21 | * Internet users can directly register names. They are used to determine the boundaries of domain names for various 22 | * purposes, such as cookie setting and domain name validation. The `PublicSuffixType` enum provides a way to categorize 23 | * these suffixes based on their characteristics and usage. 24 | */ 25 | public enum PublicSuffixType { 26 | 27 | /** 28 | * Public suffix that is backed by an ICANN-style domain name registry. 29 | *

30 | * The `REGISTRY_ONLY` type represents public suffixes that are managed by official domain name registries, typically 31 | * under the oversight of the Internet Corporation for Assigned Names and Numbers (ICANN). These suffixes follow 32 | * strict policies and guidelines for domain name registration and management. Examples include `.com`, `.org`, and 33 | * country-code top-level domains (ccTLDs) like `.uk` and `.jp`. 34 | */ 35 | REGISTRY_ONLY(), 36 | 37 | /** 38 | * Any type of public suffix. 39 | *

40 | * The `ANY` type encompasses all kinds of public suffixes, including those managed by ICANN-style registries as well 41 | * as other types of suffixes that may not follow the same stringent policies. This type is more inclusive and can 42 | * cover a broader range of domain names, including private or custom suffixes used by organizations for internal 43 | * purposes. It is useful for applications that need to handle a wide variety of domain name structures. 44 | */ 45 | ANY() 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/Trie.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | /** 4 | * A Trie (prefix tree) implementation for storing and searching strings. 5 | * The Trie supports insertion and search operations. 6 | *

7 | * A Trie is a tree-like data structure that is used to store a dynamic set of strings, where the keys are usually strings. 8 | * It is particularly useful for tasks such as autocomplete, spell checking, and IP routing. The Trie allows for efficient 9 | * retrieval of strings by their prefixes, making it a powerful tool for various text processing applications. 10 | */ 11 | public class Trie { 12 | 13 | /** 14 | * Represents the root node in the Trie. 15 | */ 16 | private final TrieNode root; 17 | 18 | /** 19 | * Constructs a new Trie with an empty root node. 20 | *

21 | * This constructor initializes the Trie with a root node that has no children. The root node acts as a placeholder 22 | * and does not store any character. It is the foundation upon which the rest of the Trie is built. 23 | */ 24 | public Trie() { 25 | root = new TrieNode(); 26 | } 27 | 28 | /** 29 | * Inserts a word into the Trie. 30 | *

31 | * This method takes a string as input and inserts it into the Trie. It iterates over each character in the word, 32 | * creating a new node if the character is not already present in the Trie. Once all characters are inserted, the 33 | * last node is marked as the end of the word. This allows the Trie to store and recognize complete words. 34 | * 35 | * @param word the word to be inserted 36 | */ 37 | public void insert(String word) { 38 | TrieNode node = root; 39 | for (char c : word.toCharArray()) { 40 | node = node.children.computeIfAbsent(c, k -> new TrieNode()); 41 | } 42 | node.isEndOfWord = true; 43 | } 44 | 45 | /** 46 | * Searches for a word in the Trie. 47 | *

48 | * This method takes a string as input and searches for it in the Trie. It traverses the Trie nodes corresponding 49 | * to each character in the word. If it reaches a node that does not exist or if the final node is not marked as 50 | * the end of a word, the search returns false. Otherwise, it returns true, indicating that the word is present 51 | * in the Trie. 52 | * 53 | * @param word the word to search for 54 | * @return {@code true} if the word is found and {@code false} otherwise 55 | */ 56 | public boolean search(String word) { 57 | TrieNode node = root; 58 | for (char c : word.toCharArray()) { 59 | node = node.children.get(c); 60 | if (node == null) { 61 | return false; 62 | } 63 | } 64 | return node.isEndOfWord; 65 | } 66 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/psl/TrieNode.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A node in the Trie. Each node represents a single character in a word. 8 | *

9 | * This class is used to construct a Trie data structure, which is a type of search tree used to store a dynamic set or 10 | * associative array where the keys are usually strings. Each node in the Trie represents a single character of a word, 11 | * and the path from the root to a node represents a prefix of the word. 12 | */ 13 | public class TrieNode { 14 | /** 15 | * The children of this node, where each key is a character and the value is the corresponding child node. 16 | *

17 | * This map is used to store the links to the child nodes, allowing the Trie to branch out for each character in the alphabet. 18 | * The keys in this map are characters, and the values are the TrieNode instances that represent the next character in the sequence. 19 | */ 20 | final Map children = new HashMap<>(); 21 | 22 | /** 23 | * Indicates whether this node represents the end of a word. 24 | *

25 | * This boolean flag is used to mark the end of a valid word in the Trie. 26 | * When this flag is true, it means that the path from the root to this node forms a complete word that is stored in the Trie. 27 | */ 28 | boolean isEndOfWord = false; 29 | 30 | /** 31 | * Default constructor for TrieNode. 32 | */ 33 | public TrieNode() { 34 | // Default constructor 35 | } 36 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/random/BasicRandomValueGenerator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.random; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.IntStream; 6 | 7 | /** 8 | * A basic implementation of the {@link RandomValueGenerator} interface that generates random alphanumeric strings. 9 | * This class uses a secure random number generator to produce random strings composed of alphanumeric characters. 10 | * It is designed to provide a simple and effective way to generate random values for various purposes, such as tokens, passwords, or other unique identifiers. 11 | * The generated strings are of a fixed length and are created using a specified character set. 12 | */ 13 | public class BasicRandomValueGenerator implements RandomValueGenerator { 14 | 15 | /** 16 | * The charset used for generating random strings. 17 | *

18 | * This string contains all the alphanumeric characters (digits 0-9, lowercase letters a-z, and uppercase letters A-Z). 19 | * It serves as the pool of characters from which the random string will be generated. 20 | */ 21 | private static final String ALPHANUMERIC_CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 22 | 23 | /** 24 | * Default constructor for BasicRandomValueGenerator. 25 | */ 26 | public BasicRandomValueGenerator() { 27 | // Default constructor 28 | } 29 | 30 | /** 31 | * Returns the charset used for generating random strings. 32 | * 33 | * @return A string representing the alphanumeric charset. 34 | */ 35 | @Override 36 | public String getCharset() { 37 | return ALPHANUMERIC_CHARSET; 38 | } 39 | 40 | /** 41 | * Generates a random alphanumeric string of length 32. 42 | *

43 | * This method creates a random string composed of characters from the alphanumeric charset. 44 | * The length of the generated string is fixed at 32 characters. 45 | * 46 | * @return A randomly generated alphanumeric string. 47 | */ 48 | @Override 49 | public String generateRandomString() { 50 | return generateString(getCharset(), 32); 51 | } 52 | 53 | /** 54 | * Generates a random string of the specified length using the given charset. 55 | *

56 | * This method takes a charset and a length as parameters and produces a random string of the specified length. 57 | * It uses a secure random number generator to select characters from the charset and build the string. 58 | * 59 | * @param charset The charset to use for generating the random string. 60 | * @param length The length of the random string to generate. 61 | * @return A randomly generated string. 62 | */ 63 | private String generateString(String charset, int length) { 64 | var random = new SecureRandom(); 65 | int charsetLength = charset.length(); 66 | 67 | return IntStream.range(0, length) 68 | .mapToObj(it -> String.valueOf(charset.charAt(random.nextInt(charsetLength)))) 69 | .collect(Collectors.joining()); 70 | } 71 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/random/RandomValueGenerator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.random; 2 | 3 | /** 4 | * Interface for generating random values. 5 | *

6 | * This interface provides methods to get the charset used for generating random strings 7 | * and to generate random alphanumeric strings. 8 | */ 9 | public interface RandomValueGenerator { 10 | 11 | /** 12 | * Returns the charset used for generating random strings. 13 | *

14 | * This method provides access to the set of characters that can be used to generate random strings. 15 | * 16 | * @return A string representing the charset. 17 | */ 18 | String getCharset(); 19 | 20 | /** 21 | * Generates a random alphanumeric string. 22 | *

23 | * This method is responsible for creating a random string composed of characters from the defined charset. 24 | * 25 | * @return A randomly generated alphanumeric string. 26 | */ 27 | String generateRandomString(); 28 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/random/RandomValueVerifier.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.random; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import com.digicert.validation.enums.DcvError; 5 | import com.digicert.validation.exceptions.DcvException; 6 | import com.digicert.validation.exceptions.InputException; 7 | import com.digicert.validation.exceptions.ValidationException; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.time.Duration; 11 | import java.time.Instant; 12 | 13 | /** 14 | * Verifies the validity and entropy of random values. 15 | *

16 | * This class ensures that random values meet the required entropy and are within the validity period. 17 | */ 18 | public class RandomValueVerifier { 19 | 20 | /** 21 | * BR version 1.3.8 requires 112 bits of entropy 22 | *

23 | * This constant defines the minimum entropy value required for a random value to be considered secure. 24 | * The value is based on the Baseline Requirements (BR) version 1.3.8, which mandates a minimum of 112 bits 25 | * of entropy for cryptographic security. 26 | */ 27 | private static final int MIN_ALLOWED_ENTROPY_VALUE = 112; 28 | 29 | /** 30 | * Calculate and cache the entropy per character in the constructor 31 | * to save time when validating random values 32 | *

33 | * This field stores the entropy per character, which is calculated once during the construction of the object. 34 | */ 35 | private final double perCharacterEntropy; 36 | 37 | /** 38 | * Default validity period for random value is 30 days 39 | *

40 | * This field defines the default validity period for a random value, which is set to 30 days. 41 | */ 42 | private final int randomValueValidityPeriod; 43 | 44 | /** 45 | * Constructs a new RandomValueVerifier with the specified configuration. 46 | * 47 | * @param dcvContext context where we can find the needed dependencies / configuration 48 | *

49 | * This constructor initializes the RandomValueVerifier with the given DCV context. 50 | */ 51 | public RandomValueVerifier(DcvContext dcvContext) { 52 | this.randomValueValidityPeriod = dcvContext.getDcvConfiguration().getRandomValueValidityPeriod(); 53 | 54 | // ensure that the randomValueGenerator is not null before making this call 55 | perCharacterEntropy = calculatePerCharacterEntropy(dcvContext.get(RandomValueGenerator.class)); 56 | } 57 | 58 | /** 59 | * Verifies the given random value for validity and entropy. 60 | * 61 | * @param randomValue The random value to verify. 62 | * @param prepareTime The time when the random value was prepared. 63 | * @throws DcvException If the random value is invalid or has insufficient entropy. 64 | */ 65 | public void verifyRandomValue(String randomValue, Instant prepareTime) throws DcvException { 66 | if (StringUtils.isEmpty(randomValue)) { 67 | throw new InputException(DcvError.RANDOM_VALUE_REQUIRED); 68 | } 69 | 70 | // Verify that the random value is not expired 71 | Duration validityPeriod = Duration.ofDays(randomValueValidityPeriod); 72 | if (prepareTime.plus(validityPeriod).isBefore(Instant.now())) { 73 | throw new ValidationException(DcvError.RANDOM_VALUE_EXPIRED); 74 | } 75 | 76 | // Validate random value 77 | if (!isEntropySufficient(randomValue)) { 78 | throw new InputException(DcvError.RANDOM_VALUE_INSUFFICIENT_ENTROPY); 79 | } 80 | } 81 | 82 | /** 83 | * Calculates the entropy per character for the given random value generator. 84 | * 85 | * @param randomValueGenerator The random value generator to use for calculating entropy. 86 | * @return The entropy per character. 87 | */ 88 | private double calculatePerCharacterEntropy(RandomValueGenerator randomValueGenerator) { 89 | long numUniqueChars = randomValueGenerator.getCharset() 90 | .chars() 91 | .distinct() 92 | .count(); 93 | 94 | return Math.log(numUniqueChars) / Math.log(2.0); 95 | } 96 | 97 | /** 98 | * Checks if the given random value has sufficient entropy. 99 | * 100 | * @param randomValue The random value to check. 101 | * @return True if the random value has sufficient entropy, false otherwise. 102 | *

103 | * This method evaluates whether the provided random value has sufficient entropy to meet security requirements. 104 | */ 105 | private boolean isEntropySufficient(String randomValue) { 106 | // Entropy Calculation: [log2 of character set * string len = entropy bits] 107 | return perCharacterEntropy * randomValue.length() >= MIN_ALLOWED_ENTROPY_VALUE; 108 | } 109 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/utils/FilenameUtils.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | /** 6 | * Utility class for validating filenames. 7 | *

8 | * The `FilenameUtils` class provides static methods for validating filenames. 9 | * It ensures that filenames contain only allowed characters and do not exceed 10 | * a maximum length. This class is useful for validating filenames before 11 | * using them in file operations or storage. 12 | */ 13 | public class FilenameUtils { 14 | /** Regex for one or more of the allowed char set for filename. */ 15 | private static final Pattern FILENAME_CHAR_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]+$"); 16 | 17 | /** 18 | * Private constructor to prevent instantiation. 19 | */ 20 | private FilenameUtils() {} 21 | 22 | /** 23 | * Validates a filename to ensure it contains only allowed characters 24 | * and does not exceed a maximum length. 25 | * 26 | * @param fileName the filename to validate 27 | * @throws IllegalArgumentException if the filename is null, empty, contains 28 | * invalid characters, or exceeds the maximum length 29 | */ 30 | public static void validateFilename(String fileName) throws IllegalArgumentException { 31 | if (fileName == null || fileName.isEmpty()) { 32 | throw new IllegalArgumentException("fileName cannot be null or empty"); 33 | } 34 | if (!FILENAME_CHAR_PATTERN.matcher(fileName).matches()) { 35 | throw new IllegalArgumentException("fileName contains invalid characters"); 36 | } 37 | 38 | // Validate file name length 39 | int maxLength = 64; 40 | if (fileName.length() > maxLength) { 41 | throw new IllegalArgumentException("fileName exceeds maximum length of " + maxLength); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/utils/NoopPslOverrideSupplier.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * A basic implementation of the PslOverrideSupplier interface. 7 | *

8 | * This implementation of the PslOverrideSupplier interface always returns an empty Optional, indicating that no 9 | * overrides are available. 10 | */ 11 | public class NoopPslOverrideSupplier implements PslOverrideSupplier { 12 | 13 | /** 14 | * Default constructor for BasicPslOverrideSupplier. 15 | */ 16 | public NoopPslOverrideSupplier() { 17 | // Default constructor 18 | } 19 | 20 | /** 21 | * Returns an empty Optional as this class does not have any overrides to supply. 22 | * 23 | * @param domain the domain for which to get the public suffix override 24 | * @return an empty Optional 25 | */ 26 | @Override 27 | public Optional getPublicSuffixOverride(String domain) { 28 | return Optional.empty(); 29 | } 30 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/utils/PslOverrideSupplier.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import com.digicert.validation.exceptions.InputException; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * Interface for providing overrides for the public suffix of a domain. 9 | *

10 | * Overrides are considered when determining the base domain of a given domain. Overrides can be used in special cases 11 | * to change which part of a domain should be considered its TLD. For example, blogspot.com is an addressable domain 12 | * even though it is normally treated like a TLD. As such, "example.blogspot.com" would be treated as a base domain, 13 | * and "blogspot.com" would be considered invalid. An override could be set up to consider "blogspot.com" as a base 14 | * domain while still treating "example.blogspot.com" as a base domain as well. 15 | */ 16 | public interface PslOverrideSupplier { 17 | 18 | /** 19 | * Returns an override for the public suffix of a domain. 20 | *

21 | * This method is called when determining the base domain of an FQDN, and checks if there is an override for 22 | * the public suffix of the FQDN. If there is an override present, it returns an Optional containing the 23 | * portion of the FQDN to treat as a TLD. If no override is needed, it returns an empty Optional. 24 | *

25 | * For example, blogspot.com is normally treated as a TLD. If this method were to return an optional containing 26 | * "com", the library would report that "blogspot.com" is its own base domain. Lacking such an override, the library 27 | * will throw an exception instead. 28 | *

29 | * Overrides do not have to be the same for a domain and its subdomains. Following the above example, when this 30 | * method is given "example.blogspot.com" it could return an empty optional, allowing for "example.blogspot.com" 31 | * to still be considered its own base domain. 32 | *

33 | * To have a domain be considered invalid, simply throw an InputException. 34 | * 35 | * @param domain the domain to check for an override 36 | * @return an Optional containing the portion of the domain to treat as a TLD, or an empty Optional if no override is present 37 | * @throws InputException if the TLD should be invalid 38 | */ 39 | Optional getPublicSuffixOverride(String domain) throws InputException; 40 | } -------------------------------------------------------------------------------- /library/src/main/java/com/digicert/validation/utils/StateValidationUtils.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import com.digicert.validation.common.ValidationState; 4 | import com.digicert.validation.enums.DcvError; 5 | import com.digicert.validation.enums.DcvMethod; 6 | import com.digicert.validation.exceptions.DcvException; 7 | import com.digicert.validation.exceptions.InputException; 8 | 9 | /** 10 | * Utility class for validating the state of a validation process. 11 | *

12 | * The StateValidationUtils class provides a set of static methods to ensure that the validation state 13 | * of a process is correctly configured. This utility class is designed to be used throughout the application 14 | * to perform consistent and reliable checks on the validation state, ensuring that all required fields are 15 | * present and correctly set. 16 | */ 17 | public class StateValidationUtils { 18 | 19 | /** Private constructor to prevent instantiation. */ 20 | private StateValidationUtils() {} 21 | 22 | /** 23 | * Verifies the given validation state. 24 | *

25 | * This method performs a series of checks on the provided ValidationState object to ensure that it is valid. 26 | * It verifies that the validation state is not null and that all required fields (domain, dcvMethod, 27 | * and prepareTime) are present and not null. 28 | * 29 | * @param validationState the validation state to verify 30 | * @param expectedMethod the expected DcvMethod for the validation state 31 | * @throws DcvException if the validation state is invalid 32 | */ 33 | public static void verifyValidationState(ValidationState validationState, DcvMethod expectedMethod) throws DcvException { 34 | if(validationState == null) { 35 | throw new InputException(DcvError.VALIDATION_STATE_REQUIRED); 36 | } 37 | 38 | if (validationState.domain() == null) { 39 | throw new InputException(DcvError.VALIDATION_STATE_DOMAIN_REQUIRED); 40 | } 41 | 42 | if (validationState.dcvMethod() == null) { 43 | throw new InputException(DcvError.VALIDATION_STATE_DCV_METHOD_REQUIRED); 44 | } 45 | 46 | if (!validationState.dcvMethod().equals(expectedMethod)) { 47 | throw new InputException(DcvError.INVALID_DCV_METHOD); 48 | } 49 | 50 | if (validationState.prepareTime() == null) { 51 | throw new InputException(DcvError.VALIDATION_STATE_PREPARE_TIME_REQUIRED); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/DcvManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class DcvManagerTest { 11 | 12 | private DcvConfiguration dcvConfiguration; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 17 | .dnsServers(List.of("8.8.8.8")) 18 | .build(); 19 | } 20 | 21 | @Test 22 | void testBuilderWithValidDcvConfiguration() { 23 | dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 24 | .dnsServers(List.of("8.8.8.8")) 25 | .build(); 26 | 27 | DcvManager dcvManager = new DcvManager.Builder() 28 | .withDcvConfiguration(dcvConfiguration) 29 | .build(); 30 | 31 | assertNotNull(dcvManager); 32 | assertNotNull(dcvManager.getDnsValidator()); 33 | assertNotNull(dcvManager.getEmailValidator()); 34 | assertNotNull(dcvManager.getFileValidator()); 35 | } 36 | 37 | @Test 38 | void testBuilderWithNullDcvConfiguration() { 39 | IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new DcvManager.Builder().withDcvConfiguration(null)); 40 | assertEquals("DcvConfiguration cannot be null", exception.getMessage()); 41 | } 42 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/challenges/BasicRandomValueValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | import com.digicert.validation.enums.DcvError; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.Arguments; 7 | import org.junit.jupiter.params.provider.MethodSource; 8 | 9 | import java.util.stream.Stream; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | class BasicRandomValueValidatorTest { 15 | 16 | @Test 17 | void testBasicRandomValue_validate_happyDay() { 18 | String randomValue = "randomValue"; 19 | String textBody = "some text body with randomValue in it"; 20 | BasicRandomValueValidator basicRandomValueValidator = new BasicRandomValueValidator(); 21 | ChallengeValidationResponse response = basicRandomValueValidator.validate(randomValue, textBody); 22 | assertEquals(randomValue, response.challengeValue().orElseThrow()); 23 | assertEquals(0, response.errors().size()); 24 | } 25 | 26 | static Stream invalidArgs() { 27 | return Stream.of( 28 | Arguments.of("randomValue", "", DcvError.RANDOM_VALUE_EMPTY_TEXT_BODY), 29 | Arguments.of("abc", "some text body with no value in it", DcvError.RANDOM_VALUE_NOT_FOUND) 30 | ); 31 | } 32 | 33 | @ParameterizedTest(name = "Invalid Params {index} : {1}") 34 | @MethodSource("invalidArgs") 35 | void testBasicRandomValue_validate_invalidParams(String randomValue, String textBody, DcvError dcvError) { 36 | 37 | BasicRandomValueValidator basicRandomValueValidator = new BasicRandomValueValidator(); 38 | ChallengeValidationResponse response = basicRandomValueValidator.validate(randomValue, textBody); 39 | assertEquals(1, response.errors().size()); 40 | assertTrue(response.errors().contains(dcvError)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/challenges/CSRGenerator.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.challenges; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.bouncycastle.asn1.x500.X500Name; 5 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 6 | import org.bouncycastle.openssl.jcajce.JcaPEMWriter; 7 | import org.bouncycastle.operator.ContentSigner; 8 | import org.bouncycastle.operator.OperatorCreationException; 9 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 10 | import org.bouncycastle.pkcs.PKCS10CertificationRequest; 11 | import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; 12 | import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; 13 | 14 | import java.io.IOException; 15 | import java.io.StringWriter; 16 | import java.security.KeyPair; 17 | import java.security.KeyPairGenerator; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.security.Security; 20 | 21 | /** 22 | * Generates a CSR using the BouncyCastle library 23 | */ 24 | @Slf4j 25 | public class CSRGenerator { 26 | 27 | public CSRGenerator(){ 28 | Security.addProvider(new BouncyCastleProvider()); 29 | } 30 | 31 | /** 32 | * Generates a key pair if one has not already been generated. This is not in the constructor because of the exception handling. 33 | */ 34 | KeyPair generateKeyPair() throws NoSuchAlgorithmException { 35 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 36 | keyPairGenerator.initialize(2048); 37 | return keyPairGenerator.generateKeyPair(); 38 | } 39 | 40 | /*** 41 | * Generates a CSR using the provided common name 42 | * @param commonName the common name to use in the CSR 43 | * @return a CSR in PEM format 44 | */ 45 | public String generateCSR(String commonName) throws NoSuchAlgorithmException, IOException, OperatorCreationException { 46 | 47 | KeyPair keyPair = generateKeyPair(); 48 | StringWriter writer = new StringWriter(); 49 | try (JcaPEMWriter pemWriter = new JcaPEMWriter(writer)) { 50 | 51 | X500Name subject = new X500Name("CN=" + commonName); 52 | PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); 53 | JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA"); 54 | ContentSigner signer = csBuilder.build(keyPair.getPrivate()); 55 | PKCS10CertificationRequest certificationRequest = p10Builder.build(signer); 56 | 57 | pemWriter.writeObject(certificationRequest); 58 | } 59 | 60 | return writer.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/client/dns/DnsClientTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.dns; 2 | 3 | import com.digicert.validation.DcvConfiguration; 4 | import com.digicert.validation.DcvContext; 5 | import com.digicert.validation.enums.DnsType; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.Arguments; 11 | import org.junit.jupiter.params.provider.MethodSource; 12 | import org.mockito.Mock; 13 | import org.mockito.MockitoAnnotations; 14 | import org.mockito.junit.jupiter.MockitoExtension; 15 | import org.xbill.DNS.Record; 16 | import org.xbill.DNS.*; 17 | 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.stream.Stream; 21 | 22 | import static org.junit.jupiter.api.Assertions.*; 23 | import static org.mockito.Mockito.mock; 24 | import static org.mockito.Mockito.when; 25 | 26 | @ExtendWith(MockitoExtension.class) 27 | class DnsClientTest { 28 | 29 | DcvConfiguration dcvConfiguration; 30 | 31 | @Mock 32 | ExtendedResolver extendedResolver; 33 | 34 | @Mock 35 | Lookup lookup; 36 | 37 | DnsClient dnsClient; 38 | 39 | @BeforeEach 40 | void setUp() { 41 | MockitoAnnotations.openMocks(this); 42 | dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 43 | .dnsServers(List.of("8.8.8.8")) 44 | .dnsTimeout(5000) 45 | .build(); 46 | DcvContext dcvContext = new DcvContext(dcvConfiguration); 47 | dnsClient = new DnsClient(dcvContext) { 48 | @Override 49 | protected ExtendedResolver createResolver(String server, Integer dnsPort) { 50 | return extendedResolver; 51 | } 52 | 53 | @Override 54 | protected Lookup createLookup(String domain, int type) { 55 | return lookup; 56 | } 57 | }; 58 | } 59 | 60 | static Stream provideDoubleTypeDnsData() { 61 | return Stream.of( 62 | Arguments.of("example.com.", DnsType.TXT, Type.TXT), 63 | Arguments.of("example.com.", DnsType.CNAME, Type.CNAME), 64 | Arguments.of("example.com.", DnsType.CAA, Type.CAA), 65 | Arguments.of("example.com.", DnsType.A, Type.A) 66 | ); 67 | } 68 | 69 | @ParameterizedTest 70 | @MethodSource("provideDoubleTypeDnsData") 71 | void testGetDnsData_withDataReturned_happyPath(String domain, DnsType dnsType, int type) throws Exception { 72 | Record dnsRecord = Record.newRecord(Name.fromString(domain), type, DClass.IN); 73 | when(lookup.run()).thenReturn(new Record[]{dnsRecord}); 74 | 75 | DnsData dnsData = dnsClient.getDnsData(List.of(domain), dnsType); 76 | 77 | assertNotNull(dnsData); 78 | assertEquals("8.8.8.8", dnsData.serverWithData()); 79 | assertEquals(domain, dnsData.domain()); 80 | assertEquals(dnsType, dnsData.dnsType()); 81 | assertFalse(dnsData.records().isEmpty()); 82 | assertTrue(dnsData.servers().contains("8.8.8.8")); 83 | } 84 | 85 | static Stream provideSingleTypeDnsData() { 86 | return Stream.of( 87 | Arguments.of("example.com.", DnsType.TXT), 88 | Arguments.of("example.com.", DnsType.CNAME), 89 | Arguments.of("example.com.", DnsType.CAA) 90 | ); 91 | } 92 | 93 | @ParameterizedTest 94 | @MethodSource("provideSingleTypeDnsData") 95 | void testGetDnsData_noDataReturned_happyPath(String domain, DnsType dnsType) { 96 | when(lookup.run()).thenReturn(null); 97 | 98 | DnsData dnsData = dnsClient.getDnsData(List.of(domain), dnsType); 99 | 100 | assertNotNull(dnsData); 101 | assertEquals("", dnsData.serverWithData()); 102 | assertEquals(domain, dnsData.domain()); 103 | assertEquals(dnsType, dnsData.dnsType()); 104 | assertTrue(dnsData.records().isEmpty()); 105 | assertTrue(dnsData.servers().contains("8.8.8.8")); 106 | } 107 | 108 | @Test 109 | void testGetDnsData_noDomainsProvided_throwsException() { 110 | List emptyDomains = Collections.emptyList(); 111 | assertThrows(IllegalArgumentException.class, () -> dnsClient.getDnsData(emptyDomains, DnsType.TXT)); 112 | } 113 | 114 | @Test 115 | void testGetDnsData_noDnsServersConfigured_throwsException() { 116 | DcvConfiguration emptyDnsServersConfig = mock(DcvConfiguration.class); 117 | when(emptyDnsServersConfig.getDnsServers()).thenReturn(Collections.emptyList()); 118 | DcvContext emptyDnsServersContext = new DcvContext(emptyDnsServersConfig); 119 | DnsClient dnsClientWithNoServers = new DnsClient(emptyDnsServersContext); 120 | 121 | List domains = List.of("example.com"); 122 | assertThrows(IllegalArgumentException.class, () -> dnsClientWithNoServers.getDnsData(domains, DnsType.TXT)); 123 | } 124 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/client/file/CustomDnsResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.file; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import com.digicert.validation.client.dns.DnsClient; 5 | import com.digicert.validation.client.dns.DnsData; 6 | import com.digicert.validation.enums.DnsType; 7 | import org.junit.jupiter.api.AfterAll; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.mockito.Mock; 12 | import org.mockito.MockitoAnnotations; 13 | import org.mockserver.integration.ClientAndServer; 14 | import org.mockserver.socket.PortFactory; 15 | import org.xbill.DNS.ARecord; 16 | import org.xbill.DNS.Name; 17 | 18 | import java.net.InetAddress; 19 | import java.net.UnknownHostException; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | import static org.junit.jupiter.api.Assertions.*; 24 | import static org.mockito.Mockito.when; 25 | 26 | class CustomDnsResolverTest { 27 | 28 | private static ClientAndServer mockServer; 29 | 30 | @Mock 31 | private DnsClient dnsClient; 32 | 33 | @Mock 34 | private DcvContext dcvContext; 35 | 36 | private CustomDnsResolver customDnsResolver; 37 | 38 | @BeforeAll 39 | public static void startServer() { 40 | mockServer = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); 41 | } 42 | 43 | @AfterAll 44 | public static void stopServer() { 45 | mockServer.stop(); 46 | } 47 | 48 | @BeforeEach 49 | public void setUp() { 50 | mockServer.reset(); 51 | MockitoAnnotations.openMocks(this); 52 | 53 | // Mock the behavior of dcvContext to return the mocked dnsClient 54 | when(dcvContext.get(DnsClient.class)).thenReturn(dnsClient); 55 | 56 | // Explicitly create an instance of CustomDnsResolver with the mocked dcvContext 57 | customDnsResolver = new CustomDnsResolver(dcvContext); 58 | } 59 | 60 | @Test 61 | void testCustomDnsResolver_resolve() throws Exception { 62 | String domain = "digicert.com"; 63 | Name name = Name.fromString(domain + "."); 64 | ARecord aRecord = new ARecord(name, 1, 3600, InetAddress.getByName("64.78.193.234")); 65 | DnsData expectedDnsData = new DnsData(List.of("dnsServer"), domain, DnsType.A, List.of(aRecord), Set.of(), "dnsServer"); 66 | when(dnsClient.getDnsData(List.of(domain), DnsType.A)).thenReturn(expectedDnsData); 67 | 68 | InetAddress[] actualResult = customDnsResolver.resolve(domain); 69 | 70 | assertNotNull(actualResult); 71 | assertEquals(1, actualResult.length); 72 | assertEquals("64.78.193.234", actualResult[0].getHostAddress()); 73 | } 74 | 75 | @Test 76 | void testCustomDnsResolver_resolveWithException() { 77 | String domain = "digicert.com"; 78 | when(dnsClient.getDnsData(List.of(domain), DnsType.A)).thenThrow(new RuntimeException("Failed to get DNS data")); 79 | 80 | UnknownHostException exception = assertThrows(UnknownHostException.class, () -> customDnsResolver.resolve(domain)); 81 | assertTrue(exception.getMessage().contains("Failed to resolve host: digicert.com due to Failed to get DNS data")); 82 | } 83 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/client/file/CustomRedirectStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.client.file; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | 8 | import java.util.stream.Stream; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | class CustomRedirectStrategyTest { 13 | private final CustomRedirectStrategy sut = new CustomRedirectStrategy(new DcvContext()); 14 | 15 | static Stream redirects_testData() { 16 | return Stream.of( 17 | // Test cases for null values 18 | Arguments.of(null, "http://example.com", false), 19 | Arguments.of("http://example.com", null, false), 20 | 21 | // Test case for relative url 22 | Arguments.of("http://example.com", "/new-location", true), 23 | Arguments.of("http://example.com", "example.com/new-location", true), 24 | 25 | // Test cases for http over various ports 26 | Arguments.of("http://example.com", "http://example.com/new-location", true), 27 | Arguments.of("http://example.com", "http://example.com:80/new-location", true), 28 | Arguments.of("http://example.com", "http://example.com:443/new-location", false), 29 | Arguments.of("http://example.com", "http://example.com:8080/new-location", false), 30 | 31 | // Test cases for https over various ports 32 | Arguments.of("http://example.com", "https://example.com", true), 33 | Arguments.of("http://example.com", "https://example.com:443", true), 34 | Arguments.of("http://example.com", "https://example.com:443/new-location", true), 35 | Arguments.of("http://example.com", "https://example.com:80", false), 36 | Arguments.of("http://example.com", "https://example.com:8443", false), 37 | 38 | // Test cases around base domain matching 39 | Arguments.of("http://example.com", "http://sub.example.com/location", true), 40 | Arguments.of("http://sub.example.com", "http://example.com/location", true), 41 | Arguments.of("http://example.com", "http://foobar.com/location", false), 42 | Arguments.of("http://example.co.uk", "http://example.uk", false), 43 | 44 | // Test cases for invalid urls 45 | Arguments.of("http://example.com", "http://[invalid.url]", false) 46 | ); 47 | } 48 | 49 | @ParameterizedTest 50 | @MethodSource("redirects_testData") 51 | void testCustomRedirectStrategy_shouldFollowRedirect(String originalUrl, String newLocationUrl, boolean expectedResult) { 52 | assertEquals(expectedResult, sut.shouldFollowRedirect(originalUrl, newLocationUrl)); 53 | } 54 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/methods/email/prepare/provider/ConstructedEmailProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare.provider; 2 | 3 | import com.digicert.validation.exceptions.PreparationException; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import java.util.Set; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.mockito.Mockito.when; 14 | 15 | class ConstructedEmailProviderTest { 16 | 17 | @Mock 18 | private EmailProvider emailProvider; 19 | 20 | @InjectMocks 21 | private ConstructedEmailProvider constructedEmailProvider; 22 | 23 | @BeforeEach 24 | public void setUp() { 25 | MockitoAnnotations.openMocks(this); 26 | } 27 | 28 | @Test 29 | void testFindEmailsForDomain() throws PreparationException { 30 | String domain = "example.com"; 31 | Set expectedEmails = Set.of( 32 | "admin@example.com", 33 | "administrator@example.com", 34 | "webmaster@example.com", 35 | "hostmaster@example.com", 36 | "postmaster@example.com" 37 | ); 38 | 39 | when(emailProvider.findEmailsForDomain(domain)).thenReturn(expectedEmails); 40 | 41 | Set emails = constructedEmailProvider.findEmailsForDomain(domain); 42 | 43 | assertEquals(expectedEmails, emails); 44 | } 45 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/methods/email/prepare/provider/DnsTxtEmailProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.prepare.provider; 2 | 3 | import com.digicert.validation.DcvContext; 4 | import com.digicert.validation.client.dns.DnsClient; 5 | import com.digicert.validation.client.dns.DnsData; 6 | import com.digicert.validation.enums.DcvError; 7 | import com.digicert.validation.enums.DnsType; 8 | import com.digicert.validation.exceptions.PreparationException; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.xbill.DNS.TXTRecord; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.when; 20 | 21 | class DnsTxtEmailProviderTest { 22 | 23 | private DnsClient dnsClient; 24 | 25 | private DnsTxtEmailProvider dnsTxtEmailProvider; 26 | 27 | @BeforeEach 28 | public void setUp() { 29 | dnsClient = mock(DnsClient.class); 30 | 31 | DcvContext dcvContext = mock(DcvContext.class); 32 | when(dcvContext.get(DnsClient.class)).thenReturn(dnsClient); 33 | 34 | dnsTxtEmailProvider = new DnsTxtEmailProvider(dcvContext); 35 | } 36 | 37 | @Test 38 | void testFindEmailsForDomainTxt() throws PreparationException { 39 | String domain = "example.com"; 40 | List dnsValues = List.of("test@example.com", "invalid-email"); 41 | 42 | // Mock the TXTRecord to return the dnsValues 43 | TXTRecord dnsRecord = mock(TXTRecord.class); 44 | when(dnsRecord.getStrings()).thenReturn(dnsValues); 45 | 46 | // Create DnsData with the mocked TXTRecord 47 | String host = "some-host"; 48 | DnsData dnsData = new DnsData(List.of(host), "some-name", DnsType.TXT, List.of(dnsRecord), 49 | Set.of(), host); 50 | String prefixedDomain = String.format("%s.%s", DnsTxtEmailProvider.DNS_TXT_EMAIL_AUTHORIZATION_PREFIX, domain); 51 | when(dnsClient.getDnsData(List.of(prefixedDomain), DnsType.TXT)).thenReturn(dnsData); 52 | 53 | // Call the method under test 54 | Set emails = dnsTxtEmailProvider.findEmailsForDomain(domain); 55 | 56 | // Verify the results 57 | assertTrue(emails.contains("test@example.com")); 58 | assertFalse(emails.contains("invalid-email")); 59 | assertEquals(1, emails.size()); 60 | } 61 | 62 | @Test 63 | void testFindEmailsForDomain_throwsPreparationException() { 64 | String domain = "example.com"; 65 | String prefixedDomain = String.format("%s.%s", DnsTxtEmailProvider.DNS_TXT_EMAIL_AUTHORIZATION_PREFIX, domain); 66 | DnsData dnsData = new DnsData(Collections.emptyList(), "some-name", DnsType.TXT, List.of(), 67 | Set.of(DcvError.DNS_LOOKUP_UNKNOWN_HOST_EXCEPTION), "some-host"); 68 | when(dnsClient.getDnsData(List.of(prefixedDomain), DnsType.TXT)).thenReturn(dnsData); 69 | 70 | PreparationException exception = assertThrows(PreparationException.class, () -> dnsTxtEmailProvider.findEmailsForDomain(domain)); 71 | 72 | assertTrue(exception.getErrors().contains(DcvError.DNS_LOOKUP_UNKNOWN_HOST_EXCEPTION)); 73 | } 74 | 75 | @Test 76 | void testFindEmailsForDomainTxt_missingDnsData() { 77 | String domain = "example.com"; 78 | String prefixedDomain = String.format("%s.%s", DnsTxtEmailProvider.DNS_TXT_EMAIL_AUTHORIZATION_PREFIX, domain); 79 | List domainList = List.of(prefixedDomain); 80 | String host = "some-host"; 81 | DnsData dnsData = new DnsData(List.of(), "", DnsType.TXT, List.of(), Set.of(DcvError.DNS_LOOKUP_RECORD_NOT_FOUND), host); 82 | when(dnsClient.getDnsData(domainList, DnsType.TXT)).thenReturn(dnsData); 83 | 84 | // Call the method under test 85 | PreparationException exception = assertThrows(PreparationException.class, () -> dnsTxtEmailProvider.findEmailsForDomain(domain)); 86 | 87 | // Verify the results 88 | assertTrue(exception.getErrors().contains(DcvError.DNS_LOOKUP_RECORD_NOT_FOUND)); 89 | } 90 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/methods/email/validate/EmailValidationRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.methods.email.validate; 2 | 3 | import com.digicert.validation.methods.email.prepare.EmailSource; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | 8 | class EmailValidationRequestTest { 9 | 10 | @Test 11 | void testEmailValidationVerification() { 12 | String domain = "email.com"; 13 | EmailSource emailSource = EmailSource.DNS_TXT; 14 | String randomValue = "randomValue123"; 15 | String emailAddress = "test@email.com"; 16 | 17 | EmailValidationRequest emailValidationRequest = EmailValidationRequest.builder() 18 | .domain(domain) 19 | .emailSource(emailSource) 20 | .randomValue(randomValue) 21 | .emailAddress(emailAddress) 22 | .validationState(null) 23 | .build(); 24 | 25 | assertEquals(domain, emailValidationRequest.getDomain()); 26 | assertEquals(emailSource, emailValidationRequest.getEmailSource()); 27 | assertEquals(randomValue, emailValidationRequest.getRandomValue()); 28 | assertEquals(emailAddress, emailValidationRequest.getEmailAddress()); 29 | } 30 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/psl/PslDataParserTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.util.Objects; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | class PslDataParserTest { 13 | 14 | // Test case for parsing PSL data 15 | @Test 16 | void testParsePslData() throws IOException { 17 | InputStream resourceAsStream = Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream("public_suffix_list_test.dat")); 18 | try (InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream)) { 19 | 20 | PslData pslData = PslDataParser.parsePslData(inputStreamReader); 21 | 22 | assertNotNull(pslData); 23 | assertNotNull(pslData.getRegistrySuffixTrie()); 24 | assertNotNull(pslData.getRegistryWildcardTrie()); 25 | assertNotNull(pslData.getRegistryExceptionTrie()); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/psl/PslDataProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.psl; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.util.Objects; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | class PslDataProviderTest { 13 | 14 | private PslDataProvider pslDataProvider; 15 | 16 | @BeforeEach 17 | void setUp() { 18 | pslDataProvider = PslDataProvider.getInstance(); 19 | } 20 | 21 | @Test 22 | void testLoadDefaultPslData() { 23 | pslDataProvider.loadDefaultData(); 24 | PslData pslData = pslDataProvider.getPslData(); 25 | 26 | assertNotNull(pslData); 27 | assertNotNull(pslData.getRegistrySuffixTrie()); 28 | assertNotNull(pslData.getRegistryWildcardTrie()); 29 | assertNotNull(pslData.getRegistryExceptionTrie()); 30 | } 31 | 32 | @Test 33 | void testLoadPslData() { 34 | pslDataProvider.resetPslData(); 35 | pslDataProvider.loadPslData(getInputStreamReader()); 36 | PslData pslData = pslDataProvider.getPslData(); 37 | 38 | assertNotNull(pslData); 39 | assertNotNull(pslData.getRegistrySuffixTrie()); 40 | assertNotNull(pslData.getRegistryWildcardTrie()); 41 | assertNotNull(pslData.getRegistryExceptionTrie()); 42 | } 43 | 44 | private InputStreamReader getInputStreamReader() { 45 | InputStream resourceAsStream = Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream("public_suffix_list_test.dat")); 46 | return new InputStreamReader(resourceAsStream); 47 | } 48 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/random/BasicRandomValueGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.random; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | class BasicRandomValueGeneratorTest { 10 | 11 | private RandomValueGenerator randomValueGenerator; 12 | private static final int MIN_ALLOWED_ENTROPY_VALUE = 112; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | randomValueGenerator = new BasicRandomValueGenerator(); 17 | } 18 | 19 | @Test 20 | void testGenerateRandomString() { 21 | var randomValue = randomValueGenerator.generateRandomString(); 22 | 23 | // BR version 1.3.8 requires a 112 bits of entropy 24 | // TO figure out entropy: [log2 of character set * string len = entropy bits] 25 | var randomValueEntropy = Math.log(randomValueGenerator.getCharset().length()) / Math.log(2.0) * randomValue.length(); 26 | assertTrue(randomValueEntropy >= MIN_ALLOWED_ENTROPY_VALUE); 27 | assertEquals(32, randomValue.length()); 28 | } 29 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/random/RandomValueVerifierTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.random; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.time.Instant; 6 | 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.digicert.validation.DcvConfiguration; 11 | import com.digicert.validation.DcvContext; 12 | import com.digicert.validation.enums.DcvError; 13 | import com.digicert.validation.exceptions.DcvException; 14 | 15 | class RandomValueVerifierTest { 16 | 17 | private RandomValueVerifier randomValueVerifier; 18 | private RandomValueGenerator randomValueGenerator; 19 | 20 | @BeforeEach 21 | void setUp() { 22 | DcvContext dcvContext = new DcvContext(); 23 | randomValueVerifier = new RandomValueVerifier(dcvContext); 24 | randomValueGenerator = dcvContext.get(RandomValueGenerator.class); 25 | } 26 | 27 | @Test 28 | void testRandomValueUtils_verifyRandomValue_happyDay() throws DcvException { 29 | // This will throw an exception if the random value is invalid 30 | randomValueVerifier.verifyRandomValue(randomValueGenerator.generateRandomString(), Instant.now()); 31 | } 32 | 33 | @Test 34 | void testRandomValueUtils_verifyRandomValue_nullRandomValue() { 35 | try { 36 | randomValueVerifier.verifyRandomValue(null, Instant.now()); 37 | fail(); 38 | } catch (DcvException e) { 39 | assertTrue(e.getErrors().contains(DcvError.RANDOM_VALUE_REQUIRED)); 40 | } 41 | } 42 | 43 | @Test 44 | void testRandomValueUtils_verifyRandomValue_expiredRandomValue() { 45 | try { 46 | randomValueVerifier.verifyRandomValue(randomValueGenerator.generateRandomString(), Instant.now().minusSeconds(2592000)); 47 | fail(); 48 | } catch (DcvException e) { 49 | assertTrue(e.getErrors().contains(DcvError.RANDOM_VALUE_EXPIRED)); 50 | } 51 | } 52 | 53 | @Test 54 | void testRandomValueUtils_verifyRandomValue_insufficientEntropy() { 55 | try { 56 | randomValueVerifier.verifyRandomValue("abc", Instant.now()); 57 | fail(); 58 | } catch (DcvException e) { 59 | assertTrue(e.getErrors().contains(DcvError.RANDOM_VALUE_INSUFFICIENT_ENTROPY)); 60 | } 61 | } 62 | 63 | @Test 64 | void testEntropyCalculationWithExactly112Bits() { 65 | randomValueGenerator = new RandomValueGenerator() { 66 | @Override 67 | public String getCharset() { 68 | return "ABCDEFGHIJKLMNOP"; // 16 character set 69 | } 70 | 71 | @Override 72 | public String generateRandomString() { 73 | return "ABCDEFGHIJKLMNOPQRSTUVWXYZab"; // 28 characters 74 | } 75 | }; 76 | 77 | DcvConfiguration dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 78 | .randomValueGenerator(randomValueGenerator) 79 | .build(); 80 | DcvContext dcvContext = new DcvContext(dcvConfiguration); 81 | randomValueVerifier = new RandomValueVerifier(dcvContext); 82 | 83 | // Then the method should correctly handle the rounding and determine if the entropy is sufficient 84 | assertDoesNotThrow(() -> randomValueVerifier.verifyRandomValue(randomValueGenerator.generateRandomString(), Instant.now())); 85 | } 86 | 87 | @Test 88 | void testEntropyCalculationOneCharUnder() throws DcvException { 89 | randomValueGenerator = new RandomValueGenerator() { 90 | @Override 91 | public String getCharset() { 92 | return "ABCDEFGHIJKLMNOPQ"; // 17 character set 93 | } 94 | 95 | @Override 96 | public String generateRandomString() { 97 | return "ABCDEFGHIJKLMNOPQRSTUVWXYZa"; // 27 characters 98 | } 99 | }; 100 | 101 | DcvConfiguration dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 102 | .randomValueGenerator(randomValueGenerator) 103 | .build(); 104 | DcvContext dcvContext = new DcvContext(dcvConfiguration); 105 | randomValueVerifier = new RandomValueVerifier(dcvContext); 106 | try { 107 | // Then the method should throw an InputException for insufficient entropy 108 | randomValueVerifier.verifyRandomValue(randomValueGenerator.generateRandomString(), Instant.now()); 109 | fail(); 110 | } catch (DcvException e) { 111 | assertTrue(e.getErrors().contains(DcvError.RANDOM_VALUE_INSUFFICIENT_ENTROPY)); 112 | } 113 | } 114 | 115 | @Test 116 | void testRandomValueUtils_validateUnicodeChars() throws DcvException { 117 | RandomValueGenerator unicodeRandomValueGenerator = new BasicRandomValueGenerator() { 118 | 119 | @Override 120 | public String getCharset() { 121 | return "अกあア가中∫π¥€❄♪♥Бבبआขいイ나国∑Ω₤₽☃"; 122 | } 123 | }; 124 | DcvConfiguration dcvConfiguration = new DcvConfiguration.DcvConfigurationBuilder() 125 | .randomValueGenerator(unicodeRandomValueGenerator) 126 | .build(); 127 | DcvContext dcvContext = new DcvContext(dcvConfiguration); 128 | randomValueVerifier = new RandomValueVerifier(dcvContext); 129 | 130 | // This will throw an exception if the random value is invalid 131 | randomValueVerifier.verifyRandomValue(unicodeRandomValueGenerator.generateRandomString(), Instant.now()); 132 | } 133 | } -------------------------------------------------------------------------------- /library/src/test/java/com/digicert/validation/utils/StateValidationUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.digicert.validation.utils; 2 | 3 | import com.digicert.validation.common.ValidationState; 4 | import com.digicert.validation.enums.DcvMethod; 5 | import com.digicert.validation.exceptions.DcvException; 6 | import com.digicert.validation.exceptions.InputException; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.time.Instant; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | class StateValidationUtilsTest { 14 | 15 | @Test 16 | void testVerifyValidationStateNull() { 17 | assertThrows(InputException.class, () -> StateValidationUtils.verifyValidationState(null, null), "Validation State is required"); 18 | } 19 | 20 | @Test 21 | void testVerifyValidationStateNullDomain() { 22 | ValidationState validationState = new ValidationState(null, Instant.now(), DcvMethod.BR_3_2_2_4_4); 23 | 24 | assertThrows(InputException.class, () -> StateValidationUtils.verifyValidationState(validationState, DcvMethod.BR_3_2_2_4_4), "Domain in Validation State is required"); 25 | } 26 | 27 | @Test 28 | void testVerifyValidationStateNullDcvMethod() { 29 | ValidationState validationState = new ValidationState("domain", Instant.now(), null); 30 | 31 | assertThrows(InputException.class, () -> StateValidationUtils.verifyValidationState(validationState, DcvMethod.BR_3_2_2_4_4), "DCV Method is required"); 32 | } 33 | 34 | @Test 35 | void testVerifyValidationStateNullPrepareTime() { 36 | ValidationState validationState = new ValidationState("domain", null, DcvMethod.BR_3_2_2_4_4); 37 | 38 | assertThrows(InputException.class, () -> StateValidationUtils.verifyValidationState(validationState, DcvMethod.BR_3_2_2_4_4), "Prepare Time is required"); 39 | } 40 | 41 | @Test 42 | void testVerifyValidation_NonMatchingDcvMethod() { 43 | ValidationState validationState = new ValidationState("domain", Instant.now(), DcvMethod.BR_3_2_2_4_18); 44 | 45 | assertThrows(InputException.class, () -> StateValidationUtils.verifyValidationState(validationState, DcvMethod.BR_3_2_2_4_4), "Dcv Method does not match expected method"); 46 | } 47 | 48 | @Test 49 | void testVerifyValidation_HappyPath() { 50 | ValidationState validationState = new ValidationState("domain", Instant.now(), DcvMethod.BR_3_2_2_4_4); 51 | 52 | try { 53 | StateValidationUtils.verifyValidationState(validationState, DcvMethod.BR_3_2_2_4_4); 54 | } catch (DcvException e) { 55 | fail(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /library/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.digicert.validation 8 | domain-control-validation-parent 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 14 | MIT License 15 | https://opensource.org/licenses/MIT 16 | repo 17 | 18 | 19 | 20 | 21 | library 22 | example-app 23 | 24 | --------------------------------------------------------------------------------