pax_global_header00006660000000000000000000000064150623400370014512gustar00rootroot0000000000000052 comment=b404a64e125e513f85a3cf991a44a481129f19dd scram-3.2/000077500000000000000000000000001506234003700124635ustar00rootroot00000000000000scram-3.2/.editorconfig000066400000000000000000000002231506234003700151350ustar00rootroot00000000000000root = true [*] charset = utf-8 indent_style = space indent_size = 2 trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true scram-3.2/.github/000077500000000000000000000000001506234003700140235ustar00rootroot00000000000000scram-3.2/.github/dependabot.yml000066400000000000000000000006411506234003700166540ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "maven" directories: - "/" - "/scram-client/src/it/jpms-scram-client" schedule: interval: "monthly" groups: all-maven-dependencies: patterns: - "*" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" groups: all-github-actions: patterns: - "*" scram-3.2/.github/workflows/000077500000000000000000000000001506234003700160605ustar00rootroot00000000000000scram-3.2/.github/workflows/codeql.yml000066400000000000000000000052351506234003700200570ustar00rootroot00000000000000name: "CodeQL Advanced" on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: '42 10 * * 1' permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: analyze: name: Analyze (${{ matrix.language }}) # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: 'ubuntu-latest' permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read # only required for workflows in private repositories actions: read contents: read strategy: fail-fast: false matrix: include: - language: java-kotlin build-mode: manual # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too. steps: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # 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 # queries: security-extended,security-and-quality - if: matrix.build-mode == 'manual' name: Set up JDK 21 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: '21' distribution: 'temurin' cache: maven - if: matrix.build-mode == 'manual' shell: bash run: ./mvnw package -P release -DskipTests -Dmaven.javadoc.skip -Dgpg.skip - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: category: "/language:${{matrix.language}}" scram-3.2/.github/workflows/maven.yml000066400000000000000000000026721506234003700177200ustar00rootroot00000000000000# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven name: Java CI with Maven on: push: branches: ["main"] pull_request: branches: ["main"] permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK 21 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: '21' distribution: 'zulu' cache: maven - name: Build with Maven run: ./mvnw -B verify -P checks,run-its # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive dependency-submission: runs-on: ubuntu-latest permissions: contents: write #required for POST snapshot API https://docs.github.com/en/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@b275d12641ac2d2108b2cbb7598b154ad2f2cee8 scram-3.2/.github/workflows/scorecard.yml000066400000000000000000000065431506234003700205600ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '20 5 * * 2' push: branches: [ "main" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore # file_mode: git # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif scram-3.2/.gitignore000066400000000000000000000024361506234003700144600ustar00rootroot00000000000000### Eclipse ### .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders .pmd # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # CDT- autotools .autotools # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ # Annotation Processing .apt_generated/ # Scala IDE specific (Scala & Java development for Eclipse) .cache-main .scala_dependencies .worksheet ### Eclipse Patch ### # Eclipse Core .project # JDT-specific (Eclipse Java Development Tools) .classpath # Annotation Processing .apt_generated .sts4-cache/ # VSCode .vscode/ ### Java ### # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* /target /*/target .flattened-pom.xml *pom.xml.versionsBackup scram-3.2/.gitlab-ci.yml000066400000000000000000000003271506234003700151210ustar00rootroot00000000000000image: eclipse-temurin:21-jdk stages: - build variables: M2_HOME: ".m2/maven" MAVEN_OPTS: "-Dmaven.repo.local=.m2" build: stage: build cache: paths: - .m2/ script: - ./mvnw clean verify scram-3.2/.mvn/000077500000000000000000000000001506234003700133415ustar00rootroot00000000000000scram-3.2/.mvn/maven.config000066400000000000000000000002071506234003700156350ustar00rootroot00000000000000--strict-checksums --show-version --errors --fail-fast -DinstallAtEnd=true -DdeployAtEnd=true -DrootDirectory=${session.rootDirectory} scram-3.2/.mvn/wrapper/000077500000000000000000000000001506234003700150215ustar00rootroot00000000000000scram-3.2/.mvn/wrapper/MavenWrapperDownloader.java000066400000000000000000000072471506234003700223240ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.io.InputStream; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.concurrent.ThreadLocalRandom; public final class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "3.3.4"; private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); public static void main(String[] args) { log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); if (args.length != 2) { System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); System.exit(1); } try { log(" - Downloader started"); final URL wrapperUrl = URI.create(args[0]).toURL(); final Path baseDir = Paths.get(".").toAbsolutePath().normalize(); final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); if (!wrapperJarPath.startsWith(baseDir)) { throw new IOException("Invalid path: outside of allowed directory"); } downloadFileFromURL(wrapperUrl, wrapperJarPath); log("Done"); } catch (IOException e) { System.err.println("- Error downloading: " + e.getMessage()); if (VERBOSE) { e.printStackTrace(); } System.exit(1); } } private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) throws IOException { log(" - Downloading to: " + wrapperJarPath); if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { final String username = System.getenv("MVNW_USERNAME"); final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } Path temp = wrapperJarPath .getParent() .resolve(wrapperJarPath.getFileName() + "." + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); try (InputStream inStream = wrapperUrl.openStream()) { Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); } finally { Files.deleteIfExists(temp); } log(" - Downloader complete"); } private static void log(String msg) { if (VERBOSE) { System.out.println(msg); } } } scram-3.2/.mvn/wrapper/maven-wrapper.properties000066400000000000000000000007231506234003700217250ustar00rootroot00000000000000wrapperVersion=3.3.4 distributionType=source distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip distributionSha256Sum=0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar wrapperSha256Sum=4e2fbf6554bc8a4702cdfdd3bef464f423393d784ddbb037216320ce55d5e4e1 alwaysUnpack=true scram-3.2/CHANGELOG.md000066400000000000000000000036671506234003700143100ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. ## [Unreleased] ## [3.2] - 2025-09-16 ### :lock: Security - Fix Timing Attack Vulnerability in SCRAM Authentication ### :ghost: Maintenance - Updated dependencies and maven plugins. - Use `central-publishing-maven-plugin` to deploy to Maven Central. ## [3.1] - 2024-06-26 ### :building_construction: Improvements - Ensure the `LICENSE` file is included in the Jar file. - Update of the `saslprep` dependency to 2.2. ### :ghost: Maintenance - Added coverage report module. - Updated dependencies and maven plugins. - Remove `nexus-staging-maven-plugin`. ## [3.0] - 2024-04-03 ### :boom: Breaking changes - :warning: Full refactor of the `scram` java implementation, this release is compatible with Java 8+, but it's incompatible with previous releases :warning: ### :rocket: New features - Fully rewrite the `ScramClient` allowing negotiation of channel-binding properly. - Create Multi-release Modular JARs, the modules names are: - `com.ongres.scram.common` for the common scram messages. - `com.ongres.scram.client` for the scram client implementation. - Add `StringPreparation.POSTGRESQL_PREPARATION`, for any error in SASL preparation, it falls back to return the raw string. - Now the released jars are reproducible. - Publish CycloneDX SBOM. - Implementation of `tls-server-end-point` channel binding data extraction. ### :building_construction: Improvements - Update of the `saslprep` dependency to 2.1. - Now the password is passed as a `char[]`. - Improve Javadoc documentation. ### :ghost: Maintenance - Migrate the main repo back to GitHub. - Remove the shaded Bouncy Castle pbkdf2 and base64 implementation used for Java 7 support. [3.0]: https://github.com/ongres/scram/compare/2.1...3.0 [3.1]: https://github.com/ongres/scram/compare/3.0...3.1 [3.2]: https://github.com/ongres/scram/compare/3.1...3.2 [Unreleased]: https://github.com/ongres/scram/compare/3.2...main scram-3.2/LICENSE000066400000000000000000000023651506234003700134760ustar00rootroot00000000000000Copyright (c) 2017 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. scram-3.2/README.md000066400000000000000000000113151506234003700137430ustar00rootroot00000000000000# SCRAM Java Implementation ![Maven Central Version](https://img.shields.io/maven-central/v/com.ongres.scram/scram-aggregator) [![Reproducible Builds](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/com/ongres/scram/badge.json)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/com/ongres/scram/README.md) ![GitHub License](https://img.shields.io/github/license/ongres/scram) > Salted Challenge Response Authentication Mechanism (SCRAM) ## Overview SCRAM (Salted Challenge Response Authentication Mechanism) is part of the family of Simple Authentication and Security Layer ([SASL, RFC 4422](https://datatracker.ietf.org/doc/html/rfc4422)) authentication mechanisms. It is described as part of [RFC 5802](https://datatracker.ietf.org/doc/html/rfc5802) and [RFC 7677](https://datatracker.ietf.org/doc/html/rfc7677). This project provides a robust and well-tested implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) in Java. It adheres to the specifications outlined in RFC 5802 and RFC 7677, ensuring secure user authentication. This SCRAM Java implementation can be used for [PostgreSQL](https://www.postgresql.org) (which supports [SASL authentication](https://www.postgresql.org/docs/current/sasl-authentication.html) since PostgreSQL 10) through the [PostgreSQL JDBC Driver](https://jdbc.postgresql.org/) and others projects that connect from Java. The code is licensed under the BSD "Simplified 2 Clause" license (see [LICENSE](LICENSE)). ## Key Features * Clean-room Implementation: The code is written from scratch, offering a reliable and independent solution. * Modular Structure: The library is designed for modularity, promoting reusability and maintainability. * Client-Server Support: The implementation caters to both client and server-side SCRAM usage in the `scram-common` module. For the moment only the `scram-client` module is implemented. * Multiple Hashing Algorithms: It supports `SHA-1` and `SHA-256` as described in the official RFC 5802 and RFC 7677 respectively, and also provides `SHA-224`, `SHA-384` and `SHA-512` for flexible security strength selection. * Channel Binding support: The library supports client mechanism negotiation with support of channel binding data provided externally. * Extensive Testing: The codebase is thoroughly tested to guarantee its functionality and correctness. * Minimal Dependencies: The library operates with a single dependency based on the implementation of the [SASLprep](https://github.com/ongres/stringprep) required by the RFC 5802. ## How to use the SCRAM Client API [![Maven Central](https://img.shields.io/badge/maven--central-scram_client-informational?style=for-the-badge&logo=apache-maven&logoColor=red)](https://maven-badges.herokuapp.com/maven-central/com.ongres.scram/scram-client) Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/scram-client.svg?label=scram-client)](http://javadoc.io/doc/com.ongres.scram/scram-client) ### Example of use: ```java byte[] cbindData = ... ScramClient scramClient = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) .username("user") .password("pencil".toCharArray()) .channelBinding("tls-server-end-point", cbindData) // client supports channel binding .build(); // The build() call negotiates the SCRAM mechanism to be used. In this example, // since the server advertise support for the SCRAM-SHA-256-PLUS mechanism, // and the builder is set with the channel binding type and data, the constructed // scramClient will use the "SCRAM-SHA-256-PLUS" mechanism for authentication. // FE-> Send the client-first-message ("p=...,,n=...,r=...") ClientFirstMessage clientFirstMsg = scramClient.clientFirstMessage(); ... // <-BE Receive the server-first-message ServerFirstMessage serverFirstMsg = scramClient.serverFirstMessage("r=...,s=...,i=..."); ... // FE-> Send the client-final-message ("c=...,r=...,p=...") ClientFinalMessage clientFinalMsg = scramClient.clientFinalMessage(); ... // <-BE Receive the server-final-message, throw an ScramException on error or invalid signature ServerFinalMessage serverFinalMsg = scramClient.serverFinalMessage("v=..."); ``` ## Contributing We welcome contributions to this project! Feel free to submit pull requests that improve the codebase, add features, or fix bugs. Please make sure your contributions adhere to coding style guidelines and include thorough testing. Make sure to compile with `./mvnw verify -Pchecks` before submitting a PR. By making a contribution to this project, you certify that you adhere to requirements of the [DCO](https://developercertificate.org/) by signing-off your commits (`git commit -s`).: scram-3.2/SECURITY.md000066400000000000000000000017201506234003700142540ustar00rootroot00000000000000# Security Policy ## Supported Versions The following table outlines which versions of `scram` are actively supported with security updates. Please upgrade to a supported release to ensure you receive patches for any security issues. | Version | Supported | Java support | | ------- | ------------------ | ------------ | | 3.x | :white_check_mark: | Java 8+ | | < 3.0 | :x: | Java 7+ | ## Reporting a Vulnerability If you believe you have found a security vulnerability, please report it to us privately through GitHub’s security advisory system: [Report a vulnerability](../../security/advisories/new) We will investigate promptly and work with you to fix the issue. --- ## Security Best Practices for Users - Always use the latest supported version of `scram`. - Monitor [GitHub Releases](https://github.com/ongres/scram/releases) for security patches. - Consider subscribing to repository notifications for updates. scram-3.2/checks/000077500000000000000000000000001506234003700137235ustar00rootroot00000000000000scram-3.2/checks/checkstyle-header.txt000066400000000000000000000001471506234003700200520ustar00rootroot00000000000000^\/\*$ ^ \* Copyright \(c\) 20[12]\d OnGres, Inc\.$ ^ \* SPDX-License-Identifier: BSD-2-Clause$ ^ \*\/$scram-3.2/checks/checkstyle-suppressions.xml000066400000000000000000000006121506234003700213550ustar00rootroot00000000000000 scram-3.2/checks/checkstyle.xml000066400000000000000000000370471506234003700166160ustar00rootroot00000000000000 scram-3.2/checks/forbiddenapis.txt000066400000000000000000000001521506234003700172730ustar00rootroot00000000000000 java.util.Arrays#equals(byte[],byte[]) @ Replace with java.security.MessageDigest#isEqual(byte[],byte[]) scram-3.2/checks/pmd-ruleset.xml000066400000000000000000000024551506234003700167140ustar00rootroot00000000000000 Custom Rules .*/target/.* .*/generated/.* scram-3.2/checks/spotbugs-exclude.xml000066400000000000000000000020701506234003700177410ustar00rootroot00000000000000 scram-3.2/coverage-report/000077500000000000000000000000001506234003700155675ustar00rootroot00000000000000scram-3.2/coverage-report/pom.xml000066400000000000000000000026321506234003700171070ustar00rootroot00000000000000 4.0.0 com.ongres.scram scram-parent 3.2 ../scram-parent/pom.xml coverage-report pom JaCoCo Coverage Report com.ongres.scram scram-common com.ongres.scram scram-client org.jacoco jacoco-maven-plugin META-INF/versions/** report-aggregate report-aggregate verify scram-3.2/mvnw000077500000000000000000000261101506234003700134000ustar00rootroot00000000000000#!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ]; then if [ -f /usr/local/etc/mavenrc ]; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false mingw=false case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true ;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="$(/usr/libexec/java_home)" export JAVA_HOME else JAVA_HOME="/Library/Java/Home" export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ]; then if [ -r /etc/gentoo-release ]; then JAVA_HOME=$(java-config --jre-home) fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw; then [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ && JAVA_HOME="$( cd "$JAVA_HOME" || ( echo "cannot cd into $JAVA_HOME." >&2 exit 1 ) pwd )" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="$(which javac)" if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=$(which readlink) if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin; then javaHome="$(dirname "$javaExecutable")" javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else javaExecutable="$(readlink -f "$javaExecutable")" fi javaHome="$(dirname "$javaExecutable")" javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ]; then if [ -n "$JAVA_HOME" ]; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="$( \unset -f command 2>/dev/null \command -v java )" fi fi if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ]; then echo "Warning: JAVA_HOME environment variable is not set." >&2 fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ]; then echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ]; do if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=$( cd "$wdir/.." || exit 1 pwd ) fi # end of workaround done printf '%s' "$( cd "$basedir" || exit 1 pwd )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then # Remove \r in case we run on Windows within Git Bash # and check out the repository with auto CRLF management # enabled. Otherwise, we may read lines that are delimited with # \r\n and produce $'-Xarg\r' rather than -Xarg due to word # splitting rules. tr -s '\r\n' ' ' <"$1" fi } log() { if [ "$MVNW_VERBOSE" = true ]; then printf '%s\n' "$1" fi } BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1 fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" if [ -r "$wrapperJarPath" ]; then log "Found $wrapperJarPath" else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" fi while IFS="=" read -r key value; do case "$key" in wrapperUrl) wrapperUrl=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" log "Downloading from: $wrapperUrl" if $cygwin; then wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget >/dev/null; then log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=$(cygpath --path --windows "$javaSource") javaClass=$(cygpath --path --windows "$javaClass") fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then log " - Compiling MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then log " - Running MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi fi ########################################################################################## # End of extension ########################################################################################## # If specified, validate the SHA-256 sum of the Maven wrapper jar file wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) wrapperSha256Sum=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then wrapperSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $wrapperSha256Result = false ]; then echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 exit 1 fi fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] \ && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain # shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" scram-3.2/mvnw.cmd000066400000000000000000000170211506234003700141400ustar00rootroot00000000000000@REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file SET WRAPPER_SHA_256_SUM="" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B ) IF NOT %WRAPPER_SHA_256_SUM%=="" ( powershell -Command "&{"^ "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ " exit 1;"^ "}"^ "}" if ERRORLEVEL 1 goto error ) @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% scram-3.2/pom.xml000066400000000000000000000035301506234003700140010ustar00rootroot00000000000000 4.0.0 com.ongres.scram scram-parent 3.2 scram-parent/pom.xml scram-aggregator pom SCRAM - Aggregator Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) https://github.com/ongres/scram 2017 OnGres, Inc https://www.ongres.com BSD 2-Clause "Simplified" License https://spdx.org/licenses/BSD-2-Clause repo com.ongres.aht Álvaro Hernández Tortosa aht@ongres.com com.ongres.matteom Matteo Melli matteom@ongres.com com.ongres.jorsol Jorge Solórzano jorsol@ongres.com scram-parent scram-common scram-client coverage coverage-report scram-3.2/scram-client/000077500000000000000000000000001506234003700150445ustar00rootroot00000000000000scram-3.2/scram-client/pom.xml000066400000000000000000000026161506234003700163660ustar00rootroot00000000000000 4.0.0 com.ongres.scram scram-parent 3.2 ../scram-parent/pom.xml scram-client SCRAM - Client com.ongres.scram scram-common coverage org.jacoco jacoco-maven-plugin run-its org.apache.maven.plugins maven-failsafe-plugin org.apache.maven.plugins maven-invoker-plugin scram-3.2/scram-client/src/000077500000000000000000000000001506234003700156335ustar00rootroot00000000000000scram-3.2/scram-client/src/it/000077500000000000000000000000001506234003700162475ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/000077500000000000000000000000001506234003700215775ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/invoker.properties000066400000000000000000000001371506234003700253730ustar00rootroot00000000000000# build project if JRE version is 11 or higher invoker.java.version = 11+ invoker.goals = test scram-3.2/scram-client/src/it/jpms-scram-client/pom.xml000066400000000000000000000030631506234003700231160ustar00rootroot00000000000000 4.0.0 com.ongres.scram.it jpms-scram-client JPMS Scram Client 3.2 UTF-8 ${java.specification.version} ${java.specification.version} ${java.specification.version} com.ongres.scram scram-client ${project.version} org.junit.jupiter junit-jupiter 5.13.4 test maven-compiler-plugin 3.14.0 maven-jar-plugin 3.4.2 maven-surefire-plugin 3.5.4 scram-3.2/scram-client/src/it/jpms-scram-client/src/000077500000000000000000000000001506234003700223665ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/src/test/000077500000000000000000000000001506234003700233455ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/src/test/java/000077500000000000000000000000001506234003700242665ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/src/test/java/module-info.java000066400000000000000000000004501506234003700273460ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ open module test.scram { requires com.ongres.scram.client; requires transitive org.junit.jupiter.engine; requires transitive org.junit.jupiter.api; requires transitive org.junit.jupiter.params; }scram-3.2/scram-client/src/it/jpms-scram-client/src/test/java/test/000077500000000000000000000000001506234003700252455ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/src/test/java/test/scram/000077500000000000000000000000001506234003700263525ustar00rootroot00000000000000scram-3.2/scram-client/src/it/jpms-scram-client/src/test/java/test/scram/ScramClientTest.java000066400000000000000000000031671506234003700322700ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package test.scram; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.util.Arrays; import com.ongres.scram.client.ScramClient; import com.ongres.scram.common.ScramFunctions; import org.junit.jupiter.api.Test; class ScramClientTest { @Test void accessPublic() { assertEquals("com.ongres.scram.client", ScramClient.class.getModule().getName()); assertEquals("com.ongres.scram.common", ScramFunctions.class.getModule().getName()); } @Test void testBuildClient() { ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) .username("user") .password("pencil".toCharArray()) .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") .build(); assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); assertEquals("n,,n=user,r=rOprNGfwEbeRWgbNEkqO", scramSession.clientFirstMessage().toString()); assertDoesNotThrow( () -> scramSession.serverFirstMessage( "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + "s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096")); assertEquals( "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + "p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", scramSession.clientFinalMessage().toString()); assertDoesNotThrow( () -> scramSession.serverFinalMessage("v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=")); } } scram-3.2/scram-client/src/it/settings.xml000066400000000000000000000015761506234003700206420ustar00rootroot00000000000000 it-repo true local.central @localRepositoryUrl@ true true local.central @localRepositoryUrl@ true true scram-3.2/scram-client/src/main/000077500000000000000000000000001506234003700165575ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/000077500000000000000000000000001506234003700175005ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/com/000077500000000000000000000000001506234003700202565ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/com/ongres/000077500000000000000000000000001506234003700215535ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/com/ongres/scram/000077500000000000000000000000001506234003700226605ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/com/ongres/scram/client/000077500000000000000000000000001506234003700241365ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java/com/ongres/scram/client/ClientFinalProcessor.java000066400000000000000000000122141506234003700310710ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.ClientFinalMessage; import com.ongres.scram.common.ClientFirstMessage; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.ServerFinalMessage; import com.ongres.scram.common.ServerFirstMessage; import com.ongres.scram.common.StringPreparation; import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ScramServerErrorException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Processor that allows to generate the client-final-message, as well as process the * server-final-message and verify server's signature. Generate the processor by calling either * {@code ServerFirstProcessor#clientFinalProcessor(char[])} or * {@code ServerFirstProcessor#clientFinalProcessor(byte[], byte[])}. */ final class ClientFinalProcessor { private final byte[] clientKey; private final byte[] storedKey; private final byte[] serverKey; private final ScramMechanism scramMechanism; private final ClientFirstMessage clientFirstMessage; private final ServerFirstMessage serverFirstMessage; private String authMessage; private ClientFinalProcessor(ScramMechanism scramMechanism, byte[] clientKey, byte[] storedKey, byte[] serverKey, ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { this.scramMechanism = scramMechanism; this.clientKey = checkNotNull(clientKey, "clientKey"); this.storedKey = checkNotNull(storedKey, "storedKey"); this.serverKey = checkNotNull(serverKey, "serverKey"); this.clientFirstMessage = clientFirstMessage; this.serverFirstMessage = serverFirstMessage; } ClientFinalProcessor(ScramMechanism scramMechanism, byte[] clientKey, byte[] serverKey, ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { this(scramMechanism, clientKey, ScramFunctions.storedKey(scramMechanism, clientKey), serverKey, clientFirstMessage, serverFirstMessage); } ClientFinalProcessor(ScramMechanism scramMechanism, byte[] saltedPassword, ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { this( scramMechanism, ScramFunctions.clientKey(scramMechanism, saltedPassword), ScramFunctions.serverKey(scramMechanism, saltedPassword), clientFirstMessage, serverFirstMessage); } ClientFinalProcessor(ScramMechanism scramMechanism, StringPreparation stringPreparation, char[] password, byte[] salt, ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { this(scramMechanism, ScramFunctions.saltedPassword(scramMechanism, stringPreparation, password, salt, serverFirstMessage.getIterationCount()), clientFirstMessage, serverFirstMessage); } private void generateAndCacheAuthMessage(byte[] cbindData) { if (null == this.authMessage) { this.authMessage = ScramFunctions.authMessage(clientFirstMessage, serverFirstMessage, cbindData); } } /** * Generates the SCRAM representation of the client-final-message, including the given * channel-binding data. * * @param cbindData The bytes of the channel-binding data * @return The message */ @NotNull ClientFinalMessage clientFinalMessage(byte @Nullable [] cbindData) { generateAndCacheAuthMessage(cbindData); return new ClientFinalMessage( clientFirstMessage.getGs2Header(), cbindData, serverFirstMessage.getNonce(), ScramFunctions.clientProof( clientKey, ScramFunctions.clientSignature(scramMechanism, storedKey, authMessage))); } /** * Receive and process the server-final-message. Server SCRAM signatures is verified. * * @param serverFinalMessage The received server-final-message * @throws ScramParseException If the message is not a valid server-final-message * @throws ScramServerErrorException If the server-final-message contained an error * @throws ScramInvalidServerSignatureException If the server signature is invalid * @throws IllegalArgumentException If the message is null or empty */ @NotNull ServerFinalMessage receiveServerFinalMessage(@NotNull String serverFinalMessage) throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException { checkNotEmpty(serverFinalMessage, "serverFinalMessage"); ServerFinalMessage message = ServerFinalMessage.parseFrom(serverFinalMessage); if (message.isError()) { throw new ScramServerErrorException(message.getServerError()); } if (!ScramFunctions.verifyServerSignature( scramMechanism, serverKey, authMessage, message.getVerifier())) { throw new ScramInvalidServerSignatureException("Invalid SCRAM server signature"); } return message; } } scram-3.2/scram-client/src/main/java/com/ongres/scram/client/MessageFlow.java000066400000000000000000000015611506234003700272200ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; import com.ongres.scram.common.ClientFinalMessage; import com.ongres.scram.common.ClientFirstMessage; import com.ongres.scram.common.ServerFinalMessage; import com.ongres.scram.common.ServerFirstMessage; import com.ongres.scram.common.exception.ScramException; import org.jetbrains.annotations.NotNull; interface MessageFlow { @NotNull ClientFirstMessage clientFirstMessage(); @NotNull ServerFirstMessage serverFirstMessage(@NotNull String serverFirstMessage) throws ScramException; @NotNull ClientFinalMessage clientFinalMessage(); @NotNull ServerFinalMessage serverFinalMessage(@NotNull String serverFinalMessage) throws ScramException; enum Stage { NONE, CLIENT_FIRST, SERVER_FIRST, CLIENT_FINAL, SERVER_FINAL; } } scram-3.2/scram-client/src/main/java/com/ongres/scram/client/ScramClient.java000066400000000000000000000524111506234003700272100ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; import static com.ongres.scram.common.util.Preconditions.checkArgument; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static com.ongres.scram.common.util.Preconditions.gt0; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.Arrays; import java.util.Collection; import java.util.function.Supplier; import com.ongres.scram.common.ClientFinalMessage; import com.ongres.scram.common.ClientFirstMessage; import com.ongres.scram.common.Gs2CbindFlag; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.ServerFinalMessage; import com.ongres.scram.common.ServerFirstMessage; import com.ongres.scram.common.StringPreparation; import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ScramServerErrorException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * A class that represents a SCRAM client. Use this class to perform a SCRAM negotiation with a * SCRAM server. This class performs an authentication execution for a given user, and has state * related to it. Thus, it cannot be shared across users or authentication executions. * *

Example of usage: * *

{@code
 * ScramClient scramClient = ScramClient.builder()
 *     .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"))
 *     .username("user")
 *     .password("pencil".toCharArray())
 *     .channelBinding("tls-server-end-point", channelBindingData) // client supports channel binding
 *     .build();
 *
 *   // The build() call negotiates the SCRAM mechanism to be used. In this example,
 *   // since the server advertise support for the SCRAM-SHA-256-PLUS mechanism,
 *   // and the builder is set with the channel binding type and data, the constructed
 *   // scramClient will use the "SCRAM-SHA-256-PLUS" mechanism for authentication.
 *
 * // Send the client-first-message ("p=...,,n=...,r=...")
 * ClientFirstMessage clientFirstMsg = scramClient.clientFirstMessage();
 * ...
 * // Receive the server-first-message
 * ServerFirstMessage serverFirstMsg = scramClient.serverFirstMessage("r=...,s=...,i=...");
 * ...
 * // Send the client-final-message ("c=...,r=...,p=...")
 * ClientFinalMessage clientFinalMsg = scramClient.clientFinalMessage();
 * ...
 * // Receive the server-final-message, throw an ScramException on error
 * ServerFinalMessage serverFinalMsg = scramClient.serverFinalMessage("v=...");
 * }
* *

Commonly, a protocol will specify that the server advertises supported and available * mechanisms to the client via some facility provided by the protocol, and the client will then * select the "best" mechanism from this list that it supports and finds suitable. * *

When building the ScramClient, it provides mechanism negotiation based on parameters, if * channel binding is missing the client will use {@code "n"} as gs2-cbind-flag, if the channel * binding is set, but the mechanisms send by the server do not advertise the {@code -PLUS} * version, it will use {@code "y"} as gs2-cbind-flag, when both client and server support channel * binding, it will use {@code "p=" cb-name} as gs2-cbind-flag. * * @see RFC-5802: Salted Challenge Response * Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms * @see RFC-7677: SCRAM-SHA-256 and * SCRAM-SHA-256-PLUS Simple Authentication and Security Layer (SASL) Mechanisms */ public final class ScramClient implements MessageFlow { private final ScramMechanism scramMechanism; private final Gs2CbindFlag channelBinding; private final StringPreparation stringPreparation; private final String username; private final char[] password; private final byte[] saltedPassword; private final byte[] clientKey; private final byte[] serverKey; private final String cbindType; private final byte[] cbindData; private final String authzid; private final String nonce; private Stage currentState = Stage.NONE; private ClientFirstMessage clientFirstMessage; private ServerFirstProcessor serverFirstProcessor; private ClientFinalProcessor clientFinalProcessor; /** * Constructs a SCRAM client, to perform an authentication for a given user. This class can not be * instantiated directly, use a {@link #builder()} is used instead. * * @param builder The Builder used to initialize this client */ private ScramClient(@NotNull Builder builder) { this.channelBinding = builder.channelBinding; this.scramMechanism = builder.selectedScramMechanism; this.stringPreparation = builder.stringPreparation; this.username = builder.username; this.password = builder.password != null ? builder.password.clone() : null; this.saltedPassword = builder.saltedPassword; this.clientKey = builder.clientKey; this.serverKey = builder.serverKey; this.nonce = builder.nonce; this.cbindType = builder.cbindType; this.cbindData = builder.cbindData; this.authzid = builder.authzid; } /** * Returns the scram mechanism negotiated by this SASL client. * * @return the SCRAM mechanims selected during the negotiation */ public ScramMechanism getScramMechanism() { return scramMechanism; } /** * Returns the text representation of a SCRAM {@code client-first-message}. * * @apiNote should be the initial call and can be called only once * @return The {@code client-first-message} */ @Override public ClientFirstMessage clientFirstMessage() { if (currentState != Stage.NONE) { throw new IllegalStateException("Invalid state for processing client first message"); } this.clientFirstMessage = new ClientFirstMessage(channelBinding, cbindType, authzid, username, nonce); this.currentState = Stage.CLIENT_FIRST; return clientFirstMessage; } /** * Process the {@code server-first-message}, from its String representation. * * @apiNote should be called after {@link #clientFirstMessage()} and can be called only once * @param serverFirstMessage The {@code server-first-message} * @throws ScramParseException If the message is not a valid server-first-message * @throws IllegalArgumentException If the message is null or empty */ @Override public ServerFirstMessage serverFirstMessage(String serverFirstMessage) throws ScramParseException { if (currentState != Stage.CLIENT_FIRST) { throw new IllegalStateException("Invalid state for processing server first message"); } checkNotEmpty(serverFirstMessage, "serverFirstMessage"); this.serverFirstProcessor = new ServerFirstProcessor(scramMechanism, stringPreparation, serverFirstMessage, nonce, clientFirstMessage); this.currentState = Stage.SERVER_FIRST; return serverFirstProcessor.getServerFirstMessage(); } /** * Returns the text representation of a SCRAM {@code client-final-message}. * * @apiNote should be called after {@link #serverFirstMessage(String)} and can be called only once * @return The {@code client-final-message} */ @Override public ClientFinalMessage clientFinalMessage() { if (currentState != Stage.SERVER_FIRST || serverFirstProcessor == null) { throw new IllegalStateException("Invalid state for processing client final message"); } if (password != null) { this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password); Arrays.fill(password, (char) 0); // clear password after use } else if (saltedPassword != null) { this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(saltedPassword); } else if (clientKey != null && serverKey != null) { this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(clientKey, serverKey); } ClientFinalMessage clientFinalMessage = clientFinalProcessor.clientFinalMessage(cbindData); this.currentState = Stage.CLIENT_FINAL; return clientFinalMessage; } /** * Process and verify the {@code server-final-message}, from its String representation. * * @apiNote should be called after {@link #clientFinalMessage()} and can be called only once * @param serverFinalMessage The {@code server-final-message} * @throws ScramParseException If the message is not a valid * @throws ScramServerErrorException If the message is an error * @throws ScramInvalidServerSignatureException If the verification fails * @throws IllegalArgumentException If the message is null or empty */ @Override public ServerFinalMessage serverFinalMessage(String serverFinalMessage) throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException { if (currentState != Stage.CLIENT_FINAL || clientFinalProcessor == null) { throw new IllegalStateException("Invalid state for processing server final message"); } ServerFinalMessage receiveServerFinalMessage = clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage); this.currentState = Stage.SERVER_FINAL; return receiveServerFinalMessage; } /** * Creates a builder for {@link ScramClient ScramClient} instances. * * @return Builder instance to contruct a {@link ScramClient ScramClient} */ public static MechanismsBuildStage builder() { return new Builder(); } /** * Builder stage for the advertised mechanisms. */ public interface MechanismsBuildStage { /** * List of the advertised mechanisms that will be negotiated between the server and the client. * * @param scramMechanisms list with the IANA-registered mechanism name of this SASL client * @return {@code this} builder for use in a chained invocation */ UsernameBuildStage advertisedMechanisms(@NotNull Collection<@NotNull String> scramMechanisms); } /** * Builder stage for the required username. */ public interface UsernameBuildStage { /** * Sets the username. * * @param username the required username * @return {@code this} builder for use in a chained invocation */ PasswordBuildStage username(@NotNull String username); } /** * Builder stage for the password (or a ClientKey/ServerKey, or SaltedPassword). */ public interface PasswordBuildStage { /** * Sets the password. * * @param password the required password * @return {@code this} builder for use in a chained invocation */ FinalBuildStage password(char @NotNull [] password); /** * Sets the SaltedPassword. * * @param saltedPassword the required SaltedPassword * @return {@code this} builder for use in a chained invocation */ FinalBuildStage saltedPassword(byte @NotNull [] saltedPassword); /** * Sets the ClientKey/ServerKey. * * @param clientKey the required ClientKey * @param serverKey the required ServerKey * @return {@code this} builder for use in a chained invocation */ FinalBuildStage clientAndServerKey(byte @NotNull [] clientKey, byte @NotNull [] serverKey); } /** * Builder stage for the optional atributes and the final build() call. */ public interface FinalBuildStage { /** * If the client supports channel binding negotiation, this method sets the type and data used * for channel binding. * * @apiNote If {@code cbindType} or {@code cbindData} are null, sets the gs2-cbind-flag to 'n' * and does not use channel binding. * * @param cbindType channel bynding type name * @param cbindData channel binding data * @return {@code this} builder for use in a chained invocation */ FinalBuildStage channelBinding(@Nullable String cbindType, byte @Nullable [] cbindData); /** * Sets the StringPreparation, is recommended to leave the default SASL_PREPARATION. * * @param stringPreparation type of string preparation normalization * @return {@code this} builder for use in a chained invocation */ FinalBuildStage stringPreparation(@NotNull StringPreparation stringPreparation); /** * Sets the authzid. * * @param authzid the optional authorization id * @return {@code this} builder for use in a chained invocation */ FinalBuildStage authzid(@NotNull String authzid); /** * Sets a non-default length for the nonce generation. * *

The default value is 24. This call overwrites the length used for the client nonce. * * @param length The length of the nonce. Must be positive and greater than 0 * @return {@code this} builder for use in a chained invocation * @throws IllegalArgumentException If length is less than 1 */ FinalBuildStage nonceLength(int length); /** * The client will use a default nonce generator, unless an external one is provided by this * method. * * @apiNote you should rely on the default randomly generated nonce instead of this, this call * exists mostly for testing with a predefined nonce * @param nonceSupplier A supplier of valid nonce Strings. Please note that according to the SCRAM RFC only ASCII * printable characters (except the comma, ',') are permitted on a nonce. Length is not * limited. * @return {@code this} builder for use in a chained invocation * @throws IllegalArgumentException If nonceSupplier is null */ FinalBuildStage nonceSupplier(@NotNull Supplier<@NotNull String> nonceSupplier); /** * Selects a non-default SecureRandom instance, based on the given algorithm and optionally * provider. This SecureRandom instance will be used to generate secure random values, like the * ones required to generate the nonce. Algorithm and provider names are those supported by the * {@link SecureRandom} class. * * @param algorithm The name of the algorithm to use * @param provider The name of the provider of SecureRandom. Might be null * @return {@code this} builder for use in a chained invocation * @throws IllegalArgumentException If algorithm is null, or either the algorithm or provider * are not supported */ FinalBuildStage secureRandomAlgorithmProvider(@NotNull String algorithm, @Nullable String provider); /** * Returns the fully contructed ScramClient ready to start the message flow with the server. * * @return ScramClient specific for the set of parameters * @throws IllegalArgumentException if any parameter set is invalid */ ScramClient build(); } /** * Builds instances of type {@link ScramClient ScramClient}. Initialize attributes and then invoke * the {@link #build()} method to create an instance. * * @apiNote {@code Builder} is not thread-safe and generally should not be stored in a field or * collection, but instead used immediately to create instances. */ static final class Builder implements MechanismsBuildStage, UsernameBuildStage, PasswordBuildStage, FinalBuildStage { ScramMechanism selectedScramMechanism; Collection scramMechanisms; Gs2CbindFlag channelBinding = Gs2CbindFlag.CLIENT_NOT; StringPreparation stringPreparation = StringPreparation.SASL_PREPARATION; int nonceLength = 24; String nonce; SecureRandom secureRandom; String username; char[] password; byte[] saltedPassword; byte[] clientKey; byte[] serverKey; String cbindType; byte[] cbindData; String authzid; Supplier nonceSupplier; private Builder() { // called from ScramClient.builder() } @Override public FinalBuildStage stringPreparation(@NotNull StringPreparation stringPreparation) { this.stringPreparation = checkNotNull(stringPreparation, "stringPreparation"); return this; } @Override public FinalBuildStage channelBinding(@Nullable String cbindType, byte @Nullable [] cbindData) { this.cbindType = cbindType; this.cbindData = cbindData != null ? cbindData.clone() : null; this.channelBinding = cbindType != null && cbindData != null && !cbindType.isEmpty() && cbindData.length > 0 ? Gs2CbindFlag.CLIENT_YES_SERVER_NOT : Gs2CbindFlag.CLIENT_NOT; return this; } @Override public FinalBuildStage authzid(@NotNull String authzid) { this.authzid = checkNotEmpty(authzid, "authzid"); return this; } @Override public PasswordBuildStage username(@NotNull String username) { this.username = checkNotEmpty(username, "username"); return this; } @Override public FinalBuildStage password(char @NotNull [] password) { this.password = checkNotEmpty(password, "password"); return this; } @Override public FinalBuildStage saltedPassword(byte @NotNull [] saltedPassword) { this.saltedPassword = checkNotNull(saltedPassword, "saltedPassword"); return this; } @Override public FinalBuildStage clientAndServerKey(byte @NotNull [] clientKey, byte @NotNull [] serverKey) { this.clientKey = checkNotNull(clientKey, "clientKey"); this.serverKey = checkNotNull(serverKey, "serverKey"); return this; } @Override public UsernameBuildStage advertisedMechanisms( @NotNull Collection<@NotNull String> scramMechanisms) { checkNotNull(scramMechanisms, "scramMechanisms"); checkArgument(!scramMechanisms.isEmpty(), "scramMechanisms"); this.scramMechanisms = scramMechanisms; return this; } @Override public FinalBuildStage nonceLength(int length) { this.nonceLength = gt0(length, "length"); return this; } @Override public FinalBuildStage nonceSupplier(@NotNull Supplier<@NotNull String> nonceSupplier) { this.nonceSupplier = checkNotNull(nonceSupplier, "nonceSupplier"); return this; } @Override public FinalBuildStage secureRandomAlgorithmProvider(@NotNull String algorithm, @Nullable String provider) { try { this.secureRandom = null == provider ? SecureRandom.getInstance(algorithm) : SecureRandom.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException | NoSuchProviderException ex) { throw new IllegalArgumentException("Invalid algorithm or provider", ex); } return this; } @Override public ScramClient build() { final SecureRandom random = secureRandom != null ? secureRandom : new SecureRandom(); this.nonce = nonceSupplier != null ? nonceSupplier.get() : ScramFunctions.nonce(nonceLength, random); this.selectedScramMechanism = mechanismNegotiation(); return new ScramClient(this); } private ScramMechanism mechanismNegotiation() { final ScramMechanism cbind = selectMechanism(scramMechanisms, true); final ScramMechanism noncbind = selectMechanism(scramMechanisms, false); ScramMechanism mechanismNegotiaion = cbind != null ? cbind : noncbind; if (mechanismNegotiaion == null) { throw new IllegalArgumentException("Either a bare or plus mechanism must be present"); } if (channelBinding == Gs2CbindFlag.CLIENT_YES_SERVER_NOT && mechanismNegotiaion.isPlus()) { // Client and server supports channel binding this.channelBinding = Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; } else { // Client or server does not support channel binding if (noncbind == null) { throw new IllegalArgumentException("A non-PLUS mechanism was not advertised"); } this.cbindType = null; this.cbindData = null; mechanismNegotiaion = noncbind; } if (channelBinding == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED && (cbindType == null || cbindData == null)) { throw new IllegalArgumentException("Channel Binding type and data are required"); } return mechanismNegotiaion; } /** * This method classifies SCRAM mechanisms by two properties: whether they support channel * binding; and a priority, which is higher for safer algorithms (like SHA-256 vs SHA-1). * * @param channelBinding True to select {@code -PLUS} mechanisms. * @param scramMechanisms The mechanisms supported by the other peer * @return The selected mechanism, or null if no mechanism matched */ private static @Nullable ScramMechanism selectMechanism( @NotNull Collection<@NotNull String> scramMechanisms, boolean channelBinding) { ScramMechanism selectedMechanism = null; for (String mechanism : scramMechanisms) { ScramMechanism candidateMechanism = ScramMechanism.byName(mechanism); if (candidateMechanism != null && candidateMechanism.isPlus() == channelBinding && (selectedMechanism == null || candidateMechanism.ordinal() > selectedMechanism.ordinal())) { selectedMechanism = candidateMechanism; } } return selectedMechanism; } } } scram-3.2/scram-client/src/main/java/com/ongres/scram/client/ServerFirstProcessor.java000066400000000000000000000102201506234003700311520ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import java.util.Base64; import com.ongres.scram.common.ClientFirstMessage; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.ServerFirstMessage; import com.ongres.scram.common.StringPreparation; import com.ongres.scram.common.exception.ScramParseException; import org.jetbrains.annotations.NotNull; /** * Process a received server-first-message. Generate by calling * {@link ScramClient#receiveServerFirstMessage(String)}. */ final class ServerFirstProcessor { private final ScramMechanism scramMechanism; private final StringPreparation stringPreparation; private final ClientFirstMessage clientFirstMessage; private final ServerFirstMessage serverFirstMessage; ServerFirstProcessor(ScramMechanism scramMechanism, StringPreparation stringPreparation, @NotNull String receivedServerFirstMessage, @NotNull String nonce, @NotNull ClientFirstMessage clientFirstMessage) throws ScramParseException { this.scramMechanism = scramMechanism; this.stringPreparation = stringPreparation; this.serverFirstMessage = ServerFirstMessage.parseFrom(receivedServerFirstMessage, nonce); this.clientFirstMessage = clientFirstMessage; } @NotNull ServerFirstMessage getServerFirstMessage() { return serverFirstMessage; } /** * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and * also receive and parse the server-first-message. It is based on the user's password. * * @param password The user's password * @return The handler * @throws IllegalArgumentException If the message is null or empty */ ClientFinalProcessor clientFinalProcessor(char[] password) { return new ClientFinalProcessor( scramMechanism, stringPreparation, checkNotEmpty(password, "password"), Base64.getDecoder().decode(serverFirstMessage.getSalt().getBytes(UTF_8)), clientFirstMessage, serverFirstMessage); } /** * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and * also receive and parse the server-first-message. It is based on the clientKey and serverKey, * which, if available, provide an optimized path versus providing the original user's password. * * @param clientKey The client key, as per the SCRAM algorithm. It can be generated with: * {@link ScramFunctions#clientKey(ScramMechanism, byte[])} * @param serverKey The server key, as per the SCRAM algorithm. It can be generated with: * {@link ScramFunctions#serverKey(ScramMechanism, byte[])} * @return The handler * @throws IllegalArgumentException If the clientKey/serverKey is null */ ClientFinalProcessor clientFinalProcessor(byte[] clientKey, byte[] serverKey) { return new ClientFinalProcessor( scramMechanism, checkNotNull(clientKey, "clientKey"), checkNotNull(serverKey, "serverKey"), clientFirstMessage, serverFirstMessage); } /** * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and * also receive and parse the server-first-message. It is based on the saltedPassword, * which, if available, provide an optimized path versus providing the original user's password. * * @param saltedPassword The salted password, as per the SCRAM algorithm. It can be generated * with: * {@link ScramFunctions#saltedPassword(ScramMechanism, StringPreparation, char[], byte[], int)} * @return The handler * @throws IllegalArgumentException If the saltedPassword is null */ ClientFinalProcessor clientFinalProcessor(byte[] saltedPassword) { return new ClientFinalProcessor( scramMechanism, checkNotNull(saltedPassword, "saltedPassword"), clientFirstMessage, serverFirstMessage); } } scram-3.2/scram-client/src/main/java/com/ongres/scram/client/package-info.java000066400000000000000000000005511506234003700273260ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ /** * This module expose the client implementation of Salted Challenge Response * Authentication Mechanism (SCRAM). It provides a high level and easy to use API to negotiate the * mechanism and the message flow used for authentication. */ package com.ongres.scram.client; scram-3.2/scram-client/src/main/java9/000077500000000000000000000000001506234003700175715ustar00rootroot00000000000000scram-3.2/scram-client/src/main/java9/module-info.java000066400000000000000000000003101506234003700226440ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ module com.ongres.scram.client { requires transitive com.ongres.scram.common; exports com.ongres.scram.client; }scram-3.2/scram-client/src/test/000077500000000000000000000000001506234003700166125ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/000077500000000000000000000000001506234003700175335ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/000077500000000000000000000000001506234003700203115ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/example/000077500000000000000000000000001506234003700217445ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/example/ChannelBindingNegotiationTest.java000066400000000000000000000075401506234003700305210ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.example; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Arrays; import java.util.Base64; import com.ongres.scram.client.ScramClient; import com.ongres.scram.common.ClientFirstMessage; import com.ongres.scram.common.Gs2CbindFlag; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.util.TlsServerEndpoint; import org.junit.jupiter.api.Test; /** * Servers that support the use of channel binding SHOULD advertise * both the non-PLUS (SCRAM-{@code hash-function}) and PLUS-variant (SCRAM- * {@code hash-function}-PLUS) mechanism name. If the server cannot * support channel binding, it SHOULD advertise only the non-PLUS- * variant. If the server would never succeed in the authentication * of the non-PLUS-variant due to policy reasons, it MUST advertise * only the PLUS-variant. */ class ChannelBindingNegotiationTest { private static final byte[] CBIND_DATA = Base64.getDecoder().decode("Dv4abLuK1TiHcq3tJXrHODILGF" + "QuC1M4kfP4w7dyRvjadaqGq8D/Po1XeJZpzUqal+mAKXNGytneo5KPOsJnYA=="); /** * If the client supports channel binding and the server does not * appear to (i.e., the client did not see the -PLUS name advertised * by the server), then the client MUST NOT use an "n" gs2-cbind- * flag. */ @Test void clientYesServerNo() throws ScramParseException { ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256")) .username("user") .password("pencil".toCharArray()) .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, CBIND_DATA) .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") .build(); assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); ClientFirstMessage msg = scramSession.clientFirstMessage(); assertEquals(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, msg.getGs2Header().getChannelBindingFlag()); } /** * Clients that support mechanism negotiation and channel binding * MUST use a "p" gs2-cbind-flag when the server offers the PLUS- * variant of the desired GS2 mechanism. */ @Test void channelBindingRequired() throws ScramParseException { ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) .username("user") .password("pencil".toCharArray()) .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, CBIND_DATA) .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") .build(); assertEquals("SCRAM-SHA-256-PLUS", scramSession.getScramMechanism().getName()); ClientFirstMessage msg = scramSession.clientFirstMessage(); assertEquals(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, msg.getGs2Header().getChannelBindingFlag()); } /** * If the client does not support channel binding, then it MUST use * an "n" gs2-cbind-flag. Conversely, if the client requires the use * of channel binding then it MUST use a "p" gs2-cbind-flag. Clients * that do not support mechanism negotiation never use a "y" gs2- * cbind-flag, they use either "p" or "n" according to whether they * require and support the use of channel binding or whether they do * not, respectively. */ @Test void clientNo() throws ScramParseException { ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) .username("user") .password("pencil".toCharArray()) .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") .build(); assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); ClientFirstMessage msg = scramSession.clientFirstMessage(); assertEquals(Gs2CbindFlag.CLIENT_NOT, msg.getGs2Header().getChannelBindingFlag()); } } scram-3.2/scram-client/src/test/java/com/example/ScramClientTest.java000066400000000000000000000105161506234003700256560ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.example; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; import java.io.InputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import com.ongres.scram.client.ScramClient; import com.ongres.scram.common.ClientFinalMessage; import com.ongres.scram.common.util.TlsServerEndpoint; import org.junit.jupiter.api.Test; class ScramClientTest { @Test void completeTest() throws CertificateException, IOException { final X509Certificate cert = getCert(); final byte[] channelBindingData = TlsServerEndpoint.getChannelBindingData(cert); ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) .username("user") .password("pencil".toCharArray()) .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData) .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") .build(); assertEquals("SCRAM-SHA-256-PLUS", scramSession.getScramMechanism().getName()); assertEquals("p=tls-server-end-point,,n=user,r=rOprNGfwEbeRWgbNEkqO", scramSession.clientFirstMessage().toString()); assertDoesNotThrow( () -> scramSession.serverFirstMessage( "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + "s=W22ZaJ0SNY7soEsUEjb6gQ==," + "i=4096")); ClientFinalMessage clientFinalMessage = scramSession.clientFinalMessage(); assertEquals( "c=cD10bHMtc2VydmVyLWVuZC1wb2ludCwsDv4abLuK1TiHcq3tJ" + "XrHODILGFQuC1M4kfP4w7dyRvjadaqGq8D/Po1XeJZpzUqal+mAKXNGytneo5KPOsJnYA==" + ",r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0" + ",p=WIBtRXGH4I4R2CU1/tHa2YREwrJjLFa3/pKJQH/0Ofo=", clientFinalMessage.toString()); assertDoesNotThrow( () -> scramSession.serverFinalMessage("v=9k31qsYXd74d6BnbFf9jE+r9un6a8ou85FYeNxDAdqc=")); } private X509Certificate getCert() throws CertificateException, IOException { String pemFilePath = "/SHA512withRSA.pem"; // Create a CertificateFactory object for X.509 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); final X509Certificate cert; try (InputStream inputStream = getClass().getResourceAsStream(pemFilePath)) { // Check if the file exists assertNotNull(inputStream, "Certificate file not found"); // Load the PEM file as an X.509 certificate cert = (X509Certificate) certFactory.generateCertificate(inputStream); // Perform your assertions or further tests here assertNotNull(cert); assertEquals("SHA512withRSA", cert.getSigAlgName()); } return cert; } @Test void iterationTest() throws CertificateException, IOException { final X509Certificate cert = getCert(); final byte[] channelBindingData = TlsServerEndpoint.getChannelBindingData(cert); ScramClient scramSession = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256")) .username("postgres") .password("pencil".toCharArray()) .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData) .nonceSupplier(() -> "1q^MGrWUi{etW+H7(#k431kB") .build(); assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); assertEquals("y,,n=postgres,r=1q^MGrWUi{etW+H7(#k431kB", scramSession.clientFirstMessage().toString()); assertDoesNotThrow( () -> scramSession.serverFirstMessage( "r=1q^MGrWUi{etW+H7(#k431kBdAr3CWX7B6houDP4f7Z2XEpZ," + "s=Fgh8JU2AlRjBHUsIU/GgtQ==," + "i=1000000")); ClientFinalMessage clientFinalMessage = scramSession.clientFinalMessage(); assertEquals( "c=eSws," + "r=1q^MGrWUi{etW+H7(#k431kBdAr3CWX7B6houDP4f7Z2XEpZ," + "p=vQ3IyYl3LvjWOlK2c0IP5QAi6XB7Dm0Axo0V51DcHZA=", clientFinalMessage.toString()); assertDoesNotThrow( () -> scramSession.serverFinalMessage("v=sz/isCwVSUn/TBWeYABz6WaoZIcfsui9NPaJCoxxAjY=")); } } scram-3.2/scram-client/src/test/java/com/ongres/000077500000000000000000000000001506234003700216065ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/ongres/scram/000077500000000000000000000000001506234003700227135ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/ongres/scram/JarFileCheckIT.java000066400000000000000000000044751506234003700262770ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.module.ModuleDescriptor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class JarFileCheckIT { private static JarFile jarFile; private static Path buildJarPath; @BeforeAll static void beforeAll() throws IOException { buildJarPath = Paths.get(System.getProperty("buildJar")); assertTrue(Files.exists(buildJarPath)); jarFile = new JarFile(buildJarPath.toFile(), true); } @AfterAll static void afterAll() throws IOException { jarFile.close(); } @Test void checkLicense() throws IOException { JarEntry jarLicense = jarFile.getJarEntry("META-INF/LICENSE"); assertNotNull(jarLicense, "LICENSE file should be present in the final JAR file"); try (InputStream is = jarFile.getInputStream(jarLicense); BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) { String line = reader.readLine(); assertEquals("Copyright (c) 2017 OnGres, Inc.", line); } } @Test void checkMultiReleaseManifest() throws IOException { Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); String multiReleaseValue = mainAttributes.getValue(new Attributes.Name("Multi-Release")); assertNotNull(multiReleaseValue); assertEquals("true", multiReleaseValue); } @Test void checkModuleInfoPresent() throws IOException { JarEntry jarModuleInfo = jarFile.getJarEntry("META-INF/versions/9/module-info.class"); ModuleDescriptor moduleDescriptor = ModuleDescriptor.read(jarFile.getInputStream(jarModuleInfo)); assertNotNull(moduleDescriptor); assertEquals("com.ongres.scram.client", moduleDescriptor.name()); } } scram-3.2/scram-client/src/test/java/com/ongres/scram/client/000077500000000000000000000000001506234003700241715ustar00rootroot00000000000000scram-3.2/scram-client/src/test/java/com/ongres/scram/client/RfcExampleSha1.java000066400000000000000000000031331506234003700275770ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; /** * Constants for examples of the RFC for SHA-1 tests. */ class RfcExampleSha1 { public static final String USER = "user"; public static final String PASSWORD = "pencil"; public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; public static final String CLIENT_FIRST_MESSAGE = "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; public static final int SERVER_ITERATIONS = 4096; public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + ",i=" + SERVER_ITERATIONS; public static final String GS2_HEADER_BASE64 = "biws"; public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + ",r=" + FULL_NONCE; public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + SERVER_FIRST_MESSAGE + "," + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; } scram-3.2/scram-client/src/test/java/com/ongres/scram/client/ScramBuilderTest.java000066400000000000000000000120571506234003700302550ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.client; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import java.util.Base64; import com.ongres.scram.common.ClientFinalMessage; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.StringPreparation; import com.ongres.scram.common.exception.ScramParseException; import org.junit.jupiter.api.Test; class ScramBuilderTest { @Test void getValid() throws ScramParseException { ScramClient client1 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.POSTGRESQL_PREPARATION) .build(); assertNotNull(client1); assertNotNull(client1.clientFirstMessage()); ScramClient client2 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.SASL_PREPARATION) .nonceLength(12) .channelBinding("tls-server-end-point", new byte[0]) .build(); assertNotNull(client2); assertNotNull(client2.clientFirstMessage()); ScramClient client3 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.NO_PREPARATION) .nonceLength(36) .build(); assertNotNull(client3); assertNotNull(client3.clientFirstMessage()); ScramClient client4 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.NO_PREPARATION) .nonceLength(64) .build(); assertNotNull(client4); assertNotNull(client4.clientFirstMessage()); ScramClient client5 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.NO_PREPARATION) // .secureRandomAlgorithmProvider("PKCS11", null) .nonceLength(64) .build(); assertNotNull(client5); assertNotNull(client5.clientFirstMessage()); ScramClient client6 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1")) .username("*") .password("*".toCharArray()) .build(); assertNotNull(client6); assertNotNull(client6.clientFirstMessage()); ScramClient client7 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256-PLUS")) .username("*") .password("*".toCharArray()) .channelBinding("tls-server-end-point", new byte[10]) .stringPreparation(StringPreparation.NO_PREPARATION) .build(); assertNotNull(client7); assertNotNull(client7.clientFirstMessage()); byte[] saltedPassword = ScramFunctions.saltedPassword(ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, RfcExampleSha1.PASSWORD.toCharArray(), Base64.getDecoder().decode(RfcExampleSha1.SERVER_SALT), 4096); ScramClient client8 = ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS")) .username(RfcExampleSha1.USER) .saltedPassword(saltedPassword) .nonceSupplier(() -> RfcExampleSha1.CLIENT_NONCE) .build(); assertNotNull(client8); assertEquals("SCRAM-SHA-1", client8.getScramMechanism().getName()); assertNotNull(client8.clientFirstMessage()); assertThrows(IllegalStateException.class, () -> client8.clientFinalMessage()); assertDoesNotThrow(() -> client8.serverFirstMessage(RfcExampleSha1.SERVER_FIRST_MESSAGE)); assertThrows(IllegalStateException.class, () -> client8.clientFirstMessage()); assertThrows(IllegalStateException.class, () -> client8.serverFinalMessage(RfcExampleSha1.SERVER_FINAL_MESSAGE)); ClientFinalMessage clientFinalMessage = client8.clientFinalMessage(); assertEquals(RfcExampleSha1.CLIENT_FINAL_MESSAGE, clientFinalMessage.toString()); assertThrows(IllegalStateException.class, () -> client8.serverFirstMessage(RfcExampleSha1.SERVER_FIRST_MESSAGE)); assertDoesNotThrow( () -> client8.serverFinalMessage(RfcExampleSha1.SERVER_FINAL_MESSAGE)); } @Test void getInvalid() { assertThrows(IllegalArgumentException.class, () -> ScramClient.builder() .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1-PLUS")) .username("*") .password("*".toCharArray()) .stringPreparation(StringPreparation.NO_PREPARATION) .build()); } } scram-3.2/scram-client/src/test/resources/000077500000000000000000000000001506234003700206245ustar00rootroot00000000000000scram-3.2/scram-client/src/test/resources/SHA3-512withECDSA.pem000066400000000000000000000016101506234003700237640ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICbTCCAc2gAwIBAgIUF/r9sHKRKU0yh9d1vq6wCNdF2NswCwYJYIZIAWUDBAMM MEcxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNVBAcMCkFsY29i ZW5kYXMxEjAQBgNVBAoMCU9uR3JlcyBTTDAeFw0yNDAzMjAxNTIzNDdaFw0zNDAz MTgxNTIzNDdaMEcxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNV BAcMCkFsY29iZW5kYXMxEjAQBgNVBAoMCU9uR3JlcyBTTDCBmzAQBgcqhkjOPQIB BgUrgQQAIwOBhgAEAbdhZ97Wg6jLy+vwojByzoRwQOdPbY1Ye6CPY2bmDJ+At7j1 dw+a/lB3MkTMhIlCPgKO+hJty8dZBIdg/hCWNtqtAb6tSsv6RQw3nAv1pY5d5qWF nVQzYiAWLsZ5MzTWKMcy3xJTAW+YEBwCzT3Ov6mO9wfz20I5SNLjXLaixMjh4Mup o1MwUTAdBgNVHQ4EFgQUlhN7FIdCf4bBZS1NDmCLpJHOJWwwHwYDVR0jBBgwFoAU lhN7FIdCf4bBZS1NDmCLpJHOJWwwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME AwwDgYwAMIGIAkIByQyFB6lnc/Hp/eTjibbydSrGp/3ijwHFWkf44Bb9Xxsko5g4 97DtrgL/sRAD9HPnF6P4jvGQmKT4r40e4Z4xg5oCQgGxVvM9YeGFgAHWesH/Uz8A rEySKFQU6HWOg3VGk/i+h8Mf3Q3qOMw0Mq8NTHp0t4yNwwltmZDRpzZvdF3dINMa Cg== -----END CERTIFICATE----- scram-3.2/scram-client/src/test/resources/SHA512withRSA.pem000066400000000000000000000065161506234003700234440ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIJpzCCBZugAwIBAgIUWzmIIuqdxXzf3oQiJ/UhLS9r2kcwDQYJKoZIhvcNAQEN BQAwbzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDETMBEGA1UEBwwKQWxj b2JlbmRhczEUMBIGA1UECgwLT25HcmVzIEluYy4xDjAMBgNVBAsMBVNDUkFNMRQw EgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yNDAzMTMxNzQxNTVaFw0zNDAzMTExNzQx NTVaMG8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNVBAcMCkFs Y29iZW5kYXMxFDASBgNVBAoMC09uR3JlcyBJbmMuMQ4wDAYDVQQLDAVTQ1JBTTEU MBIGA1UEAwwLZXhhbXBsZS5jb20wggQWMA0GCSqGSIb3DQEBAQUAA4IEAwAwggP+ AoID9QCtTBX88QlbjYb3FsXDDWPZ6P3qrNjXhdecKdpxv81K2I3W/iBz3tEpj+FT 4pfWu5jYAf85yrnkg2/eFD2zGrWq+qfQnsFE+6N0M/JW6t5hTVz3rAh2C58I0cMo WIWJt4808b7VLeIwMJwE6hcGSIgyuVOgEhEqrnSmXD3PIoAkrAvpiLpqOCAdG7xN QUXySOeq8yIC3uCVwTqMonkB488RcaTMlDO4+3GCuiIHhiqAjtVGlmn0xTaLZdA7 3hQPDs3qkqWHM7a2pLNrdPoJgF5BdsTq5Rbw5TzNe8lm9NjyNbUilOPloci0VNg5 qgvKuRRqd1285wzCMEb/FQzu6eTv57pzmOzWyzd5D0K53GZv6Cb+yaz7xx6sUTDF q2qQClllXMGu+P0kpkdH2qBIRuxrOiPvcZ2djKk+xa4fDbfLI0Hk1STpqZq1KjOR 1+eFYGCawo/JFjMsQfyq/CVQDqy5djnKKOrbCKl3ApEdSvTBcoBbpIroKMqMIssB owu0b8bqEOl/tVamGm8oELh6iDQMD5OKlg5ruihp0VTvGeGbDU6LTksx0dygvIen dtIUoiEfaP/7LfTUWQ6X2dc00FD8lRRSygKHqg/Rq7HJ5Jmrfz7e4ozOnAqemxv2 P0kNqT9qhgBdbNaNlwQsVf5ikbLeRQCSVvgKhcmB0rM57E+7cf3HtMZ5ECfbgF91 cK67bVfFnnlR6apEN1rbBxES/OHJu7X6yr8CJc+FRHyg48/hLIH1p6aylnqlZxOX kTSOxfM6sgUsR6Krt7FWD6Dah7WAWpzokQpxm+Kdr17WBJxXcr9HptBApap5CWP2 0dtg3Uz//ParEu2e9PWeU+Dt6oJPGg7FuTke+70w4jzE6IrBlGR2+uJBazH4ibC7 k2uiaTB6OSu00G5K7PBok2C+rFacM+3BA7xuk84zhz/VHBi1g3yVtiaRaZ/lXHNU M0M6Xmtzqe10XRHo22habv4sLa6KnC27gMNQ2U8D9G3JIs1cvOqf1rFY6nmU2V7f 3Lmi9jmxoSFHYHWYF6hgAiufVhEmbOAeA34usH2DKqLJdbVBfI9eTcfxjQZiHxKp 2dAyqGP6fRhlijLpCyDk2yGb8r5oMpOvf234VgVAflPnaeRUvTpX8jAgy6N2WYXc uYuSMsNz762NlwE2iCD88jAgKlOBj4Jl2/E6UDGBocdpETF+yW9MRyNYL+msL1Yx UunYHk2CQhAt2sQBjveJpPUE+lwZUJ7s2ItHalb7oiffGCaIMQkCuzoNgb7iu1JE /Nzl/OjUJVYnavVY5Wb/VqJvUAeWWb4u8a617ip72h/1xyOm9mw7w+VFe38aqjk8 89hH8J+3vzXjAgMBAAGjUzBRMB0GA1UdDgQWBBQoMJjtzt7sWYytC3HeG63r8/Z/ gzAfBgNVHSMEGDAWgBQoMJjtzt7sWYytC3HeG63r8/Z/gzAPBgNVHRMBAf8EBTAD AQH/MA0GCSqGSIb3DQEBDQUAA4ID9QCLpHpEX7LAwTevNucEwnw1N5leRCRleK0B zTdCQiLHNJlZx/I9JnKTymc87FStFXE/JODDWu3Ggo6XXYjDeeKKCMfM9YYaOvAX 2Rmyjzn0wU9dgSIcY7LuepIsfy+Ko9olyT3dTQfqdfl44qiMFwtRhI5Xv6XG7QWa icUScrG1zBfsQtplpHTM67clnCqytVaHSQBxkBl9G/+nTz1DjYz0avOWQ4A0Q7S1 N9duk5pMGW7CcP7UP646MMYLSKO8q8AHRhKCLgq2f4FB1jGu80l3VBc8kMCX0Osb mjF1dFtYcWgzZlAgbPOEhr22d7TdYFfpuyl9Td60VdlzFWKe8mK1w8cu0DxVYML5 4NP+QeL0SduPmzRNgfF+0kHiav7wwg7RXh6aILyt9w9nCubhYhbEyai+MRX//iVg J+DhBHmB7FgWlDx+wzIjDjRJI2BJB9EK5gIM97Cew6cELWiDvKY836vrAh+TZg2G A5GwFxr3kjbYs1G2sOMaSH+Fq8+VpIBr9cQ7v1WDbrsYzStnjYaFx+uqN+SjsPh8 EgT+8+SiaKioS2yOrctovcVXLiB5HJEQdkaTWApZ+KVluJQLF0XFqf1gp2mBJVjy IN2HpRV+JUX/CHGBXE8KeH5aOJPjWNq3k2vJy+3USjH7YvNysLOhekOvRuh5uNxU 1yVWmPWsv5ww0TJZT5L0t+KHTvoSZE2I12PnonDz1t4sGQFXexkB402tkbV2nxJB 6fnks5RMDhXjr5b9/Oca02PPWSuu35ypo36Sh5cBtQ/lRT30cRcW9v1Pkk1N5I+4 FXFEjr5kPantmd2zM5m1z+EfWapVINMv4T9B4bwR8jskM2Vm3a+1Gk1NY5cRxHdS T+EQY+Z40mWxieRNHe4ss5k7uefBq3GXw1FGUQQfHFctV/yKMhjgLXleQcyr82rh t+vsfiU6Z3fIvb8MZzzEfNkLcnUTUkbeHxFYDvEter/uVFVpWyzYg9o8pliuLMf7 ggA5RgA7y6MH5XxcgHEVNx572eHdpFx6q8signasHW8p9fps60rtJGVvF0x98vK1 VDr2CIbKgnqprIc31Xkoata3U7Xevubt6Pm/kyBOs9SnASIHdZvlLQqILOKxjaMf ZD2rJ6rNY+8V14NHXTaEAgefMrF42RXZ4RxGHTNDrxkjieyUDMc9dpnOdCxT50Ai 8NKdtfHT/K0yBNvDQtg4OoHDxy9ikFYyRKJrnVCCA2hfSbuMxiAg1fewrUBPVKy4 T98gvWxCQ9fdT/wjPgn9oMv1lEKtM0JKdM0SshK7EA3PWaQfsOfvd4v00dZaDxRN W4XrAMUHzJ4YICoubSNVl/gXCgqF8kcEAiJY -----END CERTIFICATE----- scram-3.2/scram-common/000077500000000000000000000000001506234003700150565ustar00rootroot00000000000000scram-3.2/scram-common/pom.xml000066400000000000000000000026171506234003700164010ustar00rootroot00000000000000 4.0.0 com.ongres.scram scram-parent 3.2 ../scram-parent/pom.xml scram-common SCRAM - Common com.ongres.stringprep saslprep coverage org.jacoco jacoco-maven-plugin run-its org.apache.maven.plugins maven-failsafe-plugin org.apache.maven.plugins maven-invoker-plugin scram-3.2/scram-common/src/000077500000000000000000000000001506234003700156455ustar00rootroot00000000000000scram-3.2/scram-common/src/main/000077500000000000000000000000001506234003700165715ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/000077500000000000000000000000001506234003700175125ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/000077500000000000000000000000001506234003700202705ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/000077500000000000000000000000001506234003700215655ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/000077500000000000000000000000001506234003700226725ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/000077500000000000000000000000001506234003700241625ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/AbstractCharAttributeValue.java000066400000000000000000000023221506234003700322460ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Construct and write generic CharAttribute-Value pairs. * *

Concrete sub-classes should also provide a static parse(String) creation method. */ abstract class AbstractCharAttributeValue extends StringWritable { private final char charAttribute; private final @Nullable String value; protected AbstractCharAttributeValue(@NotNull T charAttribute, @Nullable String value) { if (null != value && value.isEmpty()) { throw new IllegalArgumentException("Value should be either null or non-empty"); } this.charAttribute = checkNotNull(charAttribute, "attribute").getChar(); this.value = value; } public final char getChar() { return charAttribute; } public @Nullable String getValue() { return value; } @Override StringBuilder writeTo(StringBuilder sb) { sb.append(charAttribute); if (null != value) { sb.append('=').append(value); } return sb; } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/AbstractScramMessage.java000066400000000000000000000007701506234003700310670ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import org.jetbrains.annotations.NotNull; /** * Basic implementation of the StringWritable interface, that overrides the toString() method. */ abstract class AbstractScramMessage extends StringWritable { /** * String representation of the SCRAM message. */ @Override public final @NotNull String toString() { return writeTo(new StringBuilder(48)).toString(); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/CharSupplier.java000066400000000000000000000005571506234003700274350ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; /** * Represents an attribute (a key name) that is represented by a single char. */ interface CharSupplier { /** * Return the char used to represent this attribute. * * @return The character of the attribute */ char getChar(); } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ClientFinalMessage.java000066400000000000000000000115731506234003700305310ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import java.nio.charset.StandardCharsets; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Constructs and parses client-final-messages. * * * * * * * * * * * * * * * * * * * *
Formal Syntax:
cbind-inputgs2-header [ cbind-data ]
* ;; cbind-data MUST be present for
* ;; gs2-cbind-flag of "p" and MUST be absent
* ;; for "y" or "n".
channel-binding"c=" base64
* ;; base64 encoding of cbind-input.
client-final-message-without-proofchannel-binding "," nonce ["," extensions]
client-final-messageclient-final-message-without-proof "," proof
* * @implNote {@code extensions} are not supported. * @see [RFC5802] Section 7 */ public final class ClientFinalMessage extends AbstractScramMessage { /** * channel-binding = "c=" base64 encoding of cbind-input. */ private final String cbindInput; /** * nonce = "r=" c-nonce [s-nonce]. Second part provided by server. */ private final String nonce; /** * proof = "p=" base64. */ private final byte[] proof; /** * Constructus a client-final-message with the provided gs2Header (the same one used in the * client-first-message), optionally the channel binding data, and the nonce. This method is * intended to be used by SCRAM clients, and not to be constructed directly. * * @param gs2Header The GSS-API header * @param cbindData If using channel binding, the channel binding data * @param nonce The nonce * @param proof The bytes representing the computed client proof */ public ClientFinalMessage(Gs2Header gs2Header, byte[] cbindData, String nonce, byte[] proof) { this.cbindInput = generateCBindInput(gs2Header, cbindData); this.nonce = checkNotEmpty(nonce, "nonce"); this.proof = checkNotNull(proof, "proof").clone(); } /** * Return the channel-binding "c=" base64 encoding of cbind-input. * * @return the {@code channel-binding} */ public String getCbindInput() { return cbindInput; } /** * Return the nonce. * * @return the {@code nonce} */ public String getNonce() { return nonce; } /** * Return the proof. * * @return the {@code proof} */ public byte[] getProof() { return proof.clone(); } private static void checkChannelBinding(Gs2Header gs2Header, byte[] cbindData) { final Gs2CbindFlag channelBindingFlag = gs2Header.getChannelBindingFlag(); if (channelBindingFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED && null == cbindData) { throw new IllegalArgumentException("Channel binding data is required"); } if (channelBindingFlag != Gs2CbindFlag.CHANNEL_BINDING_REQUIRED && null != cbindData) { throw new IllegalArgumentException("Channel binding data should not be present"); } } private static @NotNull String generateCBindInput(@NotNull Gs2Header gs2Header, byte @Nullable [] cbindData) { checkNotNull(gs2Header, "gs2Header"); checkChannelBinding(gs2Header, cbindData); byte[] cbindInput = gs2Header.writeTo(new StringBuilder(32)) .append(',').toString().getBytes(StandardCharsets.UTF_8); if (null != cbindData && cbindData.length != 0) { byte[] cbindInputNew = new byte[cbindInput.length + cbindData.length]; System.arraycopy(cbindInput, 0, cbindInputNew, 0, cbindInput.length); System.arraycopy(cbindData, 0, cbindInputNew, cbindInput.length, cbindData.length); cbindInput = cbindInputNew; } return ScramStringFormatting.base64Encode(cbindInput); } private StringBuilder writeToWithoutProof(@NotNull StringBuilder sb) { return StringWritableCsv.writeTo(sb, new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, cbindInput), new ScramAttributeValue(ScramAttributes.NONCE, nonce)); } static StringBuilder withoutProof(StringBuilder sb, Gs2Header gs2Header, byte[] cbindData, String nonce) { return StringWritableCsv.writeTo(sb, new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, generateCBindInput(gs2Header, cbindData)), new ScramAttributeValue(ScramAttributes.NONCE, nonce)); } @Override StringBuilder writeTo(StringBuilder sb) { writeToWithoutProof(sb); return StringWritableCsv.writeTo( sb, null, // This marks the position of writeToWithoutProof, required for the "," new ScramAttributeValue(ScramAttributes.CLIENT_PROOF, ScramStringFormatting.base64Encode(proof))); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ClientFirstMessage.java000066400000000000000000000153141506234003700305640ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.exception.ScramParseException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Constructs and parses client-first-messages. Message contains a {@code gs2-header}, a username * and a * nonce. * * * * * * * * * * * *
Formal Syntax:
client-first-message-bare[reserved-mext ","] username "," nonce ["," extensions]
client-first-messagegs2-header client-first-message-bare
* * @implNote {@code extensions} are not supported. * @see [RFC5802] Section 7 */ public final class ClientFirstMessage extends AbstractScramMessage { /** * gs2-header = gs2-cbind-flag "," [ authzid ] ",". */ private final @NotNull Gs2Header gs2Header; /** * username = "n=" saslname. */ private final @NotNull String username; /** * nonce= "r=" c-nonce [s-nonce]. */ private final @NotNull String clientNonce; /** * Constructs a client-first-message for the given user, nonce and gs2Header. This constructor is * intended to be instantiated by a scram client, and not directly. The client should be providing * the header, and nonce (and probably the user too). * * @param gs2Header The GSS-API header * @param username The SCRAM username * @param clientNonce The nonce for this session * @throws IllegalArgumentException If any of the arguments is null or empty */ public ClientFirstMessage(@NotNull Gs2Header gs2Header, @NotNull String username, @NotNull String clientNonce) { this.gs2Header = checkNotNull(gs2Header, "gs2Header"); this.username = ScramStringFormatting.toSaslName(checkNotEmpty(username, "username")); this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); } /** * Constructs a client-first-message for the given parameters. Under normal operation, this * constructor is intended to be instantiated by a scram client, and not directly. However, this * constructor is more user- or test-friendly, as the arguments are easier to provide without * building other indirect object parameters. * * @param gs2CbindFlag The channel-binding flag * @param authzid The optional authzid * @param cbindName The optional channel binding name * @param username The SCRAM user * @param clientNonce The nonce for this session * @throws IllegalArgumentException If the flag, user or nonce are null or empty */ public ClientFirstMessage(@NotNull Gs2CbindFlag gs2CbindFlag, @Nullable String cbindName, @Nullable String authzid, @NotNull String username, @NotNull String clientNonce) { this(new Gs2Header(gs2CbindFlag, cbindName, authzid), username, clientNonce); } /** * Constructs a client-first-message for the given parameters, with no channel binding nor * authzid. Under normal operation, this constructor is intended to be instantiated by a scram * client, and not directly. However, this constructor is more user- or test-friendly, as the * arguments are easier to provide without building other indirect object parameters. * * @param username The SCRAM user * @param clientNonce The nonce for this session * @throws IllegalArgumentException If the user or nonce are null or empty */ public ClientFirstMessage(@NotNull String username, @NotNull String clientNonce) { this(new Gs2Header(Gs2CbindFlag.CLIENT_NOT), username, clientNonce); } /** * Check to probe if gs2-cbind-flag is set to "p=". * * @return true if the message requires channel binding */ public boolean isChannelBindingRequired() { return gs2Header.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; } /** * Return the Gs2Header. * * @return the {@code gs2-header} */ public @NotNull Gs2Header getGs2Header() { return gs2Header; } /** * Return the username. * * @return the {@code "n=" saslname} */ public @NotNull String getUsername() { return username; } /** * Return the client nonce. * * @return the {@code c-nonce} */ public @NotNull String getClientNonce() { return clientNonce; } /** * Limited version of the StringWritableCsv method, that doesn't write the GS2 header. This method * is useful to construct the auth message used as part of the SCRAM algorithm. * * @param sb A StringBuffer where to write the data to. * @return The same StringBuffer */ @NotNull StringBuilder clientFirstMessageBare(@NotNull StringBuilder sb) { return StringWritableCsv.writeTo( sb, new ScramAttributeValue(ScramAttributes.USERNAME, username), new ScramAttributeValue(ScramAttributes.NONCE, clientNonce)); } /** * Construct a {@link ClientFirstMessage} instance from a message (String). * * @param clientFirstMessage The String representing the client-first-message * @return The instance * @throws ScramParseException If the message is not a valid client-first-message * @throws IllegalArgumentException If the message is null or empty */ @NotNull public static ClientFirstMessage parseFrom(@NotNull String clientFirstMessage) throws ScramParseException { checkNotEmpty(clientFirstMessage, "clientFirstMessage"); @NotNull String @NotNull [] userNonceString; try { userNonceString = StringWritableCsv.parseFrom(clientFirstMessage, 2, 2); } catch (IllegalArgumentException e) { throw new ScramParseException("Illegal series of attributes in client-first-message", e); } ScramAttributeValue user = ScramAttributeValue.parse(userNonceString[0]); if (ScramAttributes.USERNAME.getChar() != user.getChar()) { throw new ScramParseException("user must be the 3rd element of the client-first-message"); } ScramAttributeValue nonce = ScramAttributeValue.parse(userNonceString[1]); if (ScramAttributes.NONCE.getChar() != nonce.getChar()) { throw new ScramParseException("nonce must be the 4th element of the client-first-message"); } Gs2Header gs2Header = Gs2Header.parseFrom(clientFirstMessage); // Takes first two fields return new ClientFirstMessage(gs2Header, user.getValue(), nonce.getValue()); } @Override StringBuilder writeTo(StringBuilder sb) { StringWritableCsv.writeTo( sb, gs2Header, null // This marks the position of the rest of the elements, required for the "," ); return clientFirstMessageBare(sb); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/CryptoUtil.java000066400000000000000000000120151506234003700271420ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkArgument; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static com.ongres.scram.common.util.Preconditions.gt0; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Locale; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import com.ongres.scram.common.exception.ScramRuntimeException; import org.jetbrains.annotations.NotNull; /** * Utility static methods for cryptography related tasks. */ final class CryptoUtil { private CryptoUtil() { throw new IllegalStateException("Utility class"); } /** * Compute the "Hi" function for SCRAM. * * {@code * Hi(str, salt, i): * * U1 := HMAC(str, salt + INT(1)) * U2 := HMAC(str, U1) * ... * Ui-1 := HMAC(str, Ui-2) * Ui := HMAC(str, Ui-1) * * Hi := U1 XOR U2 XOR ... XOR Ui * * where "i" is the iteration count, "+" is the string concatenation * operator, and INT(g) is a 4-octet encoding of the integer g, most * significant octet first. * * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the * pseudorandom function (PRF) and with dkLen == output length of * HMAC() == output length of H(). * } * * @param secretKeyFactory The SecretKeyFactory to generate the SecretKey * @param keyLength The length of the key (in bits) * @param password The char array to compute the Hi function * @param salt The salt * @param iterationCount The number of iterations * @return The bytes of the computed Hi value * @throws ScramRuntimeException if unsupported PBEKeySpec */ static byte[] hi(SecretKeyFactory secretKeyFactory, int keyLength, char[] password, byte[] salt, int iterationCount) { try { PBEKeySpec spec = new PBEKeySpec(password, salt, iterationCount, keyLength); SecretKey key = secretKeyFactory.generateSecret(spec); spec.clearPassword(); return key.getEncoded(); } catch (InvalidKeySpecException ex) { throw new ScramRuntimeException( String.format(Locale.ROOT, "Platform error: unsupported PBEKeySpec for %s algorithm", secretKeyFactory.getAlgorithm()), ex); } } /** * Computes the HMAC of a given message. * * {@code * HMAC(key, str): Apply the HMAC keyed hash algorithm (defined in * [RFC2104]) using the octet string represented by "key" as the key * and the octet string "str" as the input string. The size of the * result is the hash result size for the hash function in use. For * example, it is 20 octets for SHA-1 (see [RFC3174]). * } * * @param secretKeySpec A key of the given algorithm * @param mac A MAC instance of the given algorithm * @param message The message to compute the HMAC * @return The bytes of the computed HMAC value * @throws ScramRuntimeException unsupported key for HMAC algorithm */ static byte[] hmac(SecretKeySpec secretKeySpec, Mac mac, byte[] message) { try { mac.init(secretKeySpec); } catch (InvalidKeyException ex) { throw new ScramRuntimeException( String.format(Locale.ROOT, "Platform error: unsupported key for %s algorithm", mac.getAlgorithm()), ex); } return mac.doFinal(message); } /** * Computes a byte-by-byte xor operation. * {@code * XOR: Apply the exclusive-or operation to combine the octet string * on the left of this operator with the octet string on the right of * this operator. The length of the output and each of the two * inputs will be the same for this use. * } * * @param value1 first value to apply xor * @param value2 second value to apply xor * @return xor operation */ static byte @NotNull [] xor(byte @NotNull [] value1, byte @NotNull [] value2) { checkNotNull(value1, "value1"); checkNotNull(value2, "value2"); checkArgument(value1.length == value2.length, "Both values must have the same length"); byte[] result = new byte[value1.length]; for (int i = 0; i < value1.length; i++) { result[i] = (byte) (value1[i] ^ value2[i]); } return result; } /** * Generates a random salt. Normally the output is encoded to Base64. * * @param saltSize The length of the salt, in bytes * @param random The SecureRandom to use * @return The bye[] representing the salt * @throws IllegalArgumentException if the saltSize is not positive, or if random is null */ static byte @NotNull [] salt(int saltSize, @NotNull SecureRandom random) { gt0(saltSize, "saltSize"); checkNotNull(random, "random"); byte[] randomSalt = new byte[saltSize]; random.nextBytes(randomSalt); return randomSalt; } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/Gs2AttributeValue.java000066400000000000000000000025031506234003700303410ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Parse and write GS2 Attribute-Value pairs. */ final class Gs2AttributeValue extends AbstractCharAttributeValue { Gs2AttributeValue(@NotNull Gs2Attributes attribute, @Nullable String value) { super(attribute, value); if (attribute.isRequiredValue()) { checkNotNull(value, "value"); } } /** * Parses a potential Gs2AttributeValue String. * * @param value The string that contains the Attribute-Value pair (where value is optional). * @return The parsed class, or null if the String was null. * @throws IllegalArgumentException If the String is an invalid Gs2AttributeValue */ @NotNull static Gs2AttributeValue parse(@NotNull String value) { if (value.isEmpty() || value.length() == 2 || value.length() > 2 && value.charAt(1) != '=') { throw new IllegalArgumentException("Invalid Gs2AttributeValue"); } Gs2Attributes byChar = Gs2Attributes.byChar(value.charAt(0)); String val = value.length() > 2 ? value.substring(2) : null; return new Gs2AttributeValue(byChar, val); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/Gs2Attributes.java000066400000000000000000000034531506234003700275340ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import org.jetbrains.annotations.NotNull; /** * Possible values of a GS2 Attribute. * * @see [RFC5802] Formal Syntax */ enum Gs2Attributes implements CharSupplier { /** * Channel binding attribute. Client doesn't support channel binding. */ CLIENT_NOT(Gs2CbindFlag.CLIENT_NOT.getChar(), false), /** * Channel binding attribute. Client does support channel binding but thinks the server does not. */ CLIENT_YES_SERVER_NOT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT.getChar(), false), /** * Channel binding attribute. Client requires channel binding. The selected channel binding * follows "p=". */ CHANNEL_BINDING_REQUIRED(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED.getChar(), true), /** * SCRAM attribute. This attribute specifies an authorization identity. */ AUTHZID(ScramAttributes.AUTHZID.getChar(), true); private final char flag; private final boolean requiredValue; Gs2Attributes(char flag, boolean requiredValue) { this.flag = flag; this.requiredValue = requiredValue; } @Override public char getChar() { return flag; } boolean isRequiredValue() { return requiredValue; } @NotNull static Gs2Attributes byChar(char c) { switch (c) { case 'n': return CLIENT_NOT; case 'y': return CLIENT_YES_SERVER_NOT; case 'p': return CHANNEL_BINDING_REQUIRED; case 'a': return AUTHZID; default: throw new IllegalArgumentException("Invalid GS2Attribute character '" + c + "'"); } } @NotNull static Gs2Attributes byGs2CbindFlag(Gs2CbindFlag cbindFlag) { return byChar(cbindFlag.getChar()); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/Gs2CbindFlag.java000066400000000000000000000024011506234003700272070ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import org.jetbrains.annotations.NotNull; /** * Possible values of a GS2 Cbind Flag (channel binding; part of GS2 header). These values are sent * by the client, and so are interpreted from this perspective. * * @see [RFC5802] Formal Syntax */ public enum Gs2CbindFlag implements CharSupplier { /** * Client doesn't support channel binding. */ CLIENT_NOT('n'), /** * Client does support channel binding but thinks the server does not. */ CLIENT_YES_SERVER_NOT('y'), /** * Client requires channel binding. The selected channel binding follows "p=". */ CHANNEL_BINDING_REQUIRED('p'); private final char flag; Gs2CbindFlag(char flag) { this.flag = flag; } @Override public char getChar() { return flag; } @NotNull static Gs2CbindFlag byChar(char c) { switch (c) { case 'n': return CLIENT_NOT; case 'y': return CLIENT_YES_SERVER_NOT; case 'p': return CHANNEL_BINDING_REQUIRED; default: throw new IllegalArgumentException("Invalid Gs2CbindFlag character '" + c + "'"); } } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/Gs2Header.java000066400000000000000000000150471506234003700266000ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.castNonNull; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.util.Preconditions; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * GS2 header for SCRAM. * * * * * * * * * * * * * * *
Formal Syntax:
gs2-cbind-flag("p=" cb-name) / "n" / "y"
* ;; "n" -> client doesn't support channel binding.
* ;; "y" -> client does support channel binding
* ;; but thinks the server does not.
* ;; "p" -> client requires channel binding.
* ;; The selected channel binding follows "p=".
gs2-headergs2-cbind-flag "," [ authzid ] ","
* ;; GS2 header for SCRAM
* ;; (the actual GS2 header includes an optional
* ;; flag to indicate that the GSS mechanism is not
* ;; "standard", but since SCRAM is "standard", we
* ;; don't include that flag).
authzid"a=" saslname
* * @see [RFC5802] Formal Syntax */ public final class Gs2Header extends StringWritable { private final @NotNull Gs2AttributeValue gs2CbindFlag; private final @Nullable Gs2AttributeValue authzid; /** * Construct and validates a Gs2Header. Only provide the channel binding name if the channel * binding flag is set to required. * * @param cbindFlag The channel binding flag * @param cbName The channel-binding name. Should be not null if channel binding is required * @param authzid The optional SASL authorization identity * @throws IllegalArgumentException If the channel binding flag and argument are invalid */ public Gs2Header(@NotNull Gs2CbindFlag cbindFlag, @Nullable String cbName, @Nullable String authzid) { checkChannelBinding(cbindFlag, cbName); this.gs2CbindFlag = new Gs2AttributeValue(Gs2Attributes.byGs2CbindFlag(cbindFlag), cbName); this.authzid = authzid == null ? null : new Gs2AttributeValue(Gs2Attributes.AUTHZID, ScramStringFormatting.toSaslName(authzid)); } /** * Construct and validates a Gs2Header with no authzid. Only provide the channel binding name if * the channel binding flag is set to required. * * @param cbindFlag The channel binding flag * @param cbName The channel-binding name. Should be not null iif channel binding is required * @throws IllegalArgumentException If the channel binding flag and argument are invalid */ public Gs2Header(@NotNull Gs2CbindFlag cbindFlag, @Nullable String cbName) { this(cbindFlag, cbName, null); } /** * Construct and validates a Gs2Header with no authzid nor channel binding. * * @param cbindFlag The channel binding flag * @throws IllegalArgumentException If the channel binding is supported (no cbname can be provided * here) */ public Gs2Header(@NotNull Gs2CbindFlag cbindFlag) { this(cbindFlag, null, null); } /** * Return the channel binding flag. * * @return the {@code gs2-cbind-flag} */ public @NotNull Gs2CbindFlag getChannelBindingFlag() { return Gs2CbindFlag.byChar(gs2CbindFlag.getChar()); } /** * Return the channel binding type. * * @return the {@code cb-name} */ public @Nullable String getChannelBindingName() { return gs2CbindFlag.getValue(); } /** * Return the authzid. * * @return the {@code "a=" saslname} */ public @Nullable String getAuthzid() { return authzid != null ? castNonNull(authzid).getValue() : null; } @Override StringBuilder writeTo(StringBuilder sb) { return StringWritableCsv.writeTo(sb, gs2CbindFlag, authzid); } /** * Read a Gs2Header from a String. String may contain trailing fields that will be ignored. * * @param message The String containing the Gs2Header * @return The parsed Gs2Header object * @throws IllegalArgumentException If the format/values of the String do not conform to a * Gs2Header */ public static @NotNull Gs2Header parseFrom(@NotNull String message) { checkNotNull(message, "Null message"); @NotNull String[] gs2HeaderSplit = StringWritableCsv.parseFrom(message, 2); if (gs2HeaderSplit.length == 0) { throw new IllegalArgumentException("Invalid number of fields for the GS2 Header"); } Gs2AttributeValue gs2cbind = Gs2AttributeValue.parse(castNonNull(gs2HeaderSplit[0])); String authzId = Preconditions.isNullOrEmpty(gs2HeaderSplit[1]) ? null : castNonNull(Gs2AttributeValue.parse(gs2HeaderSplit[1])).getValue(); return new Gs2Header(Gs2CbindFlag.byChar(gs2cbind.getChar()), gs2cbind.getValue(), authzId); } private static void checkChannelBinding(@NotNull Gs2CbindFlag cbindFlag, @Nullable String cbName) { checkNotNull(cbindFlag, "cbindFlag"); if (cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED ^ cbName != null) { throw new IllegalArgumentException( "Specify required channel binding flag and type together, or none"); } if (cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED) { validateChannelBindingType(castNonNull(cbName)); } } /** * Checks that the channel binding name is valid. * *

{@code
   * cb-name = 1*(ALPHA / DIGIT / "." / "-")
   *           ;; See RFC 5056, Section 7.
   * }
* * @see IANA * Channel-Binding Types * @param cbname Channel Binding Name * @throws IllegalArgumentException If the name is not a valid channel binding type. */ private static void validateChannelBindingType(@NotNull String cbname) { checkNotEmpty(cbname, "cbname"); switch (cbname) { // IANA Registered Types case "tls-server-end-point": case "tls-unique": case "tls-exporter": break; default: // https://datatracker.ietf.org/doc/html/rfc5056#section-7 for (int i = 0; i < cbname.length(); i++) { char ch = cbname.charAt(i); if (!(ch >= 'A' && ch <= 'Z') && !(ch >= 'a' && ch <= 'z') && !(ch >= '0' && ch <= '9') && !(ch >= '-' && ch <= '.')) { throw new IllegalArgumentException( "Invalid Channel Binding Type name '" + cbname + "'"); } } break; } } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java000066400000000000000000000025411506234003700307550ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.castNonNull; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.exception.ScramParseException; import org.jetbrains.annotations.NotNull; /** * Parse and write SCRAM Attribute-Value pairs. */ class ScramAttributeValue extends AbstractCharAttributeValue { public ScramAttributeValue(@NotNull ScramAttributes attribute, @NotNull String value) { super(attribute, checkNotNull(value, "value")); } @Override public final @NotNull String getValue() { return castNonNull(super.getValue()); } /** * Parses a potential ScramAttributeValue String. * * @param value The string that contains the Attribute-Value pair. * @return The parsed class * @throws ScramParseException If the argument is empty or an invalid Attribute-Value */ public static @NotNull ScramAttributeValue parse(@NotNull String value) throws ScramParseException { if (value == null || value.length() < 3 || value.charAt(1) != '=') { throw new ScramParseException("Invalid ScramAttributeValue '" + value + "'"); } return new ScramAttributeValue(ScramAttributes.byChar(value.charAt(0)), value.substring(2)); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ScramAttributes.java000066400000000000000000000116131506234003700301430ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.exception.ScramParseException; /** * SCRAM Attributes as defined in Section * 5.1 of the RFC. * *

Not all the available attributes may be available in this implementation. */ enum ScramAttributes implements CharSupplier { /** * This attribute specifies the name of the user whose password is used for authentication (a.k.a. * "authentication identity" [RFC4422]). If the * "a" attribute is not specified (which would normally be the case), this username is also the * identity that will be associated with the connection subsequent to authentication and * authorization. * *

The client SHOULD prepare the username using the "SASLprep" profile [RFC4013] of the "stringprep" algorithm [RFC3454] treating it as a query string (i.e., * unassigned Unicode code points are allowed). * *

The characters ',' or '=' in usernames are sent as '=2C' and '=3D' respectively. */ USERNAME('n'), /** * This is an optional attribute, and is part of the GS2 [RFC5801] bridge between the GSS-API and SASL. * This attribute specifies an authorization identity. A client may include it in its first * message to the server if it wants to authenticate as one user, but subsequently act as a * different user. This is typically used by an administrator to perform some management task on * behalf of another user, or by a proxy in some situations. * *

If this attribute is omitted (as it normally would be), the authorization identity is * assumed to be derived from the username specified with the (required) "n" attribute. * *

The server always authenticates the user specified by the "n" attribute. If the "a" * attribute specifies a different user, the server associates that identity with the connection * after successful authentication and authorization checks. * *

The syntax of this field is the same as that of the "n" field with respect to quoting of '=' * and ','. */ AUTHZID('a'), /** * This attribute specifies a sequence of random printable ASCII characters excluding ',' (which * forms the nonce used as input to the hash function). No quoting is applied to this string. */ NONCE('r'), /** * This REQUIRED attribute specifies the base64-encoded GS2 header and channel binding data. The * attribute data consist of:

  • the GS2 header from the client's first message (recall * that the GS2 header contains a channel binding flag and an optional authzid). This header is * going to include channel binding type prefix (see [RFC5056]), if and only if the client is using * channel binding;
  • followed by the external channel's channel binding data, if and * only if the client is using channel binding.
*/ CHANNEL_BINDING('c'), /** * This attribute specifies the base64-encoded salt used by the server for this user. */ SALT('s'), /** * This attribute specifies an iteration count for the selected hash function and user. */ ITERATION('i'), /** * This attribute specifies a base64-encoded ClientProof. */ CLIENT_PROOF('p'), /** * This attribute specifies a base64-encoded ServerSignature. */ SERVER_SIGNATURE('v'), /** * This attribute specifies an error that occurred during authentication exchange. Can help * diagnose the reason for the authentication exchange failure. */ ERROR('e'); private final char attributeChar; ScramAttributes(char attributeChar) { this.attributeChar = checkNotNull(attributeChar, "attributeChar"); } @Override public char getChar() { return attributeChar; } /** * Find a SCRAMAttribute by its character. * * @param c The character. * @return The SCRAMAttribute that has that character. * @throws ScramParseException If no SCRAMAttribute has this character. */ public static ScramAttributes byChar(char c) throws ScramParseException { switch (c) { case 'n': return USERNAME; case 'a': return AUTHZID; case 'r': return NONCE; case 'c': return CHANNEL_BINDING; case 's': return SALT; case 'i': return ITERATION; case 'p': return CLIENT_PROOF; case 'v': return SERVER_SIGNATURE; case 'e': return ERROR; default: throw new ScramParseException("Attribute with char '" + c + "' does not exist"); } } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ScramFunctions.java000066400000000000000000000220041506234003700277610ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static java.nio.charset.StandardCharsets.UTF_8; import java.security.MessageDigest; import java.security.SecureRandom; import com.ongres.scram.common.util.Preconditions; import org.jetbrains.annotations.NotNull; /** * Utility functions (mostly crypto) for SCRAM. */ public final class ScramFunctions { private static final byte @NotNull [] CLIENT_KEY_HMAC_MESSAGE = "Client Key".getBytes(UTF_8); private static final byte @NotNull [] SERVER_KEY_HMAC_MESSAGE = "Server Key".getBytes(UTF_8); private ScramFunctions() { throw new IllegalStateException("Utility class"); } /** * Compute the salted password, based on the given SCRAM mechanism, the String preparation * algorithm, the provided salt and the number of iterations. * *
{@code
   *      SaltedPassword  := Hi(Normalize(password), salt, i)
   *  }
* * @param scramMechanism The SCRAM mechanism * @param stringPreparation The String preparation * @param password The non-salted password * @param salt The bytes representing the salt * @param iterationCount The number of iterations * @return The salted password */ public static byte @NotNull [] saltedPassword(@NotNull ScramMechanism scramMechanism, @NotNull StringPreparation stringPreparation, char @NotNull [] password, byte @NotNull [] salt, int iterationCount) { return scramMechanism.saltedPassword(stringPreparation, password, salt, iterationCount); } /** * Computes the HMAC of the message and key, using the given SCRAM mechanism. * *
{@code
   *     HMAC(key, str)
   * }
* * @param scramMechanism The SCRAM mechanism * @param message The message to compute the HMAC * @param key The key used to initialize the MAC * @return The computed HMAC */ public static byte @NotNull [] hmac(@NotNull ScramMechanism scramMechanism, byte @NotNull [] key, byte @NotNull [] message) { return scramMechanism.hmac(key, message); } /** * Generates a client key, from the salted password. * *
{@code
   *      ClientKey := HMAC(SaltedPassword, "Client Key")
   *  }
* * @param scramMechanism The SCRAM mechanism * @param saltedPassword The salted password * @return The client key */ public static byte[] clientKey(@NotNull ScramMechanism scramMechanism, byte @NotNull [] saltedPassword) { return hmac(scramMechanism, saltedPassword, CLIENT_KEY_HMAC_MESSAGE); } /** * Generates a server key, from the salted password. * *
{@code
   *      ServerKey := HMAC(SaltedPassword, "Server Key")
   * }
* * @param scramMechanism The SCRAM mechanism * @param saltedPassword The salted password * @return The server key */ public static byte[] serverKey(@NotNull ScramMechanism scramMechanism, byte @NotNull [] saltedPassword) { return hmac(scramMechanism, saltedPassword, SERVER_KEY_HMAC_MESSAGE); } /** * Computes the hash function of a given value, based on the SCRAM mechanism hash function. * *
{@code
   *     H(str)
   * }
* * @param scramMechanism The SCRAM mechanism * @param message The message to hash * @return The hashed value */ public static byte[] hash(@NotNull ScramMechanism scramMechanism, byte @NotNull [] message) { return scramMechanism.digest(message); } /** * Generates a stored key, from the salted password. * *
{@code
   *      StoredKey := H(ClientKey)
   * }
* * @param scramMechanism The SCRAM mechanism * @param clientKey The client key * @return The stored key */ public static byte[] storedKey(@NotNull ScramMechanism scramMechanism, byte @NotNull [] clientKey) { return hash(scramMechanism, clientKey); } /** * Computes the SCRAM client signature. * *
{@code
   *      ClientSignature := HMAC(StoredKey, AuthMessage)
   * }
* * @param scramMechanism The SCRAM mechanism * @param storedKey The stored key * @param authMessage The auth message * @return The client signature */ public static byte @NotNull [] clientSignature(@NotNull ScramMechanism scramMechanism, byte @NotNull [] storedKey, @NotNull String authMessage) { return hmac(scramMechanism, storedKey, authMessage.getBytes(UTF_8)); } /** * Computes the SCRAM client proof to be sent to the server on the client-final-message. * *
{@code
   *      ClientProof := ClientKey XOR ClientSignature
   * }
* * @param clientKey The client key * @param clientSignature The client signature * @return The client proof */ public static byte[] clientProof(byte @NotNull [] clientKey, byte @NotNull [] clientSignature) { return CryptoUtil.xor(clientKey, clientSignature); } /** * Compute the SCRAM server signature. * *
{@code
   *      ServerSignature := HMAC(ServerKey, AuthMessage)
   * }
* * @param scramMechanism The SCRAM mechanism * @param serverKey The server key * @param authMessage The auth message * @return The server signature */ public static byte @NotNull [] serverSignature(@NotNull ScramMechanism scramMechanism, byte @NotNull [] serverKey, @NotNull String authMessage) { return hmac(scramMechanism, serverKey, authMessage.getBytes(UTF_8)); } /** * Verifies that a provided client proof is correct. * * @param scramMechanism The SCRAM mechanism * @param clientProof The provided client proof * @param storedKey The stored key * @param authMessage The auth message * @return True if the client proof is correct */ public static boolean verifyClientProof( @NotNull ScramMechanism scramMechanism, byte @NotNull [] clientProof, byte @NotNull [] storedKey, @NotNull String authMessage) { byte[] clientSignature = clientSignature(scramMechanism, storedKey, authMessage); byte[] clientKey = CryptoUtil.xor(clientSignature, clientProof); byte[] computedStoredKey = hash(scramMechanism, clientKey); return MessageDigest.isEqual(storedKey, computedStoredKey); } /** * Verifies that a provided server proof is correct. * * @param scramMechanism The SCRAM mechanism * @param serverKey The server key * @param authMessage The auth message * @param serverSignature The provided server signature * @return True if the server signature is correct */ public static boolean verifyServerSignature( ScramMechanism scramMechanism, byte[] serverKey, String authMessage, byte[] serverSignature) { byte[] computedServerSignature = serverSignature(scramMechanism, serverKey, authMessage); return MessageDigest.isEqual(serverSignature, computedServerSignature); } /** * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except * comma (','). * * @param nonceSize The length of the nonce, in characters/bytes * @param random The SecureRandom to use * @return The String representing the nonce * @throws IllegalArgumentException if the nonceSize is not positive, or if random is null */ public static String nonce(int nonceSize, SecureRandom random) { Preconditions.gt0(nonceSize, "nonceSize"); Preconditions.checkNotNull(random, "random"); final StringBuilder nonceBuilder = new StringBuilder(nonceSize); while (nonceBuilder.length() < nonceSize) { int codePoint = random.nextInt(0x7E - 0x21 + 1) + 0x21; if (codePoint != ',') { nonceBuilder.append((char) codePoint); } } return nonceBuilder.toString(); } /** * Generates a random salt that can be used to generate a salted password. * * @param saltSize The length of the salt, in bytes * @param random The SecureRandom to use * @return The bye[] representing the salt * @throws IllegalArgumentException if the saltSize is not positive, or if random is null */ public static byte @NotNull [] salt(int saltSize, @NotNull SecureRandom random) { return CryptoUtil.salt(saltSize, random); } /** * The AuthMessage is computed by concatenating messages from the authentication exchange. * *
{@code
   *      AuthMessage := client-first-message-bare + "," +
   *                                    server-first-message + "," +
   *                                    client-final-message-without-proof
   * }
* * @param clientFirstMessage the {@link ClientFirstMessage ClientFirstMessage} * @param serverFirstMessage the {@link ServerFirstMessage ServerFirstMessage} * @param cbindData the channel binding data, or null * @return the AuthMessage */ public static String authMessage(ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage, byte[] cbindData) { StringBuilder sb = clientFirstMessage.clientFirstMessageBare(new StringBuilder(96)) .append(',').append(serverFirstMessage).append(','); ClientFinalMessage.withoutProof(sb, clientFirstMessage.getGs2Header(), cbindData, serverFirstMessage.getNonce()); return sb.toString(); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ScramMechanism.java000066400000000000000000000226161506234003700277260ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static com.ongres.scram.common.util.Preconditions.gt0; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.SecretKeySpec; import com.ongres.scram.common.exception.ScramRuntimeException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; /** * SCRAM Mechanisms supported by this library. At least, {@code SCRAM-SHA-1} and * {@code SCRAM-SHA-256} are provided, since both the hash and the HMAC implementations are provided * by the Java JDK version 8 or greater. * *

{@link java.security.MessageDigest}: "Every implementation of the Java platform is required to * support the following standard MessageDigest algorithms: {@code SHA-1}, {@code SHA-256}". * *

{@link javax.crypto.Mac}: "Every implementation of the Java platform is required to support * the following standard Mac algorithms: {@code HmacSHA1}, {@code HmacSHA256}". * * @see SASL * SCRAM Family Mechanisms */ public enum ScramMechanism { /** * SCRAM-SHA-1 mechanism, defined in RFC-5802. */ SCRAM_SHA_1("SCRAM-SHA-1", "SHA-1", 160, "HmacSHA1", 4096), /** * SCRAM-SHA-1-PLUS mechanism, defined in RFC-5802. */ SCRAM_SHA_1_PLUS("SCRAM-SHA-1-PLUS", "SHA-1", 160, "HmacSHA1", 4096), /** * SCRAM-SHA-224 mechanism, not defined in an RFC. */ SCRAM_SHA_224("SCRAM-SHA-224", "SHA-224", 224, "HmacSHA224", 4096), /** * SCRAM-SHA-224-PLUS mechanism, not defined in an RFC. */ SCRAM_SHA_224_PLUS("SCRAM-SHA-224-PLUS", "SHA-224", 224, "HmacSHA224", 4096), /** * SCRAM-SHA-256 mechanism, defined in RFC-7677. */ SCRAM_SHA_256("SCRAM-SHA-256", "SHA-256", 256, "HmacSHA256", 4096), /** * SCRAM-SHA-256-PLUS mechanism, defined in RFC-7677. */ SCRAM_SHA_256_PLUS("SCRAM-SHA-256-PLUS", "SHA-256", 256, "HmacSHA256", 4096), /** * SCRAM-SHA-384 mechanism, not defined in an RFC. */ SCRAM_SHA_384("SCRAM-SHA-384", "SHA-384", 384, "HmacSHA384", 4096), /** * SCRAM-SHA-384-PLUS mechanism, not defined in an RFC. */ SCRAM_SHA_384_PLUS("SCRAM-SHA-384-PLUS", "SHA-384", 384, "HmacSHA384", 4096), /** * SCRAM-SHA-512 mechanism. */ SCRAM_SHA_512("SCRAM-SHA-512", "SHA-512", 512, "HmacSHA512", 10000), /** * SCRAM-SHA-512-PLUS mechanism. */ SCRAM_SHA_512_PLUS("SCRAM-SHA-512-PLUS", "SHA-512", 512, "HmacSHA512", 10000); private static final @Unmodifiable Map BY_NAME_MAPPING = Arrays.stream(values()) .filter(ScramMechanism::isAlgorithmSupported) .collect(Collectors.collectingAndThen( Collectors.toMap(ScramMechanism::getName, Function.identity()), Collections::unmodifiableMap)); private static final @Unmodifiable List SUPPORTED_MECHANISMS = BY_NAME_MAPPING.keySet() .stream() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); private final @NotNull String mechanismName; private final @NotNull String hashAlgorithmName; private final int keyLength; private final @NotNull String hmacAlgorithmName; private final @NotNull String keyFactoryAlgorithmName; private final boolean channelBinding; private final int iterationCount; ScramMechanism(String name, String hashAlgorithmName, int keyLength, String hmacAlgorithmName, int iterationCount) { this.mechanismName = checkNotNull(name, "name"); this.hashAlgorithmName = checkNotNull(hashAlgorithmName, "hashAlgorithmName"); this.keyLength = gt0(keyLength, "keyLength"); this.hmacAlgorithmName = checkNotNull(hmacAlgorithmName, "hmacAlgorithmName"); this.keyFactoryAlgorithmName = "PBKDF2With" + hmacAlgorithmName; this.channelBinding = name.endsWith("-PLUS"); this.iterationCount = gt0(iterationCount, "iterationCount"); } /** * Method that returns the name of the hash algorithm. It is protected since should be of no * interest for direct users. The instance is supposed to provide abstractions over the algorithm * names, and are not meant to be directly exposed. * * @return The name of the hash algorithm */ @NotNull String getHashAlgorithmName() { return hashAlgorithmName; } /** * Method that returns the name of the HMAC algorithm. It is protected since should be of no * interest for direct users. The instance is supposed to provide abstractions over the algorithm * names, and are not meant to be directly exposed. * * @return The name of the HMAC algorithm */ @NotNull String getHmacAlgorithmName() { return hmacAlgorithmName; } /** * The name of the mechanism. * *

Must be a value registered under IANA: SASL SCRAM * Family Mechanisms * * @return The mechanism name */ @NotNull public String getName() { return mechanismName; } /** * The mechanism {@code -PLUS} require channel binding. * * @return true if the mechanism requires channel binding */ public boolean isPlus() { return channelBinding; } /** * Returns the length of the key length of the algorithm. * * @return The length (in bits) */ int getKeyLength() { return keyLength; } int getIterationCount() { return iterationCount; } /** * Calculate a message digest, according to the algorithm of the SCRAM mechanism. * * @param message the message * @return The calculated message digest * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included * implementations */ byte @NotNull [] digest(byte @NotNull [] message) { try { return MessageDigest.getInstance(hashAlgorithmName).digest(message); } catch (NoSuchAlgorithmException e) { throw new ScramRuntimeException( "Hash algorithm " + hashAlgorithmName + " not present in current JVM", e); } } /** * Calculate the hmac of a key and a message, according to the algorithm of the SCRAM mechanism. * * @param key the key * @param message the message * @return The calculated message hmac instance * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included * implementations */ byte @NotNull [] hmac(byte @NotNull [] key, byte @NotNull [] message) { try { return CryptoUtil.hmac(new SecretKeySpec(key, hmacAlgorithmName), Mac.getInstance(hmacAlgorithmName), message); } catch (NoSuchAlgorithmException e) { throw new ScramRuntimeException( "HMAC algorithm " + hmacAlgorithmName + " not present in current JVM", e); } } /** * Compute the salted password. * * @param stringPreparation Type of preparation to perform in the string * @param password Password used * @param salt Salt used * @param iterationCount Number of iterations * @return The salted password * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included * implementations */ byte @NotNull [] saltedPassword(@NotNull StringPreparation stringPreparation, char @NotNull [] password, byte @NotNull [] salt, int iterationCount) { final char[] normalizedPassword = stringPreparation.normalize(password); try { return CryptoUtil.hi( SecretKeyFactory.getInstance(keyFactoryAlgorithmName), keyLength, normalizedPassword, salt, iterationCount); } catch (NoSuchAlgorithmException ex) { throw new ScramRuntimeException( "Unsupported " + keyFactoryAlgorithmName + " for " + mechanismName, ex); } } /** * Gets a SCRAM mechanism given its standard IANA name, supported by the Java security provider. * * @apiNote This will get only the mechanims supported by the Java security provider, if the * configured security provider lacks the algorithm this method will return {@code null}. * * @param name The standard IANA full name of the mechanism. * @return An instance that contains the ScramMechanism if it was found, or null otherwise. */ public static @Nullable ScramMechanism byName(@NotNull String name) { return BY_NAME_MAPPING.get(checkNotNull(name, "name")); } /** * List all the supported SCRAM mechanisms by this client implementation. * * @return A unmodifiable list of the IANA-registered, SCRAM supported mechanisms */ @Unmodifiable public static @NotNull List<@NotNull String> supportedMechanisms() { return Collections.unmodifiableList(SUPPORTED_MECHANISMS); } private static boolean isAlgorithmSupported(@NotNull ScramMechanism mechanism) { try { MessageDigest.getInstance(mechanism.hashAlgorithmName); Mac.getInstance(mechanism.hmacAlgorithmName); SecretKeyFactory.getInstance(mechanism.keyFactoryAlgorithmName); return true; } catch (NoSuchAlgorithmException e) { return false; } } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java000066400000000000000000000077771506234003700313360ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import java.util.Base64; import com.ongres.saslprep.SASLprep; import com.ongres.stringprep.Profile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Class with static methods that provide support for converting to/from salNames. * * @see [RFC5802] Section 7: Formal * Syntax */ final class ScramStringFormatting { static final Profile SASL_PREP = new SASLprep(); private ScramStringFormatting() { throw new IllegalStateException("Utility class"); } /** * Given a value-safe-char (normalized UTF-8 String), return one where characters ',' and '=' are * represented by '=2C' or '=3D', respectively. * * @param value The value to convert so saslName * @return The saslName, with caracter escaped (if any) */ @NotNull static String toSaslName(@NotNull final String value) { if (value.isEmpty()) { return value; } final char[] originalChars = SASL_PREP.prepareQuery(value.toCharArray()); int comma = 0; int equal = 0; // Fast path for (char c : originalChars) { if (',' == c) { comma++; } else if ('=' == c) { equal++; } } if (comma == 0 && equal == 0) { return new String(originalChars); } // Replace chars char[] saslChars = new char[originalChars.length + comma * 2 + equal * 2]; int i = 0; for (char c : originalChars) { if (',' == c) { saslChars[i++] = '='; saslChars[i++] = '2'; saslChars[i++] = 'C'; } else if ('=' == c) { saslChars[i++] = '='; saslChars[i++] = '3'; saslChars[i++] = 'D'; } else { saslChars[i++] = c; } } return new String(saslChars); } /** * Given a saslName, return a non-escaped String. * * @param value The saslName * @return The saslName, unescaped * @throws IllegalArgumentException If a ',' character is present, or a '=' not followed by either * '2C' or '3D' */ @Nullable static String fromSaslName(@Nullable String value) { if (null == value || value.isEmpty()) { return value; } int equal = 0; char[] orig = value.toCharArray(); // Fast path for (int i = 0; i < orig.length; i++) { if (orig[i] == ',') { throw new IllegalArgumentException("Invalid ',' character present in saslName"); } if (orig[i] == '=') { equal++; if (i + 2 > orig.length - 1) { throw new IllegalArgumentException("Invalid '=' character present in saslName"); } if (!(orig[i + 1] == '2' && orig[i + 2] == 'C' || orig[i + 1] == '3' && orig[i + 2] == 'D')) { throw new IllegalArgumentException( "Invalid char '=" + orig[i + 1] + orig[i + 2] + "' found in saslName"); } } } if (equal == 0) { return value; } // Replace characters char[] replaced = new char[orig.length - equal * 2]; for (int r = 0, o = 0; r < replaced.length; r++) { if ('=' == orig[o]) { if (orig[o + 1] == '2' && orig[o + 2] == 'C') { replaced[r] = ','; } else if (orig[o + 1] == '3' && orig[o + 2] == 'D') { replaced[r] = '='; } o += 3; } else { replaced[r] = orig[o]; o += 1; } } return new String(replaced); } static @NotNull String base64Encode(byte @NotNull [] value) { checkNotNull(value, "value"); return new String(Base64.getEncoder().encode(value), UTF_8); } static byte @NotNull [] base64Decode(@NotNull String value) { checkNotEmpty(value, "value"); return Base64.getDecoder().decode(value.getBytes(UTF_8)); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ServerFinalMessage.java000066400000000000000000000126361506234003700305620ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.castNonNull; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ServerErrorValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Constructs and parses {@code server-final-messages}. * * * * * * * * * * * * * * * * * * * *
Formal Syntax:
server-error"e=" server-error-value
server-error-value"invalid-encoding" /
* "extensions-not-supported" / ; unrecognized 'm' value
* "invalid-proof" /
* "channel-bindings-dont-match" /
* "server-does-support-channel-binding" /
*     ; server does not support channel binding
* "channel-binding-not-supported" /
* "unsupported-channel-binding-type" /
* "unknown-user" /
* "invalid-username-encoding" /
*     ; invalid username encoding (invalid UTF-8 or
*     ; SASLprep failed)
* "no-resources" /
* "other-error"

* ; Unrecognized errors should be treated as "other-error".
* ; In order to prevent information disclosure, the server
* ; may substitute the real reason with "other-error".
verifier"v=" base64
* ;; base-64 encoded ServerSignature.
server-final-message(server-error / verifier)
* ["," extensions]
* * @implNote {@code extensions} are not supported. * @see [RFC5802] Section 7 */ public final class ServerFinalMessage extends AbstractScramMessage { private final byte @Nullable [] verifier; private final @Nullable String serverError; /** * Constructs a server-final-message with no errors, and the provided server verifier. * * @param verifier The bytes of the computed signature * @throws IllegalArgumentException If the verifier is null */ public ServerFinalMessage(byte @NotNull [] verifier) { this.verifier = checkNotNull(verifier, "verifier"); this.serverError = null; } /** * Constructs a server-final-message which represents a SCRAM error. * * @param serverError The error message * @throws IllegalArgumentException If the error is null */ public ServerFinalMessage(@NotNull String serverError) { this.serverError = validateServerErrorType(serverError); this.verifier = null; } /** * Whether this server-final-message contains an error. * * @return True if it contains an error, false if it contains a verifier */ public boolean isError() { return null != serverError; } /** * Get the verifier value from the "v=" server-final-message. * * @return the {@code verifier} */ public byte @Nullable [] getVerifier() { return verifier != null ? checkNotNull(verifier, "verifier").clone() : null; } /** * Get the server-error-value from the "e=" server-final-message. * * @return the {@code server-error-value} */ public @Nullable String getServerError() { return serverError; } /** * Parses a server-final-message from a String. * * @param serverFinalMessage The message * @return A constructed server-final-message instance * @throws ScramParseException If the argument is not a valid server-final-message * @throws IllegalArgumentException If the message is null or empty */ public static @NotNull ServerFinalMessage parseFrom(@NotNull String serverFinalMessage) throws ScramParseException { checkNotEmpty(serverFinalMessage, "serverFinalMessage"); @NotNull String @NotNull [] attributeValues = StringWritableCsv.parseFrom(serverFinalMessage, 1, 0); if (attributeValues.length != 1) { throw new ScramParseException("Invalid server-final-message"); } ScramAttributeValue attributeValue = ScramAttributeValue.parse(attributeValues[0]); if (ScramAttributes.SERVER_SIGNATURE.getChar() == attributeValue.getChar()) { byte[] verifier = ScramStringFormatting.base64Decode(attributeValue.getValue()); return new ServerFinalMessage(verifier); } else if (ScramAttributes.ERROR.getChar() == attributeValue.getChar()) { return new ServerFinalMessage(attributeValue.getValue()); } else { throw new ScramParseException( "Invalid server-final-message: it must contain either a verifier or an error attribute"); } } @Override StringBuilder writeTo(StringBuilder sb) { return StringWritableCsv.writeTo( sb, isError() ? new ScramAttributeValue(ScramAttributes.ERROR, castNonNull(serverError)) : new ScramAttributeValue(ScramAttributes.SERVER_SIGNATURE, ScramStringFormatting.base64Encode(castNonNull(verifier)))); } private static String validateServerErrorType(@NotNull String serverError) { checkNotNull(serverError, "serverError"); if (ServerErrorValue.getErrorMessage(serverError) == null) { throw new IllegalArgumentException( "Invalid server-error-value '" + serverError + "'"); } return serverError; } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/ServerFirstMessage.java000066400000000000000000000134321506234003700306130ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.castNonNull; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static com.ongres.scram.common.util.Preconditions.gt0; import com.ongres.scram.common.exception.ScramParseException; import org.jetbrains.annotations.NotNull; /** * Constructs and parses {@code server-first-messages}. * * * * * * * * * * * * * * * *
Formal Syntax:
nonce"r=" c-nonce [s-nonce]
* ;; Second part provided by server.
salt"s=" base64
server-first-message[reserved-mext ","] nonce "," salt ",
* "iteration-count ["," extensions]
* * @implNote {@code extensions} are not supported. * @see [RFC5802] Section 7 */ public final class ServerFirstMessage extends AbstractScramMessage { private final @NotNull String clientNonce; private final @NotNull String serverNonce; private final @NotNull String salt; private final int iterationCount; /** * Constructs a server-first-message from a client-first-message and the additional required data. * * * * * * * * * * * * * * * *
Formal Syntax:
server-error"e=" server-error-value
verifier"v=" base64
* ;; base-64 encoded ServerSignature.
server-final-message(server-error / verifier)
* ["," extensions]
* * @param clientNonce The c-nonce used in client-first-message * @param serverNonce The s-nonce returned by the server * @param salt The salt * @param iterationCount The iteration count (must be positive) * @throws IllegalArgumentException If clientFirstMessage, serverNonce or salt are null or empty, * or iteration < 1 */ public ServerFirstMessage(@NotNull String clientNonce, @NotNull String serverNonce, @NotNull String salt, int iterationCount) { this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); this.serverNonce = checkNotEmpty(serverNonce, "serverNonce"); this.salt = checkNotNull(salt, "salt"); this.iterationCount = gt0(iterationCount, "iterationCount"); } /** * The client nonce. * * @return The client nonce */ public @NotNull String getClientNonce() { return clientNonce; } /** * The server nonce. * * @return The server nonce */ public @NotNull String getServerNonce() { return serverNonce; } /** * The concatenation of the client nonce and the server nonce: {@code c-nonce [s-nonce]}. * * @return The nonce */ public @NotNull String getNonce() { return clientNonce + serverNonce; } /** * The salt in base64. * * @return The salt in base64. */ public String getSalt() { return salt; } /** * The number of iterations. * * @return The number of iterations. */ public int getIterationCount() { return iterationCount; } /** * Parses a server-first-message from a String. * * @param serverFirstMessage The string representing the server-first-message * @param clientNonce The clientNonce that is present in the client-first-message * @return The parsed instance * @throws ScramParseException If the argument is not a valid server-first-message * @throws IllegalArgumentException If either argument is empty or serverFirstMessage is not a * valid message */ public static @NotNull ServerFirstMessage parseFrom(@NotNull String serverFirstMessage, @NotNull String clientNonce) throws ScramParseException { checkNotEmpty(serverFirstMessage, "serverFirstMessage"); checkNotEmpty(clientNonce, "clientNonce"); String[] attributeValues = StringWritableCsv.parseFrom(serverFirstMessage, 3, 0); if (attributeValues.length != 3) { throw new ScramParseException("Invalid server-first-message"); } ScramAttributeValue nonce = ScramAttributeValue.parse(castNonNull(attributeValues[0])); if (ScramAttributes.NONCE.getChar() != nonce.getChar()) { throw new ScramParseException( "nonce must be the 1st element of the server-first-message"); } if (!nonce.getValue().startsWith(clientNonce)) { throw new ScramParseException("parsed nonce does not start with client nonce"); } ScramAttributeValue salt = ScramAttributeValue.parse(castNonNull(attributeValues[1])); if (ScramAttributes.SALT.getChar() != salt.getChar()) { throw new ScramParseException("salt must be the 2nd element of the server-first-message"); } ScramAttributeValue iteration = ScramAttributeValue.parse(castNonNull(attributeValues[2])); if (ScramAttributes.ITERATION.getChar() != iteration.getChar()) { throw new ScramParseException( "iteration must be the 3rd element of the server-first-message"); } int iterationInt; try { iterationInt = Integer.parseInt(iteration.getValue()); } catch (NumberFormatException ex) { throw new ScramParseException("invalid iteration", ex); } return new ServerFirstMessage(clientNonce, nonce.getValue().substring(clientNonce.length()), salt.getValue(), iterationInt); } @Override StringBuilder writeTo(StringBuilder sb) { return StringWritableCsv.writeTo( sb, new ScramAttributeValue(ScramAttributes.NONCE, getNonce()), new ScramAttributeValue(ScramAttributes.SALT, salt), new ScramAttributeValue(ScramAttributes.ITERATION, Integer.toString(iterationCount))); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/StringPreparation.java000066400000000000000000000060511506234003700305020ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; /** * StringPreparations enumerations to use in SCRAM. */ public enum StringPreparation { /** * Implementation of StringPreparation that performs no preparation. Non US-ASCII characters will * produce an exception. * *

Even though the [RFC5802] is not very * clear about it, this implementation will normalize non-printable US-ASCII characters similarly * to what SASLprep does (i.e., removing them). */ NO_PREPARATION { @Override char[] doNormalize(char[] value) { return UsAsciiUtils.toPrintable(value); } }, /** * Implementation of StringPreparation that performs {@code SASLprep} preparation. UTF-8 byte * sequences that are prohibited by the SASLprep algorithm will produce an exception. */ SASL_PREPARATION { @Override char[] doNormalize(char[] value) { return ScramStringFormatting.SASL_PREP.prepareStored(value); } }, /** * Implementation of StringPreparation that performs {@code SASLprep} preparation for PostgreSQL. * *

The SCRAM specification dictates that the password is also in UTF-8, and is * processed with the {@code SASLprep} algorithm. PostgreSQL, however, does not require UTF-8 to * be used for the password. When a user's password is set, it is processed with SASLprep as if it * was in UTF-8, regardless of the actual encoding used. However, if it is not a legal UTF-8 byte * sequence, or it contains UTF-8 byte sequences that are prohibited by the SASLprep algorithm, * the raw password will be used without SASLprep processing, instead of throwing an error. This * allows the password to be normalized when it is in UTF-8, but still allows a non-UTF-8 password * to be used, and doesn't require the system to know which encoding the password is * in.
* * @see PostgreSQL * SCRAM-SHA-256 Authentication */ POSTGRESQL_PREPARATION { @Override char[] doNormalize(char[] value) { try { return ScramStringFormatting.SASL_PREP.prepareStored(value); } catch (IllegalArgumentException ex) { // the raw password will be used without SASLprep processing return value; } } }; abstract char[] doNormalize(char[] value); /** * Normalize acording the selected preparation. * * @param value array of chars to normalize * @return the normalized array of chars * @throws IllegalArgumentException if the string is null or empty */ public char[] normalize(char[] value) { if (null == value || value.length == 0) { throw new IllegalArgumentException("Empty string for value"); } char[] normalized = doNormalize(value); if (null == normalized || normalized.length == 0) { throw new IllegalArgumentException("null or empty value after normalization"); } return normalized; } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/StringWritable.java000066400000000000000000000007631506234003700277730ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import org.jetbrains.annotations.NotNull; /** * Abstract class to denote classes which can write to a StringBuffer. */ abstract class StringWritable { /** * Write the class information to the given StringBuffer. * * @param sb Where to write the data. * @return The same StringBuffer. */ abstract @NotNull StringBuilder writeTo(@NotNull StringBuilder sb); } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/StringWritableCsv.java000066400000000000000000000075241506234003700304510ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import java.util.Arrays; import com.ongres.scram.common.util.Preconditions; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Helper class to generate Comma Separated Values of StringWritables. */ final class StringWritableCsv { private StringWritableCsv() { throw new IllegalStateException("Utility class"); } /** * Write a sequence of StringWritableCsv to a StringBuffer. Null StringWritables are not printed, * but separator is still used. Separator is a comma (',') * * @param sb The sb to write to * @param values Zero or more attribute-value pairs to write * @return The same sb, with data filled in (if any) * @throws IllegalArgumentException If sb is null */ static @NotNull StringBuilder writeTo(@NotNull StringBuilder sb, @Nullable StringWritable... values) { checkNotNull(sb, "sb"); if (null == values || values.length == 0) { return sb; } if (null != values[0]) { Preconditions.castNonNull(values[0]).writeTo(sb); } int i = 1; while (i < values.length) { sb.append(','); if (null != values[i]) { Preconditions.castNonNull(values[i]).writeTo(sb); } i++; } return sb; } /** * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No * validation is performed on the individual attribute-values returned. * * @param value The String with the set of attribute-values * @param n Number of entries to return (entries will be null of there were not enough). 0 means * unlimited * @param offset How many entries to skip before start returning * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null or either n or offset are negative */ static @NotNull String @NotNull [] parseFrom(@NotNull String value, int n, int offset) { checkNotNull(value, "value"); if (n < 0 || offset < 0) { throw new IllegalArgumentException("Limit and offset have to be >= 0"); } if (value.isEmpty()) { return new String[0]; } String[] split = value.split(",", -1); if (split.length < offset) { throw new IllegalArgumentException("Not enough items for the given offset"); } return Arrays.copyOfRange( split, offset, (n == 0 ? split.length : n) + offset); } /** * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No * validation is performed on the individual attribute-values returned. Elements are returned * starting from the first available attribute-value. * * @param value The String with the set of attribute-values * @param n Number of entries to return (entries will be null of there were not enough). 0 means * unlimited * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null or n is negative */ static @NotNull String @NotNull [] parseFrom(@NotNull String value, int n) { return parseFrom(value, n, 0); } /** * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No * validation is performed on the individual attribute-values returned. All the available * attribute-values will be returned. * * @param value The String with the set of attribute-values * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null */ static @NotNull String @NotNull [] parseFrom(@NotNull String value) { return parseFrom(value, 0, 0); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/UsAsciiUtils.java000066400000000000000000000033401506234003700274060ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import java.nio.CharBuffer; /** * Utility to remove non-printable characters from the US-ASCII String. */ final class UsAsciiUtils { private UsAsciiUtils() { throw new IllegalStateException("Utility class"); } /** * Removes non-printable characters from the US-ASCII String. * * @param value The original String * @return The possibly modified String, without non-printable US-ASCII characters. * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. */ static char[] toPrintable(final char[] value) { checkNotNull(value, "value"); CharBuffer charBuffer = CharBuffer.allocate(value.length); for (char ch : value) { if (ch >= 127) { throw new IllegalArgumentException( "value contains character '" + ch + "' which is non US-ASCII"); } else if (ch >= 32) { charBuffer.put(ch); } } // Flip the buffer to prepare for reading charBuffer.flip(); char[] charArray = new char[charBuffer.remaining()]; charBuffer.get(charArray); return charArray; } /** * Removes non-printable characters from the US-ASCII String. * * @param value The original String * @return The possibly modified String, without non-printable US-ASCII characters. * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. */ static String toPrintable(final String value) { char[] charArray = checkNotNull(value, "value").toCharArray(); return new String(toPrintable(charArray)); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/000077500000000000000000000000001506234003700261605ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/ScramException.java000066400000000000000000000015051506234003700317500ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; /** * This class represents an generic error when using SCRAM, which is a SASL method. */ public class ScramException extends Exception { private static final long serialVersionUID = 1L; /** * Constructs a new instance of ScramException with a detailed message. * * @param detail A String containing details about the exception */ public ScramException(String detail) { super(detail); } /** * Constructs a new instance of ScramException with a detailed message and a root cause. * * @param detail A String containing details about the exception * @param ex The root exception */ public ScramException(String detail, Throwable ex) { super(detail, ex); } } ScramInvalidServerSignatureException.java000066400000000000000000000021261506234003700362510ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; /** * This class represents an error when verifying the a base64-encoded ServerSignature in a * {@code server-final-message}. * *

Is used by the client to verify that the server has access to the user's authentication * information. */ public class ScramInvalidServerSignatureException extends ScramException { private static final long serialVersionUID = 1L; /** * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message. * * @param detail A String containing details about the exception */ public ScramInvalidServerSignatureException(String detail) { super(detail); } /** * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message and a * root cause. * * @param detail A String containing details about the exception * @param ex The root exception */ public ScramInvalidServerSignatureException(String detail, Throwable ex) { super(detail, ex); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java000066400000000000000000000015161506234003700327450ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; /** * This class represents an error when parsing SCRAM messages. */ public class ScramParseException extends ScramException { private static final long serialVersionUID = 1L; /** * Constructs a new instance of ScramParseException with a detailed message. * * @param detail A String containing details about the exception */ public ScramParseException(String detail) { super(detail); } /** * Constructs a new instance of ScramParseException with a detailed message and a root cause. * * @param detail A String containing details about the exception * @param ex The root exception */ public ScramParseException(String detail, Throwable ex) { super(detail, ex); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/ScramRuntimeException.java000066400000000000000000000015471506234003700333220ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; /** * This class represents an error when using SCRAM, which is a SASL method. */ public class ScramRuntimeException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Constructs a new instance of ScramRuntimeException with a detailed message. * * @param detail A String containing details about the exception */ public ScramRuntimeException(String detail) { super(detail); } /** * Constructs a new instance of ScramRuntimeException with a detailed message and a root cause. * * @param detail A String containing details about the exception * @param ex The root exception */ public ScramRuntimeException(String detail, Throwable ex) { super(detail, ex); } } ScramServerErrorException.java000066400000000000000000000027541506234003700341010ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; /** * This class specifies an error that occurred during authentication exchange in a * {@code server-final-message}. * *

It is sent by the server in its {@code server-final-message} and can help diagnose the reason * for the authentication exchange failure. */ public class ScramServerErrorException extends ScramException { private static final long serialVersionUID = 1L; /** * {@code server-error-value}. */ private final String serverError; /** * Constructs a new instance of ScramServerErrorException with a detailed message. * * @param serverError The SCRAM error in the message */ public ScramServerErrorException(String serverError) { super(ServerErrorValue.getErrorMessage(serverError)); this.serverError = serverError; } /** * Constructs a new instance of ScramServerErrorException with a detailed message and a root * cause. * * @param serverError The SCRAM error in the message * @param ex The root exception */ public ScramServerErrorException(String serverError, Throwable ex) { super(ServerErrorValue.getErrorMessage(serverError), ex); this.serverError = serverError; } /** * Return the "e=" server-error-value from the server-final-message. * * @return the error type returned in the server-final-message */ public String getServerError() { return serverError; } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/ServerErrorValue.java000066400000000000000000000042571506234003700323100ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.exception; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * This attribute specifies an error that occurred during authentication exchange. It is sent by the * server in its final message and can help diagnose the reason for the authentication exchange * failure. */ public final class ServerErrorValue { private static final ConcurrentMap ERROR_MESSAGE = initServerErrorValue(); private ServerErrorValue() { throw new IllegalStateException(); } private static ConcurrentMap initServerErrorValue() { ConcurrentMap map = new ConcurrentHashMap<>(); map.put("invalid-encoding", "The message format or encoding is incorrect"); map.put("extensions-not-supported", "Requested extensions are not recognized by the server"); map.put("invalid-proof", "The client-provided proof is invalid"); map.put("channel-bindings-dont-match", "Channel bindings sent by the client don't match those expected by the server."); map.put("server-does-support-channel-binding", "Server doesn't support channel binding at all."); map.put("channel-binding-not-supported", "Channel binding is not supported for this user"); map.put("unsupported-channel-binding-type", "The requested channel binding type is not supported."); map.put("unknown-user", "The specified username is not recognized"); map.put("invalid-username-encoding", "The username encoding is invalid (either invalid UTF-8 or SASLprep failure)"); map.put("no-resources", "The server lacks resources to process the request"); map.put("other-error", "A generic error occurred that doesn't fit into other categories"); return map; } /** * This get the error message used in a {@link ScramServerErrorException}. * * @param errorValue the {@code server-error-value} send by the server * @return String with a user friendly message about the error */ public static String getErrorMessage(String errorValue) { return ERROR_MESSAGE.get(errorValue); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/exception/package-info.java000066400000000000000000000003621506234003700313500ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ /** * This package expose the exceptions that can be throw by the client/server * implementations of SCRAM. */ package com.ongres.scram.common.exception; scram-3.2/scram-common/src/main/java/com/ongres/scram/common/package-info.java000066400000000000000000000004741506234003700273560ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ /** * This package expose the messages used to implement a client/server of Salted Challenge Response * Authentication Mechanism (SCRAM). * * @implNote {@code extensions} are not supported. */ package com.ongres.scram.common; scram-3.2/scram-common/src/main/java/com/ongres/scram/common/util/000077500000000000000000000000001506234003700251375ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java/com/ongres/scram/common/util/Preconditions.java000066400000000000000000000103111506234003700306160ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Generic utility methods used to validate data. * * @apiNote This is not part of the public API of the SCRAM library, it's provided as a helper * utility and could be renamed or removed at any time. */ public final class Preconditions { private Preconditions() { throw new IllegalStateException("Utility class"); } /** * Checks that the argument is not null. * * @param value The value to be checked * @param valueName The name of the value that is checked in the method * @param The type of the value * @return The same value passed as argument * @throws IllegalArgumentException If value is null. */ public static @NotNull T checkNotNull(@Nullable T value, @NotNull String valueName) { if (null == value) { throw new IllegalArgumentException("Null value for '" + valueName + "'"); } return value; } @SuppressWarnings("null") public static @NotNull T castNonNull(@Nullable T ref) { assert ref != null : "Misuse of castNonNull: called with a null argument"; return (@NotNull T) ref; } /** * Checks that the String is not null and not empty. * * @param value The String to check * @param valueName The name of the value that is checked in the method * @return The same String passed as argument * @throws IllegalArgumentException If value is null or empty */ public static @NotNull String checkNotEmpty(@NotNull String value, @NotNull String valueName) { if (checkNotNull(value, valueName).isEmpty()) { throw new IllegalArgumentException("The value for '" + valueName + "' must not be empty"); } return value; } /** * Checks that the char[] is not null and not empty. * * @param value The String to check * @param valueName The name of the value that is checked in the method * @return The same String passed as argument * @throws IllegalArgumentException If value is null or empty */ public static char @NotNull [] checkNotEmpty(char @NotNull [] value, @NotNull String valueName) { if (checkNotNull(value, valueName).length == 0) { throw new IllegalArgumentException("The value for '" + valueName + "' must not be empty"); } return value; } /** * Checks that the argument is valid, based in a check boolean condition. * * @param check The boolean check * @param valueName The name of the value that is checked in the method * @throws IllegalArgumentException if check is not valid */ public static void checkArgument(boolean check, @NotNull String valueName) { if (!check) { throw new IllegalArgumentException("Argument '" + valueName + "' is not valid"); } } /** * Checks that the argument is valid, based in a check boolean condition. * * @param check The boolean check * @param valueName The name of the value that is checked in the method * @param errMsg Detail of the error message * @throws IllegalArgumentException if check is not valid */ public static void checkArgument(boolean check, @NotNull String valueName, @NotNull String errMsg) { if (!check) { throw new IllegalArgumentException("Argument '" + valueName + "' is not valid, " + errMsg); } } /** * Checks that the integer argument is positive. * * @param value The value to be checked * @param valueName The name of the value that is checked in the method * @return The same value passed as argument * @throws IllegalArgumentException If value is equal or less than 0 */ public static int gt0(int value, @NotNull String valueName) { if (value <= 0) { throw new IllegalArgumentException("'" + valueName + "' must be positive, was: " + value); } return value; } /** * Returns {@code true} if the given string is null or is the empty string. * * @param string a String reference to check * @return {@code true} if the string is null or the string is empty */ public static boolean isNullOrEmpty(@Nullable String string) { return string == null || string.isEmpty(); } } scram-3.2/scram-common/src/main/java/com/ongres/scram/common/util/TlsServerEndpoint.java000066400000000000000000000071141506234003700314370ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import org.jetbrains.annotations.NotNull; /** * Utilitiy for extracting the {@code "tls-server-end-point"} channel binding data. * * @apiNote This is not part of the public API of the SCRAM library, it's provided as a helper to * extract the channel-binding data and could be renamed or removed at any time. */ public final class TlsServerEndpoint { /** * The "tls-server-end-point" Channel Binding Type. */ public static final String TLS_SERVER_END_POINT = "tls-server-end-point"; private TlsServerEndpoint() { throw new IllegalStateException("Utility class"); } /** * Get the digest algorithm that would be used for a given signature algorithm name. * *

The TLS server's certificate bytes need to be hashed with SHA-256 if its signature algorithm * is MD5 or SHA-1 as per RFC 5929 (https://tools.ietf.org/html/rfc5929#section-4.1). If something * else is used, the same hash as the signature algorithm is used. * * @param signatureAlgorithm the signature algorithm name for the certificate signature algorithm * @return the MessageDigest algorithm, or {@code null} if the name is not recognized * @see The tls-server-end-point * Channel Binding Type */ private static MessageDigest getDigestAlgorithm(final String signatureAlgorithm) { int index = signatureAlgorithm.indexOf("with"); String algorithm = index > 0 ? signatureAlgorithm.substring(0, index) : "SHA-256"; // if the certificate's signatureAlgorithm uses a single hash // function and that hash function neither MD5 nor SHA-1, then use // the hash function associated with the certificate's signatureAlgorithm. if (!algorithm.startsWith("SHA3-")) { algorithm = algorithm.replace("SHA", "SHA-"); } // if the certificate's signatureAlgorithm uses a single hash // function, and that hash function is either MD5 [RFC1321] or SHA-1 // [RFC3174], then use SHA-256 [FIPS-180-3] if ("MD5".equals(algorithm) || "SHA-1".equals(algorithm)) { algorithm = "SHA-256"; } try { return MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { return null; } } /** * The hash of the TLS server's certificate [RFC5280] as it appears, octet for octet, in the * server's Certificate message. Note that the Certificate message contains a certificate_list, in * which the first element is the server's certificate. * *

The TLS server's certificate bytes need to be hashed with SHA-256 if its signature algorithm * is MD5 or SHA-1 as per RFC 5929 (https://tools.ietf.org/html/rfc5929#section-4.1). If something * else is used, the same hash as the signature algorithm is used. * * @param serverCert the TLS server's peer certificate * @return the hash of the TLS server's peer certificate * @throws CertificateEncodingException if an encoding error occurs. */ public static byte @NotNull [] getChannelBindingData(final @NotNull X509Certificate serverCert) throws CertificateEncodingException { MessageDigest digestAlgorithm = getDigestAlgorithm(serverCert.getSigAlgName()); if (digestAlgorithm == null) { return new byte[0]; } return digestAlgorithm.digest(serverCert.getEncoded()); } } scram-3.2/scram-common/src/main/java9/000077500000000000000000000000001506234003700176035ustar00rootroot00000000000000scram-3.2/scram-common/src/main/java9/module-info.java000066400000000000000000000004311506234003700226620ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ module com.ongres.scram.common { requires transitive com.ongres.saslprep; exports com.ongres.scram.common; exports com.ongres.scram.common.exception; exports com.ongres.scram.common.util; }scram-3.2/scram-common/src/test/000077500000000000000000000000001506234003700166245ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/000077500000000000000000000000001506234003700175455ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/000077500000000000000000000000001506234003700203235ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/ongres/000077500000000000000000000000001506234003700216205ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/ongres/scram/000077500000000000000000000000001506234003700227255ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/ongres/scram/JarFileCheckIT.java000066400000000000000000000044751506234003700263110ustar00rootroot00000000000000/* * Copyright (c) 2024 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.module.ModuleDescriptor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class JarFileCheckIT { private static JarFile jarFile; private static Path buildJarPath; @BeforeAll static void beforeAll() throws IOException { buildJarPath = Paths.get(System.getProperty("buildJar")); assertTrue(Files.exists(buildJarPath)); jarFile = new JarFile(buildJarPath.toFile(), true); } @AfterAll static void afterAll() throws IOException { jarFile.close(); } @Test void checkLicense() throws IOException { JarEntry jarLicense = jarFile.getJarEntry("META-INF/LICENSE"); assertNotNull(jarLicense, "LICENSE file should be present in the final JAR file"); try (InputStream is = jarFile.getInputStream(jarLicense); BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) { String line = reader.readLine(); assertEquals("Copyright (c) 2017 OnGres, Inc.", line); } } @Test void checkMultiReleaseManifest() throws IOException { Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); String multiReleaseValue = mainAttributes.getValue(new Attributes.Name("Multi-Release")); assertNotNull(multiReleaseValue); assertEquals("true", multiReleaseValue); } @Test void checkModuleInfoPresent() throws IOException { JarEntry jarModuleInfo = jarFile.getJarEntry("META-INF/versions/9/module-info.class"); ModuleDescriptor moduleDescriptor = ModuleDescriptor.read(jarFile.getInputStream(jarModuleInfo)); assertNotNull(moduleDescriptor); assertEquals("com.ongres.scram.common", moduleDescriptor.name()); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/000077500000000000000000000000001506234003700242155ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ClientFinalMessageTest.java000066400000000000000000000010361506234003700314150ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class ClientFinalMessageTest { @Test void writeToWithoutProofValid() { StringBuilder sb = ClientFinalMessage.withoutProof(new StringBuilder(), new Gs2Header(Gs2CbindFlag.CLIENT_NOT), null, RfcExampleSha1.FULL_NONCE); assertEquals(RfcExampleSha1.CLIENT_FINAL_MESSAGE_WITHOUT_PROOF, sb.toString()); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ClientFirstMessageTest.java000066400000000000000000000126441506234003700314620ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.ongres.scram.common.exception.ScramParseException; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class ClientFirstMessageTest { @ParameterizedTest @CsvSource(value = {"CLIENT_NOT,cbind,,user,nonce", "CHANNEL_BINDING_REQUIRED,,,user,nonce", "CHANNEL_BINDING_REQUIRED,tls-*,,user,nonce", "CLIENT_YES_SERVER_NOT,,,,nonce", "CLIENT_YES_SERVER_NOT,,,'',nonce", "CHANNEL_BINDING_REQUIRED,tls-export,,u,", "CHANNEL_BINDING_REQUIRED,tls-export,,u,''"}) void constructorTestInvalid(@NotNull Gs2CbindFlag flag, String cbName, String authzid, @NotNull String user, @NotNull String nonce) { assertThrows(IllegalArgumentException.class, () -> new ClientFirstMessage(flag, cbName, authzid, user, nonce)); } @Test void writeToValidValues() { assertEquals("n,,n=user,r=fyko", new ClientFirstMessage("user", "fyko").toString()); assertEquals("y,,n=user,r=fyko", new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", "fyko") .toString()); assertEquals("p=tls-server-end-point,,n=user,r=fyko", new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", null, "user", "fyko").toString()); assertEquals("p=tls-server-end-point,a=authzid,n=user,r=fyko", new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", "authzid", "user", "fyko").toString()); } @Test void parseToValidValues() throws ScramParseException { assertEquals(ClientFirstMessage.parseFrom("n,,n=user,r=fyko").toString(), new ClientFirstMessage("user", "fyko").toString()); assertEquals(ClientFirstMessage.parseFrom("y,,n=user,r=fyko").toString(), new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", "fyko") .toString()); assertEquals(ClientFirstMessage.parseFrom("p=tls-server-end-point,,n=user,r=fyko").toString(), new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", null, "user", "fyko").toString()); assertEquals( ClientFirstMessage.parseFrom("p=tls-server-end-point,a=authzid,n=user,r=fyko").toString(), new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", "authzid", "user", "fyko").toString()); } @Test void parseFromValidValues() throws ScramParseException { ClientFirstMessage m1 = ClientFirstMessage.parseFrom("n,,n=user,r=" + CLIENT_NONCE); assertFalse(m1.isChannelBindingRequired()); assertSame(Gs2CbindFlag.CLIENT_NOT, m1.getGs2Header().getChannelBindingFlag()); assertNull(m1.getGs2Header().getChannelBindingName()); assertNull(m1.getGs2Header().getAuthzid()); assertEquals("user", m1.getUsername()); assertEquals(CLIENT_NONCE, m1.getClientNonce()); ClientFirstMessage m2 = ClientFirstMessage.parseFrom("y,,n=user,r=" + CLIENT_NONCE); assertTrue( !m2.isChannelBindingRequired() && m2.getGs2Header().getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT && null == m2.getGs2Header().getAuthzid() && "user".equals(m2.getUsername()) && CLIENT_NONCE.equals(m2.getClientNonce())); ClientFirstMessage m3 = ClientFirstMessage.parseFrom("y,a=user2,n=user,r=" + CLIENT_NONCE); assertTrue( !m3.isChannelBindingRequired() && m3.getGs2Header().getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT && null != m3.getGs2Header().getAuthzid() && "user2".equals(m3.getGs2Header().getAuthzid()) && "user".equals(m3.getUsername()) && CLIENT_NONCE.equals(m3.getClientNonce())); ClientFirstMessage m4 = ClientFirstMessage.parseFrom("p=tls-unique,a=user2,n=user,r=" + CLIENT_NONCE); assertTrue(m4.isChannelBindingRequired()); assertSame(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, m4.getGs2Header().getChannelBindingFlag()); assertNotNull(m4.getGs2Header().getChannelBindingName()); assertEquals("tls-unique", m4.getGs2Header().getChannelBindingName()); assertNotNull(m4.getGs2Header().getAuthzid()); assertEquals("user2", m4.getGs2Header().getAuthzid()); assertEquals("user", m4.getUsername()); assertEquals(CLIENT_NONCE, m4.getClientNonce()); } @Test void parseFromInvalidValues() { String[] invalidValues = new String[] { "n,,r=user,r=" + CLIENT_NONCE, "n,,z=user,r=" + CLIENT_NONCE, "n,,n=user", "n,", "n,,", "n,,n=user,r", "n,,n=user,r=" }; int n = 0; for (String s : invalidValues) { try { assertNotNull(ClientFirstMessage.parseFrom(s)); } catch (ScramParseException e) { n++; } } assertEquals(invalidValues.length, n); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/Gs2AttributeValueTest.java000066400000000000000000000046051506234003700312410ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import com.ongres.scram.common.Gs2AttributeValue; import com.ongres.scram.common.Gs2Attributes; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.NullSource; class Gs2AttributeValueTest { @ParameterizedTest @NullSource @EnumSource(value = Gs2Attributes.class, names = {"CHANNEL_BINDING_REQUIRED", "AUTHZID"}) void constructorNotAllowsNullValuesForCbAuthzId(Gs2Attributes attrib) { assertThrows(IllegalArgumentException.class, () -> new Gs2AttributeValue(attrib, null)); } @ParameterizedTest @EnumSource(value = Gs2Attributes.class, names = {"CLIENT_NOT", "CLIENT_YES_SERVER_NOT"}) void constructorAllowsNullValuesForClientnClienty(Gs2Attributes attrib) { Gs2AttributeValue gs2 = assertDoesNotThrow(() -> new Gs2AttributeValue(attrib, null)); assertNotNull(gs2); } @Test void parseIllegalValuesStructure() { String[] values = new String[] {"", "as", "asdfjkl", Gs2Attributes.CHANNEL_BINDING_REQUIRED.getChar() + "="}; int n = 0; for (String value : values) { try { assertNotNull(Gs2AttributeValue.parse(value)); } catch (IllegalArgumentException e) { n++; } } assertEquals(values.length, n, "Not every illegal value thrown IllegalArgumentException"); } @Test void parseIllegalValuesInvalidGS2Attibute() { String[] values = new String[] {"z=asdfasdf", "i=value"}; int n = 0; for (String value : values) { try { assertNotNull(Gs2AttributeValue.parse(value)); } catch (IllegalArgumentException e) { n++; } } assertEquals(values.length, n, "Not every illegal value thrown IllegalArgumentException"); } @Test void parseLegalValues() { String[] values = new String[] {"n", "y", "p=value", "a=authzid"}; for (String value : values) { assertNotNull(Gs2AttributeValue.parse(value)); } } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/Gs2HeaderTest.java000066400000000000000000000047771506234003700275030ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.ongres.scram.common.Gs2CbindFlag; import com.ongres.scram.common.Gs2Header; import org.junit.jupiter.api.Test; class Gs2HeaderTest { private static final String[] VALID_GS2HEADER_STRINGS = new String[] { "n,", "y,", "n,a=blah", "p=tls-server-end-point,", "p=tls-server-end-point,a=b" }; private static final Gs2Header[] VALID_GS_2_HEADERS = new Gs2Header[] { new Gs2Header(Gs2CbindFlag.CLIENT_NOT), new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT), new Gs2Header(Gs2CbindFlag.CLIENT_NOT, null, "blah"), new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point"), new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", "b") }; private void assertGS2Header(String expected, Gs2Header gs2Header) { assertEquals(expected, gs2Header.writeTo(new StringBuilder()).toString()); } @Test void constructorValid() { for (int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { assertGS2Header(VALID_GS2HEADER_STRINGS[i], VALID_GS_2_HEADERS[i]); } } @Test void constructorInvalid1() { assertThrows(IllegalArgumentException.class, () -> new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null)); } @Test void constructorInvalid2() { assertThrows(IllegalArgumentException.class, () -> new Gs2Header(Gs2CbindFlag.CLIENT_NOT, "blah")); } @Test void constructorInvalid3() { assertThrows(IllegalArgumentException.class, () -> new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, "blah")); } @Test void constructorInvalid4() { assertThrows(IllegalArgumentException.class, () -> new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "b")); } @Test void parseFromInvalid() { String[] invalids = new String[] {"Z,", "n,Z=blah", "p,", "n=a,"}; int n = 0; for (String invalid : invalids) { try { Gs2Header.parseFrom(invalid); } catch (IllegalArgumentException e) { n++; } } assertEquals(invalids.length, n); } @Test void parseFromValid() { for (int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { assertGS2Header( VALID_GS_2_HEADERS[i].writeTo(new StringBuilder()).toString(), Gs2Header.parseFrom(VALID_GS2HEADER_STRINGS[i])); } } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java000066400000000000000000000031351506234003700276250ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; /** * Constants for examples of the RFC for SHA-1 tests. */ public class RfcExampleSha1 { public static final String USER = "user"; public static final String PASSWORD = "pencil"; public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; public static final String CLIENT_FIRST_MESSAGE = "n,," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; public static final int SERVER_ITERATIONS = 4096; public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + ",i=" + SERVER_ITERATIONS; public static final String GS2_HEADER_BASE64 = "biws"; public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + ",r=" + FULL_NONCE; public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + SERVER_FIRST_MESSAGE + "," + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java000066400000000000000000000032421506234003700300000ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; /** * Constants for examples of the RFC for SHA-256 tests. */ public class RfcExampleSha256 { public static final String USER = "user"; public static final String PASSWORD = "pencil"; public static final String CLIENT_NONCE = "rOprNGfwEbeRWgbNEkqO"; public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; public static final String CLIENT_FIRST_MESSAGE = "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; public static final String SERVER_SALT = "W22ZaJ0SNY7soEsUEjb6gQ=="; public static final int SERVER_ITERATIONS = 4096; public static final String SERVER_NONCE = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"; public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + ",i=" + SERVER_ITERATIONS; public static final String GS2_HEADER_BASE64 = "biws"; public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + ",r=" + FULL_NONCE; public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + SERVER_FIRST_MESSAGE + "," + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; public static final String CLIENT_FINAL_MESSAGE_PROOF = "dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ="; public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; public static final String SERVER_FINAL_MESSAGE = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/SaslPrepTest.java000066400000000000000000000025401506234003700274520ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import com.ongres.stringprep.Profile; import com.ongres.stringprep.Stringprep; import org.junit.jupiter.api.Test; class SaslPrepTest { private static final Profile saslPrep = Stringprep.getProvider("SASLprep"); @Test void rfc4013Examples() throws IOException { // Taken from https://tools.ietf.org/html/rfc4013#section-3 assertEquals("IX", saslPrep.prepareStored("I\u00ADX")); assertEquals("user", saslPrep.prepareStored("user")); assertEquals("USER", saslPrep.prepareStored("USER")); assertEquals("a", saslPrep.prepareStored("\u00AA")); assertEquals("IX", saslPrep.prepareStored("\u2168")); try { saslPrep.prepareStored("\u0007"); fail("Should throw IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("Prohibited ASCII control \"0x0007\"", e.getMessage()); } try { saslPrep.prepareStored("\u0627\u0031"); fail("Should thow IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("RandALCat character is not the first and the last character", e.getMessage()); } } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java000066400000000000000000000056211506234003700316520ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.RfcExampleSha1.CLIENT_FINAL_MESSAGE_PROOF; import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; import static com.ongres.scram.common.RfcExampleSha1.FULL_NONCE; import static com.ongres.scram.common.RfcExampleSha1.GS2_HEADER_BASE64; import static com.ongres.scram.common.RfcExampleSha1.SERVER_FINAL_MESSAGE; import static com.ongres.scram.common.RfcExampleSha1.SERVER_ITERATIONS; import static com.ongres.scram.common.RfcExampleSha1.SERVER_SALT; import static com.ongres.scram.common.RfcExampleSha1.USER; import static com.ongres.scram.common.ScramAttributes.CLIENT_PROOF; import static com.ongres.scram.common.ScramAttributes.USERNAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import com.ongres.scram.common.exception.ScramParseException; import org.junit.jupiter.api.Test; class ScramAttributeValueTest { private static final Object NULL = null; // Skip validation for null analisys @Test void constructorDoesNotAllowNullValue() { assertThrows(IllegalArgumentException.class, () -> new ScramAttributeValue(USERNAME, (String) NULL), "A null value must throw an IllegalArgumentException"); } @Test void parseIllegalValuesStructure() { String[] values = new String[] { null, "", "asdf", "asdf=a", CLIENT_PROOF.getChar() + "=", CLIENT_PROOF.getChar() + ",a" }; int n = 0; for (String value : values) { try { assertNotNull(ScramAttributeValue.parse(value)); } catch (ScramParseException e) { n++; } } assertEquals(values.length, n, "Not every illegal value thrown ScramParseException"); } @Test void parseIllegalValuesInvalidSCRAMAttibute() { // SCRAM allows for extensions. If a new attribute is supported and its value has been used // below, // test will fail and will need to be fixed String[] values = new String[] {"z=asdfasdf", "!=value"}; for (String value : values) { assertThrows(ScramParseException.class, () -> ScramAttributeValue.parse(value)); } } @Test void parseLegalValues() throws ScramParseException { String[] legalValues = new String[] { CLIENT_PROOF.getChar() + "=" + "proof", USERNAME.getChar() + "=" + "username", "n=" + USER, "r=" + CLIENT_NONCE, "r=" + FULL_NONCE, "s=" + SERVER_SALT, "i=" + SERVER_ITERATIONS, "c=" + GS2_HEADER_BASE64, "p=" + CLIENT_FINAL_MESSAGE_PROOF, SERVER_FINAL_MESSAGE, }; for (String value : legalValues) { assertNotNull(ScramAttributeValue.parse(value)); } assertNotNull(ScramAttributeValue.parse("e=unsupported-channel-binding-type")); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java000066400000000000000000000237631506234003700306710ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; import java.util.Locale; import org.junit.jupiter.api.Test; class ScramFunctionsTest { private void assertBytesEqualsBase64(String expected, byte[] actual) { assertArrayEquals(ScramStringFormatting.base64Decode(expected), actual); } @Test void hmac() { String message = "The quick brown fox jumps over the lazy dog"; byte[] key = generateSaltedPasswordSha256(); assertBytesEqualsBase64( "1zw4SuJ+BRmn6corI3Y1+eGQwGQ=", ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_1, key, message.getBytes(StandardCharsets.US_ASCII))); assertBytesEqualsBase64( "+Q4a/8FjMG6MoLE/8y8LWyEBJmjpVLbuXtU8rnLd/5E=", ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_256, key, message.getBytes(StandardCharsets.US_ASCII))); assertBytesEqualsBase64( "uu/n7DW8kGuWWANfaHT/rwEU/EBufylMLTWOCtLmvxp" + "2Zmx01UpZO4nauZfkaSWob8jt7no0+xWIVXv/d7LvkQ==", ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_512, key, message.getBytes(StandardCharsets.US_ASCII))); } private byte[] generateSaltedPassword() { return ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "pencil".toCharArray(), Base64.getDecoder().decode("QSXCR+Q6sek8bf92".getBytes(StandardCharsets.UTF_8)), 4096); } private byte[] generateSaltedPasswordSha256() { byte[] salt = Base64.getDecoder().decode("Fgh8JU2AlRjBHUsIU/GgtQ=="); byte[] saltedPassword = ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_256, StringPreparation.SASL_PREPARATION, "test".toCharArray(), salt, 4096); byte[] clientKey = ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_256, saltedPassword); byte[] storedKey = ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_256, clientKey); byte[] serverKey = ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_256, saltedPassword); String encodeToStringSalt = Base64.getEncoder().encodeToString(salt); String encodeToStringClient = Base64.getEncoder().encodeToString(storedKey); String encodeToStringServer = Base64.getEncoder().encodeToString(serverKey); assertEquals("XiT346dvVvPmnmTWeW0djrcMYBGuiQDh8QYbBJaBm/I=", encodeToStringClient); assertEquals("CY9vUvDF8v6FIR8Zwircvd82YV58J5AwWiMWwfssuwg=", encodeToStringServer); String pw = String.format(Locale.ROOT, "%S$%d:%s$%s:%s", ScramMechanism.SCRAM_SHA_256.getName(), 4096, encodeToStringSalt, encodeToStringClient, encodeToStringServer); assertEquals( "SCRAM-SHA-256$4096:Fgh8JU2AlRjBHUsIU/GgtQ==$XiT346dvVvPmnmTWeW0djrcMYBGuiQDh8QYbBJaBm/I=:" + "CY9vUvDF8v6FIR8Zwircvd82YV58J5AwWiMWwfssuwg=", pw); return ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_256, StringPreparation.SASL_PREPARATION, "pencil".toCharArray(), Base64.getDecoder().decode("W22ZaJ0SNY7soEsUEjb6gQ=="), 4096); } @Test void saltedPassword() { assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", generateSaltedPassword()); } @Test void saltedPasswordWithSaslPrep() { assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "\u2168\u3000a\u0300".toCharArray(), Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "\u00ADIX \u00E0".toCharArray(), Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "IX \u00E0".toCharArray(), Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "\u0070enc\u1806il".toCharArray(), Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 4096)); IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "\u2168\u3000a\u0300\u0007".toCharArray(), Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 6400)); assertEquals("Prohibited ASCII control \"0x0007\"", e.getMessage()); assertBytesEqualsBase64("MFB9tXSMpUK2frvCND2TWRGdiVY=", ScramFunctions.saltedPassword( ScramMechanism.SCRAM_SHA_1_PLUS, StringPreparation.SASL_PREPARATION, "\u0070enc\u1806il \u00B4\u00BD".toCharArray(), Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 4096)); } @Test void saltedPasswordSha256() { assertBytesEqualsBase64("xKSVEDI6tPlSysH6mUQZOeeOp01r6B3fcJbodRPcYV0=", generateSaltedPasswordSha256()); } private byte[] generateClientKey() { return ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_1, generateSaltedPassword()); } private byte[] generateClientKeySha256() { return ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_256, generateSaltedPasswordSha256()); } @Test void clientKey() { assertBytesEqualsBase64("4jTEe/bDZpbdbYUrmaqiuiZVVyg=", generateClientKey()); } @Test void clientKeySha256() { assertBytesEqualsBase64("pg/JI9Z+hkSpLRa5btpe9GVrDHJcSEN0viVTVXaZbos=", generateClientKeySha256()); } private byte[] generateStoredKey() { return ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_1, generateClientKey()); } private byte[] generateStoredKeySha256() { return ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_256, generateClientKeySha256()); } @Test void storedKey() { assertBytesEqualsBase64("6dlGYMOdZcOPutkcNY8U2g7vK9Y=", generateStoredKey()); } @Test void storedKeySha256() { assertBytesEqualsBase64("WG5d8oPm3OtcPnkdi4Uo7BkeZkBFzpcXkuLmtbsT4qY=", generateStoredKeySha256()); } private byte[] generateServerKey() { return ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_1, generateSaltedPassword()); } private byte[] generateServerKeySha256() { return ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_256, generateSaltedPasswordSha256()); } @Test void serverKey() { assertBytesEqualsBase64("D+CSWLOshSulAsxiupA+qs2/fTE=", generateServerKey()); } @Test void serverKeySha256() { assertBytesEqualsBase64("wfPLwcE6nTWhTAmQ7tl2KeoiWGPlZqQxSrmfPwDl2dU=", generateServerKeySha256()); } private byte[] generateClientSignature() { return ScramFunctions.clientSignature(ScramMechanism.SCRAM_SHA_1, generateStoredKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); } private byte[] generateClientSignatureSha256() { return ScramFunctions.clientSignature(ScramMechanism.SCRAM_SHA_256, generateStoredKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); } @Test void clientSignature() { assertBytesEqualsBase64("XXE4xIawv6vfSePi2ovW5cedthM=", generateClientSignature()); } @Test void clientSignatureSha256() { assertBytesEqualsBase64("0nMSRnwopAqKfwXHPA3jPrPL+0qDeDtYFEzxmsa+G98=", generateClientSignatureSha256()); } private byte[] generateClientProof() { return ScramFunctions.clientProof(generateClientKey(), generateClientSignature()); } private byte[] generateClientProofSha256() { return ScramFunctions.clientProof(generateClientKeySha256(), generateClientSignatureSha256()); } @Test void clientProof() { assertBytesEqualsBase64("v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", generateClientProof()); } @Test void clientProofSha256() { assertBytesEqualsBase64("dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", generateClientProofSha256()); } private byte[] generateServerSignature() { return ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_1, generateServerKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); } private byte[] generateServerSignatureSha256() { return ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_256, generateServerKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); } @Test void serverSignature() { assertBytesEqualsBase64("rmF9pqV8S7suAoZWja4dJRkFsKQ=", generateServerSignature()); } @Test void serverSignatureSha256() { assertBytesEqualsBase64("6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=", generateServerSignatureSha256()); } @Test void verifyClientProof() { assertTrue( ScramFunctions.verifyClientProof( ScramMechanism.SCRAM_SHA_1, generateClientProof(), generateStoredKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE)); } @Test void verifyClientProofSha256() { assertTrue( ScramFunctions.verifyClientProof( ScramMechanism.SCRAM_SHA_256, generateClientProofSha256(), generateStoredKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE)); } @Test void verifyServerSignature() { assertTrue( ScramFunctions.verifyServerSignature( ScramMechanism.SCRAM_SHA_1, generateServerKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE, generateServerSignature())); } @Test void verifyServerSignatureSha256() { assertTrue( ScramFunctions.verifyServerSignature( ScramMechanism.SCRAM_SHA_256, generateServerKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE, generateServerSignatureSha256())); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ScramMechanismTest.java000066400000000000000000000062641506234003700306220ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; class ScramMechanismTest { private static final byte[] EMPTY_KEY = new byte[32]; @ParameterizedTest @ValueSource(strings = {"SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"}) void testIanaScramMechanisms(@NotNull String name) { assertNotNull(ScramMechanism.byName(name)); } @ParameterizedTest @EnumSource(ScramMechanism.class) void testNameConvention(ScramMechanism scramMechanism) { // Note that SASL mechanism names are limited to 20 octets, which means that only // hash function names with lengths shorter or equal to 9 octets // (20-length("SCRAM-")-length("-PLUS") can be used. String name = scramMechanism.getName(); assertTrue(name.startsWith("SCRAM-"), "name should start with SCRAM-"); if (scramMechanism.isPlus()) { assertTrue(name.endsWith("-PLUS"), "name should end with -PLUS"); } else { assertFalse(name.endsWith("-PLUS"), "name should not end with -PLUS"); } String hashName = name.replace("SCRAM-", "").replace("-PLUS", ""); assertEquals(scramMechanism.getHashAlgorithmName(), hashName); assertTrue(hashName.length() <= 9); } @ParameterizedTest @ValueSource(strings = {"SCRAM-SHA", "SHA-1-PLUS", "SCRAM-SHA-256-", "SCRAM-SHA-256-PLUS!"}) @EmptySource void byNameInvalid(@NotNull String name) { assertNull(ScramMechanism.byName(name)); } @ParameterizedTest @NullSource void byNullName(@NotNull String name) { assertThrows(IllegalArgumentException.class, () -> ScramMechanism.byName(name)); } @ParameterizedTest @EnumSource(ScramMechanism.class) void testHashSupportedByJvm(ScramMechanism scramMechanism) { byte[] digest = scramMechanism.digest(new byte[0]); assertNotNull(digest, "got a null digest"); assertEquals(scramMechanism.getKeyLength() / 8, digest.length); } @ParameterizedTest @MethodSource("provideSupportedMechanisms") void testHmacSupportedByJvm(@NotNull String mechanism) { ScramMechanism scramMechanism = ScramMechanism.byName(mechanism); assertNotNull(scramMechanism); byte[] hmac = scramMechanism.hmac(EMPTY_KEY, new byte[0]); assertNotNull(hmac, "got a null HMAC"); assertEquals(scramMechanism.getKeyLength() / 8, hmac.length); } private static @NotNull List<@NotNull String> provideSupportedMechanisms() { return ScramMechanism.supportedMechanisms(); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java000066400000000000000000000037071506234003700322160ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class ScramStringFormattingTest { private static final String[] VALUES_NO_CHARS_TO_BE_ESCAPED = new String[] {"asdf", "''--%%21", " ttt???"}; private static final String[] VALUES_TO_BE_ESCAPED = new String[] { ",", "=", "a,b", "===", "a=", ",=,", "=2C", "=3D" }; private static final String[] ESCAPED_VALUES = new String[] { "=2C", "=3D", "a=2Cb", "=3D=3D=3D", "a=3D", "=2C=3D=2C", "=3D2C", "=3D3D" }; private static final String[] INVALID_SASL_NAMES = new String[] {"=", "as,df", "a=b", " ttt???=2D"}; @Test void toSaslNameNoCharactersToBeEscaped() { for (String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { assertEquals(s, ScramStringFormatting.toSaslName(s)); } } @Test void toSaslNameWithCharactersToBeEscaped() { for (int i = 0; i < VALUES_TO_BE_ESCAPED.length; i++) { assertEquals(ESCAPED_VALUES[i], ScramStringFormatting.toSaslName(VALUES_TO_BE_ESCAPED[i])); } } @Test void fromSaslNameNoCharactersToBeEscaped() { for (String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { assertEquals(s, ScramStringFormatting.fromSaslName(s)); } } @Test void fromSaslNameWithCharactersToBeUnescaped() { for (int i = 0; i < ESCAPED_VALUES.length; i++) { assertEquals(VALUES_TO_BE_ESCAPED[i], ScramStringFormatting.fromSaslName(ESCAPED_VALUES[i])); } } @Test void fromSaslNameWithInvalidCharacters() { int n = 0; for (String s : INVALID_SASL_NAMES) { try { assertEquals(s, ScramStringFormatting.fromSaslName(s)); } catch (IllegalArgumentException e) { n++; } } assertTrue(n == INVALID_SASL_NAMES.length, "Not all values produced IllegalArgumentException"); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ServerFinalMessageTest.java000066400000000000000000000050011506234003700314410ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE; import static com.ongres.scram.common.RfcExampleSha1.PASSWORD; import static com.ongres.scram.common.RfcExampleSha1.SERVER_FINAL_MESSAGE; import static com.ongres.scram.common.RfcExampleSha1.SERVER_ITERATIONS; import static com.ongres.scram.common.RfcExampleSha1.SERVER_SALT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Base64; import com.ongres.scram.common.exception.ScramParseException; import org.junit.jupiter.api.Test; class ServerFinalMessageTest { @Test void validConstructor() { byte[] serverKey = ScramFunctions.serverKey( ScramMechanism.SCRAM_SHA_1, ScramFunctions.saltedPassword(ScramMechanism.SCRAM_SHA_1, StringPreparation.NO_PREPARATION, PASSWORD.toCharArray(), Base64.getDecoder().decode(SERVER_SALT), SERVER_ITERATIONS)); ServerFinalMessage serverFinalMessage1 = new ServerFinalMessage( ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_1, serverKey, AUTH_MESSAGE)); assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); assertFalse(serverFinalMessage1.isError()); ServerFinalMessage serverFinalMessage2 = new ServerFinalMessage("unknown-user"); assertEquals(ScramAttributes.ERROR.getChar() + "=unknown-user", serverFinalMessage2.toString()); assertTrue(serverFinalMessage2.isError()); } @Test void validParseFrom() throws ScramParseException { ServerFinalMessage serverFinalMessage1 = ServerFinalMessage.parseFrom(SERVER_FINAL_MESSAGE); assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); assertFalse(serverFinalMessage1.isError()); ServerFinalMessage serverFinalMessage2 = ServerFinalMessage.parseFrom("e=channel-binding-not-supported"); assertEquals("e=channel-binding-not-supported", serverFinalMessage2.toString()); assertTrue(serverFinalMessage2.isError()); assertEquals("channel-binding-not-supported", serverFinalMessage2.getServerError()); } @Test void invalidServerError() throws ScramParseException { assertThrows(IllegalArgumentException.class, () -> ServerFinalMessage.parseFrom("e=binding-et-supported")); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/ServerFirstMessageTest.java000066400000000000000000000017301506234003700315040ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; import static com.ongres.scram.common.RfcExampleSha1.SERVER_FIRST_MESSAGE; import static org.junit.jupiter.api.Assertions.assertEquals; import com.ongres.scram.common.exception.ScramParseException; import org.junit.jupiter.api.Test; class ServerFirstMessageTest { @Test void validConstructor() { ServerFirstMessage serverFirstMessage = new ServerFirstMessage( CLIENT_NONCE, "3rfcNHYJY1ZVvWVs7j", "QSXCR+Q6sek8bf92", 4096); assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); } @Test void validParseFrom() throws ScramParseException { ServerFirstMessage serverFirstMessage = ServerFirstMessage.parseFrom(SERVER_FIRST_MESSAGE, CLIENT_NONCE); assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/StringPreparationTest.java000066400000000000000000000146121506234003700313770ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.security.SecureRandom; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; class StringPreparationTest { private static final String[] ONLY_NON_PRINTABLE_STRINGS = new String[] {(char) 13 + "", (char) 13 + "\n\n"}; @ParameterizedTest @NullAndEmptySource void doNormalizeNullEmpty(char[] value) { for (StringPreparation stringPreparation : StringPreparation.values()) { assertThrows(IllegalArgumentException.class, () -> stringPreparation.normalize(value)); } } @ParameterizedTest @ValueSource(strings = { "toastingxenotime", "infecttolerant", "cobblerjack", "zekedigital", "freshscarisdale", "lamwaylon", "lagopodousmonkeys", "fanfarecheesy", "willowfinnegan", "canoeamoeba", "stinkeroddball", "terracecomet", "cakebrazos", "headersidesaddle", "cloudultracrepidarian", "grimegastropub", "stallchilli", "shawnapentagon", "chapeltarp", "rydbergninja", "differencegym", "europiummuscle", "swilledonce", "defensivesyntaxis", "desktopredundant", "stakingsky", "goofywaiting", "boundsemm", "pipermonstrous", "faintfrog", "riskinsist", "constantjunkie", "rejectbroth", "ceilbeau", "ponyjaialai", "burnishselfies", "unamusedglenmore", "parmesanporcupine", "suteconcerto", "ribstony", "sassytwelve", "coursesnasturtium", "singlecinders", "kinkben", "chiefpussface", "unknownivery", "robterra", "wearycubes", "bearcontent", "aquifertrip", "insulinlick", "batterypeace", "rubigloo", "fixessnizort", "coalorecheesy", "logodarthvader", "equipmentbizarre", "charitycolne", "gradecomputer", "incrediblegases", "ingotflyingfish", "abaftmounting", "kissingfluke", "chesterdinky", "anthropicdip", "portalcairo", "purebredhighjump", "jamaicansteeping", "skaterscoins", "chondrulelocust", "modespretty", "otisnadrid", "lagoonone", "arrivepayday", "lawfulpatsy", "customersdeleted", "superiorarod", "abackwarped", "footballcyclic", "sawtshortstop", "waskerleysanidine", "polythenehead", "carpacciosierra", "gnashgabcheviot", "plunkarnisdale", "surfacebased", "wickedpark", "capitalistivan", "kinglassmuse", "adultsceiriog", "medrones", "climaxshops", "archeangolfer", "tomfront", "kobeshift", "nettleaugustus", "bitesizedlion", "crickedbunting", "englishrichard", "dangerousdelmonico", "sparklemicrosoft", "kneepadsfold", "enunciatesunglasses", "parchmentsteak", "meigpiton", "puttingcitrusy", "eyehash", "newtonatomiser", "witchesburberry", "positionwobbly", "clipboardamber", "ricolobster", "calendarpetal", "shinywound", "dealemral", "moonrakerfinnish", "banditliberated", "whippedfanatical", "jargongreasy", "yumlayla", "dwarfismtransition", "doleriteduce", "sikickball", "columngymnastics", "draybowmont", "jupitersnorkling", "siderealmolding", "dowdyrosary", "novaskeeter", "whickerpulley", "rutlandsliders", "categoryflossed", "coiltiedogfish", "brandwaren", "altairlatigo", "acruxyouthscape", "harmonicdash", "jasperserver", "slicedaggie", "gravityfern", "bitsstorm", "readymadehobby", "surfeitgrape", "pantheonslabs", "ammandecent", "skicrackers", "speyfashions", "languagedeeno", "pettyconfit", "minutesshimmering", "thinhopeangellist", "sleevelesscadmium", "controlarc", "robinvolvox", "postboxskylark", "tortepleasing", "lutzdillinger", "amnioteperl", "burntmaximize", "gamblingearn", "bumsouch", "coronagraphdown", "bodgeelearning", "hackingscraper", "hartterbium", "mindyurgonian", "leidlebalki", "labelthumbs", "lincolncrisps", "pearhamster", "termsfiona", "tickingsomber", "hatellynfi", "northumberlandgrotesque", "harpistcaramel", "gentryswiss", "illusionnooks", "easilyrows", "highgluten", "backedallegiance", "laelsitesearch", "methodfix", "teethminstral", "chemicalchildish", "likablepace", "alikealeph", "nalasincere", "investbaroque", "conditionenvelope", "splintsmccue", "carnonprompt", "resultharvey", "acceptsheba", "redditmonsoon", "multiplepostbox", "invitationchurch", "drinksgaliath", "ordersvivid", "mugsgit", "clumpingfreak" }) void doNormalizeValidAsciiCases(String username) { char[] validAsciiUsername = username.toCharArray(); for (StringPreparation stringPreparation : StringPreparation.values()) { assertArrayEquals(validAsciiUsername, stringPreparation.normalize(validAsciiUsername)); } } private static Stream provideRandomStrings() { final SecureRandom srand = new SecureRandom(); return IntStream.iterate(0, i -> i) .limit(1000) .mapToObj(c -> srand.ints(32, 127) .filter(i -> (i >= 32) && (i <= 127)) .limit(128) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString()); } /* * Some simple random testing won't hurt. If a test would fail, create new test with the generated * word. */ @ParameterizedTest @MethodSource("provideRandomStrings") void doNormalizeValidAsciiRandom(String random) { for (StringPreparation stringPreparation : StringPreparation.values()) { assertArrayEquals(random.toCharArray(), stringPreparation.normalize(random.toCharArray()), "'" + random + "' is a printable ASCII string, should not be changed by normalize()"); } } @Test void doNormalizeNoPreparationEmptyAfterNormalization() { for (String s : ONLY_NON_PRINTABLE_STRINGS) { char[] charArray = s.toCharArray(); assertThrows(IllegalArgumentException.class, () -> StringPreparation.NO_PREPARATION.normalize(charArray)); } } @Test void doNormalizeNoPreparationNonEmptyAfterNormalization() { // No exception should be thrown for (String s : ONLY_NON_PRINTABLE_STRINGS) { StringPreparation.NO_PREPARATION.normalize((s + "a").toCharArray()); } } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/StringWritableCsvTest.java000066400000000000000000000072431506234003700313420ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class StringWritableCsvTest { private static final String[] ONE_ARG_VALUES = new String[] {"c=channel", "i=4096", "a=authzid", "n"}; private static final String SEVERAL_VALUES_STRING = "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL"; @Test void writeToNullOrEmpty() { assertEquals(0, StringWritableCsv.writeTo(new StringBuilder()).length()); assertEquals(0, StringWritableCsv.writeTo(new StringBuilder(), new StringWritable[] {}).length()); } @Test void writeToOneArg() { StringWritable[] pairs = new StringWritable[] { new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, "channel"), new ScramAttributeValue(ScramAttributes.ITERATION, "" + 4096), new Gs2AttributeValue(Gs2Attributes.AUTHZID, "authzid"), new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null) }; for (int i = 0; i < pairs.length; i++) { assertEquals(ONE_ARG_VALUES[i], StringWritableCsv.writeTo(new StringBuilder(), pairs[i]).toString()); } } @Test void writeToSeveralArgs() { assertEquals( SEVERAL_VALUES_STRING, StringWritableCsv.writeTo( new StringBuilder(), new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null), null, new ScramAttributeValue(ScramAttributes.USERNAME, "user"), new ScramAttributeValue(ScramAttributes.NONCE, "fyko+d2lbbFgONRv9qkxdawL") ).toString()); } @Test void parseFromEmpty() { assertArrayEquals(new String[] {}, StringWritableCsv.parseFrom("")); } @Test void parseFromOneArgWithLimitsOffsets() { for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s)); } int[] numberEntries = new int[] {0, 1}; for (int n : numberEntries) { for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n)); } } for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3)); } for (int n : numberEntries) { for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n, 0)); } } for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3, 0)); } for (int n : numberEntries) { for (String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {null}, StringWritableCsv.parseFrom(s, n, 1)); } } } @Test void parseFromSeveralArgsWithLimitsOffsets() { assertArrayEquals( new String[] {"n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL"}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING)); assertArrayEquals( new String[] {"n", ""}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2)); assertArrayEquals( new String[] {"", "n=user"}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 1)); assertArrayEquals( new String[] {"r=fyko+d2lbbFgONRv9qkxdawL", null}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 3)); assertArrayEquals( new String[] {null, null}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 4)); assertArrayEquals( new String[] {"n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL", null}, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 5)); } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/UsAsciiUtilsTest.java000066400000000000000000000036741506234003700303130ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.ongres.scram.common.UsAsciiUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; class UsAsciiUtilsTest { @ParameterizedTest @NullSource void toPrintableNull(String value) { assertThrows(IllegalArgumentException.class, () -> UsAsciiUtils.toPrintable(value), () -> "Calling with null value must throw IllegalArgumentException"); } @ParameterizedTest @ValueSource(strings = {"abcdé", "ñ", "€", "Наташа", (char) 127 + ""}) void toPrintableNonASCII(String value) { assertThrows(IllegalArgumentException.class, () -> UsAsciiUtils.toPrintable(value), () -> "String(s) with non-ASCII characters not throwing IllegalArgumentException"); } @ParameterizedTest @CsvSource(value = {" u , u ", "a" + (char) 12 + ",a", (char) 0 + "ttt" + (char) 31 + ",ttt"}, ignoreLeadingAndTrailingWhitespace = false) void toPrintableNonPrintable(String original, String expected) { assertEquals(expected, UsAsciiUtils.toPrintable(original)); } @Test void toPrintableAllPrintable() { List values = new ArrayList(); values.addAll(Arrays.asList( new String[] {(char) 33 + "", "user", "!", "-,.=?", (char) 126 + ""})); for (int c = 33; c < 127; c++) { values.add("---" + (char) c + "---"); } for (String s : values) { assertEquals(s, UsAsciiUtils.toPrintable(s), "All printable String '" + s + "' not returning the same value"); } } } scram-3.2/scram-common/src/test/java/com/ongres/scram/common/util/000077500000000000000000000000001506234003700251725ustar00rootroot00000000000000scram-3.2/scram-common/src/test/java/com/ongres/scram/common/util/NonceTest.java000066400000000000000000000027511506234003700277440ustar00rootroot00000000000000/* * Copyright (c) 2017 OnGres, Inc. * SPDX-License-Identifier: BSD-2-Clause */ package com.ongres.scram.common.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import com.ongres.scram.common.ScramFunctions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class NonceTest { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @ParameterizedTest @ValueSource(ints = {0, -1, Integer.MIN_VALUE}) void nonceInvalidSize(int size) { assertThrows(IllegalArgumentException.class, () -> ScramFunctions.nonce(size, SECURE_RANDOM)); } @Test void nonceValid() throws NoSuchAlgorithmException { int nonces = 1000; int nonceMaxSize = 100; SecureRandom random = new SecureRandom(); // Some more random testing for (int i = 0; i < nonces; i++) { final int size = SECURE_RANDOM.nextInt(nonceMaxSize) + 1; final String nonce = ScramFunctions.nonce(size, random); for (int j = 0; j < nonce.length(); j++) { char c = nonce.charAt(j); if (c == ',' || c < (char) 33 || c > (char) 126) { fail("Character c='" + c + "' is not allowed on a nonce"); } } assertEquals(size, nonce.length()); } } } scram-3.2/scram-parent/000077500000000000000000000000001506234003700150575ustar00rootroot00000000000000scram-3.2/scram-parent/javadoc/000077500000000000000000000000001506234003700164665ustar00rootroot00000000000000scram-3.2/scram-parent/javadoc/style.css000066400000000000000000000011751506234003700203440ustar00rootroot00000000000000table { border-collapse: collapse; font-size: 100%; margin: 1em 1em; } caption { font-weight: bold; text-align: left; font-style: italic; padding-bottom: 0.5em; } td { font-family: 'Courier New', Courier, monospace; font-size: small; border: 1px solid #EEEEEE; padding: 0.5em 1em; vertical-align: top; } blockquote { margin-left: 10px; padding: 10px 20px; background-color: #f5f5f5; border-left: 5px solid #ddd; font-style: italic; font-size: 1.1em; } blockquote::before { content: open-quote; color: #ccc; margin-left: -0.5em; } blockquote::after { content: close-quote; color: #ccc; }scram-3.2/scram-parent/pom.xml000066400000000000000000000660221506234003700164020ustar00rootroot00000000000000 4.0.0 com.ongres.scram scram-parent 3.2 pom SCRAM - Parent Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) https://github.com/ongres/scram 2017 OnGres, Inc https://www.ongres.com BSD 2-Clause "Simplified" License https://spdx.org/licenses/BSD-2-Clause repo com.ongres.aht Álvaro Hernández Tortosa aht@ongres.com com.ongres.matteom Matteo Melli matteom@ongres.com com.ongres.jorsol Jorge Solórzano jorsol@ongres.com scm:git:https://github.com/ongres/scram.git scm:git:git@github.com:ongres/scram.git 3.2 https://github.com/ongres/scram GitHub https://github.com/ongres/scram/issues UTF-8 UTF-8 8 ${base.java.version} ${base.java.version} 2025-09-16T20:00:00Z 26.0.2-1 5.13.4 2.2 3.14.0 3.4.2 3.3.1 3.11.3 3.5.0 3.3.1 3.5.4 3.5.4 3.6.1 3.1.4 0.8.0 3.9.1 3.2.8 0.8.13 1.7.2 3.1.2 4.0.0 2.9.1 11.0.1 3.6.0 2.41.0 4.9.5 4.9.5.0 1.14.0 7.17.0 3.27.0 3.9 ${rootDirectory}/checks ${checks.location}/checkstyle.xml ${checks.location}/checkstyle-suppressions.xml ${checks.location}/checkstyle-header.txt ${checks.location}/spotbugs-exclude.xml ${checks.location}/pmd-ruleset.xml com.ongres.scram scram-common ${project.version} com.ongres.scram scram-client ${project.version} com.ongres.stringprep saslprep ${saslprep.version} org.junit junit-bom ${junit5.version} pom import org.junit.jupiter junit-jupiter test org.jetbrains annotations ${jetbrains-annotations.version} provided true org.apache.maven.plugins maven-compiler-plugin ${compiler-plugin.version} true true true java9-module compile none 9 ${project.basedir}/src/main/java9 true default-testCompile testCompile 11 org.apache.maven.plugins maven-jar-plugin ${jar-plugin.version} true true org.apache.maven.plugins maven-source-plugin ${source-plugin.version} attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin ${javadoc-plugin.version} en_US true false ${rootDirectory}/scram-parent/javadoc true ${project.groupId}:* true false com.ongres.scram.common.util style.css apiNote a API Note: implSpec a Implementation Requirements: implNote a Implementation Note: attach-javadocs jar org.apache.maven.plugins maven-resources-plugin ${resources-plugin.version} add-license copy-resources generate-resources ${project.build.outputDirectory}/META-INF ${rootDirectory} LICENSE false org.apache.maven.plugins maven-invoker-plugin ${invoker-plugin.version} false ${project.build.directory}/it */pom.xml ${project.build.directory}/local-repo src/it/settings.xml integration-test install integration-test verify org.apache.maven.plugins maven-surefire-plugin ${surefire-plugin.version} org.apache.maven.plugins maven-failsafe-plugin ${failsafe-plugin.version} **/*Test.java **/*IT.java ${project.build.directory}/${project.build.finalName}.jar integration-test verify org.apache.maven.plugins maven-install-plugin ${install-plugin.version} org.sonatype.central central-publishing-maven-plugin ${central-publishing-maven-plugin.version} true central org.apache.maven.plugins maven-clean-plugin ${clean-plugin.version} org.apache.maven.plugins maven-enforcer-plugin ${enforcer-plugin.version} enforce-versions enforce [3.9.9,) [21,) org.jacoco jacoco-maven-plugin ${jacoco-plugin.verson} prepare-agent prepare-agent process-test-classes org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} ossrh all flatten flatten process-resources flatten-clean clean clean org.apache.maven.plugins maven-gpg-plugin ${gpg-plugin.version} sign-artifacts sign verify org.cyclonedx cyclonedx-maven-plugin ${cyclonedx-plugin.version} true false false false false makeAggregateBom org.apache.maven.plugins maven-enforcer-plugin checks org.apache.maven.plugins maven-compiler-plugin ${compiler-plugin.version} true true -Xlint:all -XDcompilePolicy=simple --should-stop=ifError=FLOW -Xplugin:ErrorProne -XepAllErrorsAsWarnings -XepDisableWarningsInGeneratedCode -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED com.google.errorprone error_prone_core ${errorprone.version} de.thetaphi forbiddenapis ${forbiddenapis.version} jdk-unsafe jdk-deprecated jdk-internal jdk-non-portable jdk-reflection jdk-system-out ${checks.location}/forbiddenapis.txt check testCheck org.apache.maven.plugins maven-jdeps-plugin ${jdeps-plugin.version} 9 jdkinternals org.apache.maven.plugins maven-checkstyle-plugin ${checkstyle-plugin.version} com.puppycrawl.tools checkstyle ${checkstyle.version} style check verify error true true true false true false com.github.spotbugs spotbugs-maven-plugin ${spotbugs-plugin.version} Max Low true true true com.h3xstream.findsecbugs findsecbugs-plugin ${findsecbugs.version} com.github.spotbugs spotbugs ${spotbugs.version} scan check verify org.apache.maven.plugins maven-pmd-plugin ${pmd-plugin.version} 5 true true false ${pmd.ruleset} net.sourceforge.pmd pmd-core ${pmd.version} net.sourceforge.pmd pmd-java ${pmd.version} pmd-scan check verify com.github.ekryd.sortpom sortpom-maven-plugin ${sortpom-plugin.version} sort verify release org.codehaus.mojo flatten-maven-plugin org.apache.maven.plugins maven-source-plugin org.apache.maven.plugins maven-javadoc-plugin org.apache.maven.plugins maven-gpg-plugin org.cyclonedx cyclonedx-maven-plugin org.sonatype.central central-publishing-maven-plugin compile-java9 [9,) ${base.java.version} org.apache.maven.plugins maven-compiler-plugin java9-module compile