diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 000000000..f6b142872 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,29 @@ +# 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. + +github: + description: "Apache Commons Crypto" + homepage: https://commons.apache.org/crypto/ + +notifications: + commits: commits@commons.apache.org + issues: issues@commons.apache.org + pullrequests: issues@commons.apache.org + jira_options: link label + jobs: notifications@commons.apache.org + issues_bot_dependabot: notifications@commons.apache.org + pullrequests_bot_dependabot: notifications@commons.apache.org + issues_bot_codecov-commenter: notifications@commons.apache.org + pullrequests_bot_codecov-commenter: notifications@commons.apache.org diff --git a/.github/GH-ROBOTS.txt b/.github/GH-ROBOTS.txt new file mode 100644 index 000000000..e3329e55f --- /dev/null +++ b/.github/GH-ROBOTS.txt @@ -0,0 +1,19 @@ +# 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. + +# Keeps on creating FUD PRs in test code +# Does not follow Apache disclosure policies +User-agent: JLLeitschuh/security-research +Disallow: * diff --git a/.github/workflows/adhoctest.yml b/.github/workflows/adhoctest.yml new file mode 100644 index 000000000..f029543d4 --- /dev/null +++ b/.github/workflows/adhoctest.yml @@ -0,0 +1,113 @@ +# 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. + +name: Java AdHoc + +# Check for openssl and crypto installs + +on: + # allow direct trigger + workflow_dispatch: + # and self-trigger + push: + paths: + - '**/workflows/adhoctest.yml' + +permissions: + contents: read + +jobs: + build: + + runs-on: ${{ matrix.os }} + # env: + # LD_LIBRARY_PATH: "/usr/local" + # DYLD_LIBRARY_PATH: "/usr/local" + # ZLD_LIBRARY_PATH: "/usr/local" + # ZDYLD_LIBRARY_PATH: "/usr/local" + # DYLD: "/usr/local" + # DYLD_: "/usr/local" + # commons.crypto.debug: true + strategy: + matrix: + # os: [macos-latest] + os: [windows-latest] + java: [ 8 ] + # ref: [ 'rel/commons-crypto-1.1.0', master ] + ref: [ master ] + fail-fast: false + + steps: + # - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + # with: + # persist-credentials: false + # ref: ${{ matrix.ref }} + # - name: Set up JDK ${{ matrix.java }} + # uses: actions/setup-java@v3.5.1 + # with: + # distribution: 'temurin' + # java-version: ${{ matrix.java }} + - name: OpenSSL version + run: openssl version -a + - name: Find libcrypto + run: | + dir -s 'C:\Program Files\' libcrypto.dll + # - name: Compile with Maven + # env: + # OPENSSL_HOME: "C:\\Miniconda\\Library" + # run: mvn -V compile -D"rat.skip" -D"animal.sniffer.skip" --no-transfer-progress -DtrimStackTrace=false + # - name: Run sample Crypto + # run: | + # mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.Crypto" -D"commons.crypto.debug=true" + # - name: Run sample OpenSslJna (default library) + # if: always() + # run: | + # mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.jna.OpenSslJna" -D"commons.crypto.debug=true" + # # - name: Run sample OpenSslJna + # # if: always() + # # run: | + # # mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.jna.OpenSslJna" -D"jna.library.path=C:/Miniconda/Library/bin" + # - name: Run sample OpenSslJna (miniconda lib) + # if: always() + # run: | + # mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.jna.OpenSslJna" -D"jna.library.path=/usr/local/miniconda/lib" -D"commons.crypto.debug=true" + # - name: Run sample OpenSslJna (Cellar lib from openssl version -a) + # if: always() + # run: | + # mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.jna.OpenSslJna" -D"jna.library.path=/usr/local/Cellar/openssl@1.1/1.1.1v/lib" -D"commons.crypto.debug=true" + # # - name: Find OpenSSL Mac + # # if: ${{ matrix.os == 'macos-latest' }} + # # run: | + # # for i in $(which -a openssl) ; do echo $i; $i version ; echo ""; done + # # set -v + # # find /usr/lib -name libcrypto*.dylib -ls || true + # # find /usr/local -name libcrypto*.dylib -ls || true + # # find /opt/local/lib -name libcrypto*.dylib -ls || true + # # - name: Find OpenSSL Win + # # if: ${{ matrix.os == 'windows-latest' }} + # # run: | + # # where /T openssl + # # echo "===" + # # where /T libcrypto.dll + # # echo "===" + # # dir "C:\Program Files\OpenSSL\bin\" + # # C: + # # cd \ + # # dir /s libcrypto*.dll + # # shell: cmd + # - name: env sort + # if: always() + # run: | + # env | sort diff --git a/.github/workflows/benchmarkadhoc.yml b/.github/workflows/benchmarkadhoc.yml new file mode 100644 index 000000000..541749f26 --- /dev/null +++ b/.github/workflows/benchmarkadhoc.yml @@ -0,0 +1,68 @@ +# 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. + +name: Benchmark adhoc + +on: + # allow direct trigger + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + # macos-latest and ubuntu-latest uses OpenSSL 3 which breaks tests + os: [macos-11, ubuntu-20.04, windows-latest] + # Run lowest and highest Java versions only + java: [ 8, 21 ] + experimental: [false] + fail-fast: false + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: OpenSSL version + run: openssl version -a + # - name: Build with Maven + # # OPENSSL_HOME is needed for Windows build; not used by other builds so can set unconditionally + # # It's not clear how one is supposed to find the correct setting; + # # The value below was found by searching for openssl files under C (warning: slow) + # # Other possible values are: + # # "C:\\Miniconda\\pkgs\\openssl-1.1.1n-h2bbff1b_0\\Library" + # # "C:\\ProgramData\\chocolatey\\lib\\mingw\\tools\\install\\mingw64\\opt" + # env: + # OPENSSL_HOME: "C:\\Miniconda\\Library" + # run: mvn --show-version --batch-mode --no-transfer-progress -DtrimStackTrace=false clean test-compile -Pbenchmark + # # will fail on Windows... + - name: Host details + run: uname -a diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fb0db3365..60d4f847d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,18 +16,27 @@ name: "CodeQL" on: + # allow direct trigger + workflow_dispatch: push: + paths-ignore: + - '**/workflows/*.yml' branches: [ master ] pull_request: + paths-ignore: + - '**/workflows/*.yml' # The branches below must be a subset of the branches above branches: [ master ] - schedule: - - cron: '33 9 * * 4' + # schedule: + # - cron: '33 9 * * 4' + +permissions: + contents: read jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 permissions: actions: read contents: read @@ -37,16 +46,35 @@ jobs: fail-fast: false matrix: language: [ 'cpp', 'java' ] + java: [ 8 ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Override Java default + # Java 11 complains about illegal access; drop this override when sorted + # Also complains: "Corrupted channel by directly writing to native stream in forked JVM 1" + - name: Set up JDK ${{ matrix.java }} + if: ${{ matrix.language == 'java' }} + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -67,9 +95,10 @@ jobs: # uses a compiled language - name: Build with Maven - run: mvn package + # -DargLine=--add-opens=java.base/sun.nio.ch=ALL-UNNAMED (not with Java 8) + run: mvn -V package --no-transfer-progress -Drat.skip -Danimal.sniffer.skip # make bootstrap # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 68d62f6fb..d7d0bb402 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -15,7 +15,15 @@ name: Coverage -on: [push, pull_request] +on: + # allow direct trigger + workflow_dispatch: + push: + paths-ignore: + - '**/workflows/*.yml' + pull_request: + paths-ignore: + - '**/workflows/*.yml' permissions: contents: read @@ -23,28 +31,30 @@ permissions: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: java: [ 8 ] steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3.0.4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 with: - distribution: adopt + distribution: 'temurin' java-version: ${{ matrix.java }} - name: Build with Maven - run: mvn -V test jacoco:report --file pom.xml --no-transfer-progress + run: mvn --show-version --batch-mode --no-transfer-progress test jacoco:report - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 with: files: ./target/site/jacoco/jacoco.xml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index e3c6516eb..46b7bf842 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -15,7 +15,21 @@ name: Java CI -on: [push, pull_request] +# cross-build tests are done in a separate action as they currrently take a long time + +on: + # allow direct trigger + workflow_dispatch: + push: + paths-ignore: + - '**/workflows/*.yml' + - '!**/workflows/maven.yml' + pull_request: + paths-ignore: + - '**/workflows/*.yml' + +permissions: + contents: read jobs: build: @@ -24,34 +38,98 @@ jobs: continue-on-error: ${{ matrix.experimental }} strategy: matrix: -# os: [ubuntu-latest, windows-latest, macos-latest] - os: [macos-latest] - java: [ 8, 11, 17 ] + # macos-latest and ubuntu-latest uses OpenSSL 3 which breaks tests + os: [macos-11, ubuntu-20.04, windows-latest] + # These names are used in conditional statements below. + java: [ 8, 11, 17, 21 ] experimental: [false] + # macos-13-arm64 does not appear to be available + # include: + # - java: 21 + # os: macos-13-arm64 + # experimental: true # include: -# - java: 18-ea -# os: ubuntu-latest -# experimental: true -# - java: 18-ea +# - java: 22-ea +# os: ubuntu-20.04 +# experimental: true +# - java: 22-ea # os: windows-latest -# experimental: true -# - java: 18-ea +# experimental: true +# - java: 22-ea # os: macos-latest -# experimental: true +# experimental: true + # We don't need to build all Java versions every time + # Try excluding 11, 17 on branch pushes + exclude: + - java: ${{ (github.ref != 'refs/heads/master' && github.event_name == 'push') && 11 || 99 }} + - java: ${{ (github.ref != 'refs/heads/master' && github.event_name == 'push') && 17 || 99 }} fail-fast: false - + steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3.0.4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 with: distribution: 'temurin' java-version: ${{ matrix.java }} - - name: Build with Maven - run: mvn -V --file pom.xml --no-transfer-progress -DtrimStackTrace=false + - name: OpenSSL version + run: openssl version -a + - name: OpenSSL engine (macos) + # need to override the libarary on macOS + if: ${{ matrix.os == 'macos-11' }} + run: | + echo $(openssl version -e | sed -n -e 's/engines-.*//' -e 's/: "/=/p') >> "$GITHUB_ENV" + - name: OpenSSL engine (windows) + # need to override the libarary on windows + if: ${{ matrix.os == 'windows-latest' }} + # e.g. NAME: "libcrypto-1_1-x64.dll" + # Not sure how to derive this automatically + run: | + openssl version -a + echo "NAME=libcrypto-1_1-x64.dll" >> $env:GITHUB_ENV + # N.B. '-V -B -ntp' is shorthand for '--show-version --batch-mode --no-transfer-progress' + # + # The bash shell under Windows changes the openssl default library, so is not used for running tests + # Unfortunately that means separate steps for Windows, as it uses a different syntax for referrring to + # environment variables: $env:VARNAME instead of $VARNAME + # Also, note that Windows stores all the DLLs in the same directory. + # Instead of defining jni.library.path and jna.library.path we need to define + # jni.library.name and commons.crypto.OpenSslNativeJna to override the file names + - name: Build with Maven (Windows) + if: ${{ matrix.os == 'windows-latest' }} + # OPENSSL_HOME is needed for Windows build to find some header files + # It's not clear how one is supposed to find the correct setting; + # The value below was found by searching for openssl files under C (warning: slow) + # Other possible values are: + # "C:\\Miniconda\\pkgs\\openssl-1.1.1n-h2bbff1b_0\\Library" + # "C:\\ProgramData\\chocolatey\\lib\\mingw\\tools\\install\\mingw64\\opt" + # N.B. This must *not* be run under the bash shell, as that changes the default openssl library under Windows + env: + OPENSSL_HOME: "C:\\Miniconda\\Library" + run: | + mvn -V -B -ntp -DtrimStackTrace=false -D"jni.library.name=$env:NAME" -D"commons.crypto.OpenSslNativeJna=$env:NAME" + - name: Build with Maven (not Windows) + if: ${{ matrix.os != 'windows-latest' }} + run: | + mvn -V -B -ntp -DtrimStackTrace=false -D"jni.library.path=$ENGINESDIR" -D"jna.library.path=$ENGINESDIR" + - name: Check benchmark code compiles + if: ${{ matrix.java == '8' }} + env: + OPENSSL_HOME: "C:\\Miniconda\\Library" + run: | + mvn -V -B -ntp clean test-compile -Pbenchmark + - name: Check JNI and JNA tests are independent + # N.B. the default library fails with 'java is loading libcrypto in an unsafe way' + # so we need to define the appropriate library for each test + if: ${{ matrix.java == '8' && matrix.os != 'windows-latest' }} + run: | + mvn -V -B -ntp test -Ptestjni -D"jni.library.path=$ENGINESDIR" -Dcommons.crypto.OpenSslNativeJna=___ + mvn -V -B -ntp test -Ptestjna -D"jna.library.path=$ENGINESDIR" -Djni.library.name=___ diff --git a/.github/workflows/maven_adhoc.yml b/.github/workflows/maven_adhoc.yml new file mode 100644 index 000000000..c337004fd --- /dev/null +++ b/.github/workflows/maven_adhoc.yml @@ -0,0 +1,71 @@ +# 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. + +name: Maven adhoc + +on: + # allow direct trigger + workflow_dispatch: + # self-trigger + push: + paths: + - '**/maven_adhoc.yml' + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + # macos-latest and ubuntu-latest uses OpenSSL 3 which breaks tests + # os: [macos-11, ubuntu-20.04, windows-latest] + os: [ macos-latest, ubuntu-latest, windows-latest ] + # These names are used in conditional statements below. + # java: [ 8, 11, 17, 21 ] + java: [ 21 ] + experimental: [false] + + steps: + - name: OpenSSL version + run: openssl version -a + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build only + env: + OPENSSL_HOME: "C:\\Miniconda\\Library" + run: | + mvn clean test -B -V -ntp -DskipTests + - name: JNI test default + run: | + mvn -q exec:java -D"exec.mainClass=org.apache.commons.crypto.Crypto" -D"commons.crypto.debug=true" + - name: JNA test default + if: always() + run: | + mvn -q exec:java -D"jna.debug_load=true" -D"exec.mainClass=org.apache.commons.crypto.jna.OpenSslJna" -D"commons.crypto.debug=true" + - name: Maven test default + if: always() + run: | + mvn surefire:test -B -V -ntp -D"jna.debug_load=true" -DtrimStackTrace=false -D"commons.crypto.debug=true" diff --git a/.github/workflows/maven_crossbuild.yml b/.github/workflows/maven_crossbuild.yml new file mode 100644 index 000000000..8ee670096 --- /dev/null +++ b/.github/workflows/maven_crossbuild.yml @@ -0,0 +1,52 @@ +# 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. + +name: Java Cross Build + +# This is done as a separate action for now, as it takes a long time + +on: + # allow direct trigger + workflow_dispatch: + push: + paths: + - '**/native/**' + - '**/maven_crossbuild.yml' + +permissions: + contents: read + +jobs: + build-cross: + strategy: + matrix: + include: + - platform: aarch64 + - platform: riscv64 + jna_override: "-Djna.version=5.12.0" # See https://github.com/java-native-access/jna/issues/1557 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + with: + platforms: ${{ matrix.platform }} + - run: | + # Build package + docker compose -f src/docker/docker-compose.yaml run crypto src/docker/build.sh + # Run on platform + docker compose -f src/docker/docker-compose.yaml run crypto-${{ matrix.platform }} \ + mvn -V -B -ntp surefire:test ${{ matrix.jna_override }} diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml new file mode 100644 index 000000000..2c130e8ed --- /dev/null +++ b/.github/workflows/scorecards-analysis.yml @@ -0,0 +1,71 @@ +# 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. + +name: "Scorecards supply-chain security" + +on: + branch_protection_rule: + # schedule: + # - cron: "30 1 * * 6" # Weekly on Saturdays + push: + branches: [ "master" ] + paths-ignore: + - '**/workflows/*.yml' + +permissions: read-all + +jobs: + + analysis: + + name: "Scorecards analysis" + runs-on: ubuntu-20.04 + permissions: + # Needed to upload the results to the code-scanning dashboard. + security-events: write + actions: read + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + + steps: + + - name: "Checkout code" + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # 2.3.1 + with: + results_file: results.sarif + results_format: sarif + # A read-only PAT token, which is sufficient for the action to function. + # The relevant discussion: https://github.com/ossf/scorecard-action/issues/188 + repo_token: ${{ secrets.GITHUB_TOKEN }} + # Publish the results for public repositories to enable scorecard badges. + # For more details: https://github.com/ossf/scorecard-action#publishing-results + publish_results: true + + - name: "Upload artifact" + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # 2.22.5 + with: + sarif_file: results.sarif diff --git a/BUILDING.txt b/BUILDING.txt index c74ae6a3a..0cc1bdda8 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -1,3 +1,19 @@ + Build instructions for Apache Commons Crypto ---------------------------------------------------------------------------------- @@ -8,9 +24,9 @@ Requirements: * Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files (if running unit tests) * export JAVA_HOME -* Maven 3.0 or above +* Maven 3.3.9 or above * Make -* OpenSSL devel 1.1.1 or above (OpenSSL library header files are required) +* OpenSSL devel 1.1.1 (OpenSSL library header files are required) * GCC * G++ @@ -46,7 +62,7 @@ Check OpenSSL version: $ openssl version -If it is not 1.0.1c or above, upgrade OpenSSL version to 1.0.1c or above: +If it is not 1.1.1, upgrade OpenSSL version to 1.1.1: Upgrade OpenSSL in Linux: @@ -54,7 +70,7 @@ You can follow your OS distribution instructions to upgrade OpenSSL to a proper Upgrade OpenSSL in Mac: - $ brew install openssl101 + $ brew install openssl111 $ brew link openssl --force Get OpenSSL headers: sudo apt -y install libssl-dev @@ -90,4 +106,3 @@ Building distributions: Please read http://commons.apache.org/releases/index.html - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a5e3e052..75c4be000 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,7 @@ Making Changes -------------- + Create a _topic branch_ for your isolated work. - * Usually you should base your branch on the `master` or `trunk` branch. + * Usually you should base your branch on the `master` branch. * A good topic branch name can be the JIRA bug id plus a keyword, e.g. `CRYPTO-123-InputStream`. * If you have submitted multiple JIRA issues, try to maintain separate branches and pull requests. + Make commits of logical units. diff --git a/Makefile b/Makefile index 66064ba80..1a6ef9c76 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,11 @@ include Makefile.common COMMONS_CRYPTO_OUT:=$(TARGET)/$(commons-crypto)-$(os_arch) -COMMONS_CRYPTO_OBJ:=$(addprefix $(COMMONS_CRYPTO_OUT)/,OpenSslCryptoRandomNative.o OpenSslNative.o OpenSslInfoNative.o) +COMMONS_CRYPTO_OBJ:=$(addprefix $(COMMONS_CRYPTO_OUT)/,OpenSslCryptoRandomNative.o OpenSslNative.o OpenSslInfoNative.o DynamicLoader.o) + +# Shorthand for local dependencies +CRYPTO_H:=$(SRC_NATIVE)/org/apache/commons/crypto/org_apache_commons_crypto.h lib/include/config.h +CRYPTO_RANDOM_H:=$(SRC_NATIVE)/org/apache/commons/crypto/random/org_apache_commons_crypto_random.h # Windows uses different path separators ifeq ($(OS_NAME),Windows) @@ -35,29 +39,36 @@ endif NATIVE_TARGET_DIR:=$(TARGET)/classes/org/apache/commons/crypto/native/$(OS_NAME)/$(OS_ARCH) NATIVE_DLL:=$(NATIVE_TARGET_DIR)/$(LIBNAME) -all: $(NATIVE_DLL) +all: show $(NATIVE_DLL) + +show: + @echo "=== OS_NAME=$(OS_NAME) OS_ARCH=$(OS_ARCH) os_arch=$(os_arch) ===" -#$(TARGET)/jni-classes/org/apache/commons/crypto/cipher/OpenSslNative.h: $(TARGET)/classes/org/apache/commons/crypto/cipher/OpenSslNative.class -# $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.cipher.OpenSslNative +$(TARGET)/jni-classes/org/apache/commons/crypto/cipher/OpenSslNative.h: $(TARGET)/classes/org/apache/commons/crypto/cipher/OpenSslNative.class + $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.cipher.OpenSslNative -#$(TARGET)/jni-classes/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.h: $(TARGET)/classes/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.class -# $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.random.OpenSslCryptoRandomNative +$(TARGET)/jni-classes/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.h: $(TARGET)/classes/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.class + $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.random.OpenSslCryptoRandomNative -$#(TARGET)/jni-classes/org/apache/commons/crypto/OpenSslInfoNative.h: $(TARGET)/classes/org/apache/commons/crypto/OpenSslInfoNative.class -# $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.OpenSslInfoNative +$(TARGET)/jni-classes/org/apache/commons/crypto/OpenSslInfoNative.h: $(TARGET)/classes/org/apache/commons/crypto/OpenSslInfoNative.class + $(JAVAH) -force -classpath $(TARGET)/classes -o $@ org.apache.commons.crypto.OpenSslInfoNative -$(COMMONS_CRYPTO_OUT)/OpenSslNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/cipher/OpenSslNative.c $(TARGET)/jni-classes/org_apache_commons_crypto_cipher_OpenSslNative.h +$(COMMONS_CRYPTO_OUT)/OpenSslNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/cipher/OpenSslNative.c $(CRYPTO_H) $(TARGET)/jni-classes/org_apache_commons_crypto_cipher_OpenSslNative.h @mkdir -p $(@D) $(CC) $(CFLAGS) -c $< -o $@ -$(COMMONS_CRYPTO_OUT)/OpenSslCryptoRandomNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c $(TARGET)/jni-classes/org_apache_commons_crypto_random_OpenSslCryptoRandomNative.h +$(COMMONS_CRYPTO_OUT)/OpenSslCryptoRandomNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c $(CRYPTO_H) $(CRYPTO_RANDOM_H) $(TARGET)/jni-classes/org_apache_commons_crypto_random_OpenSslCryptoRandomNative.h @mkdir -p $(@D) $(CC) $(CFLAGS) -c $< -o $@ -$(COMMONS_CRYPTO_OUT)/OpenSslInfoNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/OpenSslInfoNative.c $(TARGET)/jni-classes/org_apache_commons_crypto_OpenSslInfoNative.h +$(COMMONS_CRYPTO_OUT)/OpenSslInfoNative.o : $(SRC_NATIVE)/org/apache/commons/crypto/OpenSslInfoNative.c $(CRYPTO_H) $(TARGET)/jni-classes/org_apache_commons_crypto_OpenSslInfoNative.h @mkdir -p $(@D) $(CC) $(CFLAGS) -DVERSION='"$(VERSION)"' -DPROJECT_NAME='"$(PROJECT_NAME)"' -I"$(TARGET)/jni-classes" -c $< -o $@ +$(COMMONS_CRYPTO_OUT)/DynamicLoader.o : $(SRC_NATIVE)/org/apache/commons/crypto/DynamicLoader.c $(CRYPTO_H) + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + $(COMMONS_CRYPTO_OUT)/$(LIBNAME): $(COMMONS_CRYPTO_OBJ) $(CXX) $(CXXFLAGS) -o $@ $+ $(LINKFLAGS) $(STRIP) $@ @@ -66,14 +77,14 @@ clean: $(DELTREE) $(subst /,$(FSEP),$(TARGET)/jni-classes) $(DELTREE) $(subst /,$(FSEP),$(COMMONS_CRYPTO_OUT)) -native: $(NATIVE_DLL) +native: show $(NATIVE_DLL) $(NATIVE_DLL): $(COMMONS_CRYPTO_OUT)/$(LIBNAME) @mkdir -p $(@D) cp $< $@ - @mkdir -p $(NATIVE_TARGET_DIR) - cp $< $(NATIVE_TARGET_DIR)/$(LIBNAME) +# These targets should correspond with the entries in the list 'known_os_archs' defined in Makefile.common +# e.g. linux32 corresponds with Linux-x86 win32: $(MAKE) native CROSS_PREFIX=i686-w64-mingw32- OS_NAME=Windows OS_ARCH=x86 @@ -87,6 +98,12 @@ mac32: mac64: $(MAKE) native OS_NAME=Mac OS_ARCH=x86_64 +macArm64: + $(MAKE) native OS_NAME=Mac OS_ARCH=arm64 + +mac-aarch64: + $(MAKE) native OS_NAME=Mac OS_ARCH=aarch64 + linux32: $(MAKE) native OS_NAME=Linux OS_ARCH=x86 @@ -108,8 +125,25 @@ linux-armhf: linux-aarch64: $(MAKE) native CROSS_PREFIX=aarch64-linux-gnu- OS_NAME=Linux OS_ARCH=aarch64 -clean-native-linux32: - $(MAKE) clean-native OS_NAME=Linux OS_ARCH=x86 +# for cross-compilation on Ubuntu, install the g++-riscv64-linux-gnu +linux-riscv64: + $(MAKE) native CROSS_PREFIX=riscv64-linux-gnu- OS_NAME=Linux OS_ARCH=riscv64 + +linux-ppc: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=Linux OS_ARCH=ppc + +linux-ppc64: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=Linux OS_ARCH=ppc64 + +sunos32: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=SunOS OS_ARCH=x86 + +sunos64: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=SunOS OS_ARCH=x86_64 + +sunos-sparc: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=SunOS OS_ARCH=sparc + +aix-ppc64: # TODO: Untested; may need additional CROSS_PREFIX define + $(MAKE) native OS_NAME=AIX OS_ARCH=ppc64 -clean-native-win32: - $(MAKE) clean-native OS_NAME=Windows OS_ARCH=x86 diff --git a/Makefile.common b/Makefile.common index eb866d3e8..449927487 100644 --- a/Makefile.common +++ b/Makefile.common @@ -51,9 +51,16 @@ jni_include := $(shell dirname "$(jni_md)") endif -# os=Default is meant to be generic unix/linux - -known_os_archs := Linux-x86 Linux-x86_64 Linux-aarch64 Linux-arm Linux-armhf Linux-ppc Linux-ppc64 Mac-x86 Mac-x86_64 FreeBSD-x86_64 Windows-x86 Windows-x86_64 SunOS-x86 SunOS-sparc SunOS-x86_64 AIX-ppc64 +# os=Default is meant to be generic Unix/Linux +# The following list must include all OS entries below (apart from Default) +# Also there should be a target in the makefile for each of the combinations +# For example, 'Linux-x86' is invoked by the target 'linux32' +known_os_archs := Linux-x86 Linux-x86_64 Linux-aarch64 Linux-riscv64 Linux-arm Linux-armhf Linux-ppc Linux-ppc64 \ + Mac-x86 Mac-x86_64 Mac-arm64 Mac-aarch64 \ + FreeBSD-x86_64 \ + Windows-x86 Windows-x86_64 \ + SunOS-x86 SunOS-sparc SunOS-x86_64 \ + AIX-ppc64 os_arch := $(OS_NAME)-$(OS_ARCH) ifeq (,$(findstring $(strip $(os_arch)),$(known_os_archs))) @@ -65,67 +72,74 @@ os_folder := $(shell echo $(OS_NAME) | tr A-Z a-z) # cross-compilation toolchain prefix (e.g. "arm-linux-gnueabi-") CROSS_PREFIX := -Default_CC := $(CROSS_PREFIX)gcc -Default_CXX := $(CROSS_PREFIX)g++ -Default_STRIP := $(CROSS_PREFIX)strip -Default_CFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -Default_CXXFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -Default_LINKFLAGS := -shared -static -Default_LIBNAME := libcommons-crypto.so +Default_CC := $(CROSS_PREFIX)gcc +Default_CXX := $(CROSS_PREFIX)g++ +Default_STRIP := $(CROSS_PREFIX)strip +Default_CFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden +Default_CXXFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden +Default_LINKFLAGS := -shared -static +Default_LIBNAME := libcommons-crypto.so +Default_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Default_COMMONS_CRYPTO_FLAGS := -Linux-x86_CC := $(CROSS_PREFIX)gcc -Linux-x86_CXX := $(CROSS_PREFIX)g++ -Linux-x86_STRIP := $(CROSS_PREFIX)strip -Linux-x86_CXXFLAGS := -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -m32 -Linux-x86_CFLAGS := -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -m32 -Linux-x86_LINKFLAGS := -shared -static-libgcc -static-libstdc++ -Linux-x86_LIBNAME := libcommons-crypto.so +Linux-x86_CC := $(CROSS_PREFIX)gcc +Linux-x86_CXX := $(CROSS_PREFIX)g++ +Linux-x86_STRIP := $(CROSS_PREFIX)strip +Linux-x86_CXXFLAGS := -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -m32 +Linux-x86_CFLAGS := -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -m32 +Linux-x86_LINKFLAGS := -shared -static-libgcc -static-libstdc++ +Linux-x86_LIBNAME := libcommons-crypto.so +Linux-x86_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-x86_COMMONS_CRYPTO_FLAGS:= -Linux-x86_64_CC := $(CROSS_PREFIX)gcc -Linux-x86_64_CXX := $(CROSS_PREFIX)g++ -Linux-x86_64_STRIP := $(CROSS_PREFIX)strip -Linux-x86_64_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Wall -Werror -Linux-x86_64_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Wall -Werror -Linux-x86_64_LINKFLAGS := -shared -static-libgcc -Linux-x86_64_LIBNAME := libcommons-crypto.so +Linux-x86_64_CC := $(CROSS_PREFIX)gcc +Linux-x86_64_CXX := $(CROSS_PREFIX)g++ +Linux-x86_64_STRIP := $(CROSS_PREFIX)strip +Linux-x86_64_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Wall -Werror +Linux-x86_64_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Wall -Werror +Linux-x86_64_LINKFLAGS := -shared -static-libgcc +Linux-x86_64_LIBNAME := libcommons-crypto.so +Linux-x86_64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-x86_64_COMMONS_CRYPTO_FLAGS := -Linux-ppc_CC := gcc -Linux-ppc_CXX := g++ -Linux-ppc_STRIP := strip -Linux-ppc_CXXFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m32 -Linux-ppc_CFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m32 -Linux-ppc_LINKFLAGS := -shared -static-libgcc -static-libstdc++ -Linux-ppc_LIBNAME := libcommons-crypto.so +Linux-ppc_CC := gcc +Linux-ppc_CXX := g++ +Linux-ppc_STRIP := strip +Linux-ppc_CXXFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m32 +Linux-ppc_CFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m32 +Linux-ppc_LINKFLAGS := -shared -static-libgcc -static-libstdc++ +Linux-ppc_LIBNAME := libcommons-crypto.so +Linux-ppc_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-ppc_COMMONS_CRYPTO_FLAGS := -Linux-ppc64_CC := gcc -Linux-ppc64_CXX := g++ -Linux-ppc64_STRIP := strip -Linux-ppc64_CXXFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Linux-ppc64_CFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Linux-ppc64_LINKFLAGS := -shared -static-libgcc -static-libstdc++ -Linux-ppc64_LIBNAME := libcommons-crypto.so +Linux-ppc64_CC := gcc +Linux-ppc64_CXX := g++ +Linux-ppc64_STRIP := strip +Linux-ppc64_CXXFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 +Linux-ppc64_CFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 +Linux-ppc64_LINKFLAGS := -shared -static-libgcc -static-libstdc++ +Linux-ppc64_LIBNAME := libcommons-crypto.so +Linux-ppc64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-ppc64_COMMONS_CRYPTO_FLAGS := AIX-ppc64_CC := gcc AIX-ppc64_CXX := g++ AIX-ppc64_STRIP := strip -X64 -AIX-ppc64_LIBNAME := libcommons-crypto.a AIX-ppc64_CXXFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 AIX-ppc64_CFLAGS := -DHAVE_CONFIG_H -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 AIX-ppc64_LINKFLAGS := -shared -static-libgcc -static-libstdc++ -lcrypt +AIX-ppc64_LIBNAME := libcommons-crypto.a +# TODO: AIX-ppc64_LIBNAME_OSSL3 ? AIX-ppc64_COMMONS_CRYPTO_FLAGS := -SunOS-x86_CC := gcc -SunOS-x86_CXX := g++ -SunOS-x86_STRIP := strip -SunOS-x86_CFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -SunOS-x86_CXXFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -SunOS-x86_LINKFLAGS := -shared -static-libgcc -static-libstdc++ -SunOS-x86_LIBNAME := libcommons-crypto.so +SunOS-x86_CC := gcc +SunOS-x86_CXX := g++ +SunOS-x86_STRIP := strip +SunOS-x86_CFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden +SunOS-x86_CXXFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden +SunOS-x86_LINKFLAGS := -shared -static-libgcc -static-libstdc++ +SunOS-x86_LIBNAME := libcommons-crypto.so +SunOS-x86_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so SunOS-x86_COMMONS_CRYPTO_FLAGS := SunOS-sparc_CC := gcc @@ -135,6 +149,7 @@ SunOS-sparc_CFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include SunOS-sparc_CXXFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden SunOS-sparc_LINKFLAGS := -shared -static-libgcc -static-libstdc++ SunOS-sparc_LIBNAME := libcommons-crypto.so +SunOS-sparc_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so SunOS-sparc_COMMONS_CRYPTO_FLAGS := SunOS-x86_64_CC := gcc @@ -144,6 +159,7 @@ SunOS-x86_64_CFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/includ SunOS-x86_64_CXXFLAGS := -include lib/inc_linux/jni_md.h -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -m64 SunOS-x86_64_LINKFLAGS := -shared -static-libgcc -static-libstdc++ SunOS-x86_64_LIBNAME := libcommons-crypto.so +SunOS-x86_64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so SunOS-x86_64_COMMONS_CRYPTO_FLAGS := # '-include lib/inc_linux/jni_md.h' is used to force the use of our version, @@ -154,10 +170,11 @@ SunOS-x86_64_COMMONS_CRYPTO_FLAGS := Linux-arm_CC := $(CROSS_PREFIX)gcc Linux-arm_CXX := $(CROSS_PREFIX)g++ Linux-arm_STRIP := $(CROSS_PREFIX)strip -Linux-arm_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)include" -O2 -fPIC -fvisibility=hidden -mfloat-abi=softfp -Linux-arm_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)include" -O2 -fPIC -fvisibility=hidden -mfloat-abi=softfp +Linux-arm_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -mfloat-abi=softfp +Linux-arm_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -mfloat-abi=softfp Linux-arm_LINKFLAGS := -shared -static-libgcc Linux-arm_LIBNAME := libcommons-crypto.so +Linux-arm_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-arm_COMMONS_CRYPTO_FLAGS:= Linux-armhf_CC := $(CROSS_PREFIX)gcc @@ -167,6 +184,7 @@ Linux-armhf_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -O2 -fPIC -fvi Linux-armhf_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -O2 -fPIC -fvisibility=hidden -mfloat-abi=hard Linux-armhf_LINKFLAGS := -shared -static-libgcc Linux-armhf_LIBNAME := libcommons-crypto.so +Linux-armhf_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-armhf_COMMONS_CRYPTO_FLAGS:= Linux-aarch64_CC := $(CROSS_PREFIX)gcc @@ -176,26 +194,63 @@ Linux-aarch64_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_ma Linux-aarch64_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -Wall -Werror Linux-aarch64_LINKFLAGS := -shared -static-libgcc Linux-aarch64_LIBNAME := libcommons-crypto.so +Linux-aarch64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so Linux-aarch64_COMMONS_CRYPTO_FLAGS := +Linux-riscv64_CC := $(CROSS_PREFIX)gcc +Linux-riscv64_CXX := $(CROSS_PREFIX)g++ +Linux-riscv64_STRIP := $(CROSS_PREFIX)strip +Linux-riscv64_CXXFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -Wall -Werror +Linux-riscv64_CFLAGS := -Ilib/inc_linux -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -Wall -Werror +Linux-riscv64_LINKFLAGS := -shared -static-libgcc +Linux-riscv64_LIBNAME := libcommons-crypto.so +Linux-riscv64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so +Linux-riscv64_COMMONS_CRYPTO_FLAGS := + +ifndef Mac_INC_OPENSSL +Mac_INC_OPENSSL := /usr/local/opt/openssl/include +endif + Mac-x86_CC := gcc -arch i386 Mac-x86_CXX := g++ -arch i386 Mac-x86_STRIP := strip -x -Mac-x86_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.4 -fvisibility=hidden -I/usr/local/include -I/usr/local/opt/openssl/include -Mac-x86_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.4 -fvisibility=hidden -I/usr/local/include -I/usr/local/opt/openssl/include +Mac-x86_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.4 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-x86_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.4 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) Mac-x86_LINKFLAGS := -dynamiclib -static-libgcc -L/usr/local/lib Mac-x86_LIBNAME := libcommons-crypto.jnilib +Mac-x86_LIBNAME_OSSL3 := libcommons-crypto-ossl3.jnilib Mac-x86_COMMONS_CRYPTO_FLAGS := Mac-x86_64_CC := gcc -arch $(OS_ARCH) Mac-x86_64_CXX := gcc -arch $(OS_ARCH) Mac-x86_64_STRIP := strip -x -Mac-x86_64_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.7 -fvisibility=hidden -I/usr/local/include -I/usr/local/opt/openssl/include -Mac-x86_64_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.7 -fvisibility=hidden -I/usr/local/include -I/usr/local/opt/openssl/include +Mac-x86_64_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.7 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-x86_64_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=10.7 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) Mac-x86_64_LINKFLAGS := -dynamiclib -L/usr/local/lib Mac-x86_64_LIBNAME := libcommons-crypto.jnilib +Mac-x86_64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.jnilib Mac-x86_64_COMMONS_CRYPTO_FLAGS := +Mac-arm64_CC := gcc -arch $(OS_ARCH) +Mac-arm64_CXX := gcc -arch $(OS_ARCH) +Mac-arm64_STRIP := strip -x +Mac-arm64_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=11.0 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-arm64_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=11.0 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-arm64_LINKFLAGS := -dynamiclib -L/usr/local/lib +Mac-arm64_LIBNAME := libcommons-crypto.jnilib +Mac-arm64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.jnilib +Mac-arm64_COMMONS_CRYPTO_FLAGS := + +Mac-aarch64_CC := gcc -arch arm64 +Mac-aarch64_CXX := gcc -arch arm64 +Mac-aarch64_STRIP := strip -x +Mac-aarch64_CFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=11.0 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-aarch64_CXXFLAGS := -Ilib/inc_mac -I"$(JAVA_HOME)/include" -O2 -fPIC -mmacosx-version-min=11.0 -fvisibility=hidden -I/usr/local/include -I$(Mac_INC_OPENSSL) +Mac-aarch64_LINKFLAGS := -dynamiclib -L/usr/local/lib +Mac-aarch64_LIBNAME := libcommons-crypto.jnilib +Mac-aarch64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.jnilib +Mac-aarch64_COMMONS_CRYPTO_FLAGS := + FreeBSD-x86_64_CC := $(CROSS_PREFIX)gcc FreeBSD-x86_64_CXX := $(CROSS_PREFIX)g++ FreeBSD-x86_64_STRIP := $(CROSS_PREFIX)strip @@ -203,6 +258,7 @@ FreeBSD-x86_64_CFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC FreeBSD-x86_64_CXXFLAGS := -I"$(JAVA_HOME)/include" -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden FreeBSD-x86_64_LINKFLAGS := -shared -static-libgcc FreeBSD-x86_64_LIBNAME := libcommons-crypto.so +FreeBSD-x86_64_LIBNAME_OSSL3 := libcommons-crypto-ossl3.so FreeBSD-x86_64_COMMONS_CRYPTO_FLAGS := Windows-x86_CC := $(CROSS_PREFIX)gcc @@ -212,6 +268,7 @@ Windows-x86_CFLAGS := -I/usr/share/mingw-w64/include -I"$(JAVA_HOME)/inclu Windows-x86_CXXFLAGS := -I/usr/share/mingw-w64/include -I"$(JAVA_HOME)/include" -I"$(OPENSSL_HOME)/include" -Ilib/inc_win -O2 -fno-inline Windows-x86_LINKFLAGS := -Wl,--kill-at -shared -static Windows-x86_LIBNAME := commons-crypto.dll +Windows-x86_LIBNAME_OSSL3 := commons-crypto-ossl3.dll Windows-x86_COMMONS_CRYPTO_FLAGS := Windows-x86_64_CC := $(CROSS_PREFIX)gcc @@ -221,6 +278,7 @@ Windows-x86_64_CFLAGS := -I/usr/share/mingw-w64/include -I"$(JAVA_HOME)/in Windows-x86_64_CXXFLAGS := -I/usr/share/mingw-w64/include -I"$(JAVA_HOME)/include" -I"$(OPENSSL_HOME)/include" -Ilib/inc_win -O2 -fno-inline Windows-x86_64_LINKFLAGS := -Wl,--kill-at -shared -static Windows-x86_64_LIBNAME := commons-crypto.dll +Windows-x86_64_LIBNAME_OSSL3 := commons-crypto-ossl3.dll Windows-x86_64_COMMONS-CRYPTO_FLAGS := @@ -232,6 +290,7 @@ CFLAGS := $($(os_arch)_CFLAGS) CXXFLAGS := $($(os_arch)_CXXFLAGS) LINKFLAGS := $($(os_arch)_LINKFLAGS) LIBNAME := $($(os_arch)_LIBNAME) +LIBNAME_OSSL3 := $($(os_arch)_LIBNAME_OSSL3) COMMONS-CRYPTO_FLAGS := $($(os_arch)_COMMONS-CRYPTO_FLAGS) diff --git a/NOTICE.txt b/NOTICE.txt index dbc46b5e0..5395df2d9 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Commons Crypto -Copyright 2016-2022 The Apache Software Foundation +Copyright 2016-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). diff --git a/PROPOSAL.html b/PROPOSAL.html index 6d374ffff..eb0ef7fe4 100644 --- a/PROPOSAL.html +++ b/PROPOSAL.html @@ -40,81 +40,81 @@
- -Providing Java based optimized and high performance cryptographic IO streams for -the applications who wants to implement the data encryption. It also provides cipher -level API to use. It does provide the openssl API integration and provide the fallback + +
Providing Java based optimized and high performance cryptographic IO streams for +the applications that want to implement the data encryption. It also provides cipher +level API to use. It does provide the openssl API integration and provide the fallback mechanism to use JCE when openssl library unavailable.
-(Note: Please note that Commons Crypto doesn’t +
(Note: Please note that Commons Crypto doesn’t -implement the cryptographic algorithm such as AES directly. It wraps to Openssl or JCE +implement the cryptographic algorithm such as AES directly. It wraps to OpenSSL or JCE which implement algorithms.)
- +This proposal is to create a package of cryptographic IO classes with the integration -of Openssl library.
+This proposal is to create a package of cryptographic IO classes with the integration + +of OpenSSL library.
-It focuses on AES-NI optimizations mainly and it can be extended to other algorithms +
It focuses on AES-NI optimizations mainly, and it can be extended to other algorithms based on demand from the users later.
- +IO Commons Crypto relies on standard JDK 7 (or later) APIs for production -deployment and on OpenSSL 1.0.1c devl libraries. It utilizes the JUnit unit testing +
IO Commons Crypto relies on standard JDK 7 (or later) APIs for production + +deployment and on OpenSSL 1.0.1c devl libraries. It utilizes the JUnit unit testing framework, but this is of interest only to developers of the component. - The functionality provided by Commons Crypto is currently in use by Apache Hadoop - - and Apache Spark, and both of those communities have expressed interest in changing - + The functionality provided by Commons Crypto is currently in use by Apache Hadoop + + and Apache Spark, and both of those communities have expressed interest in changing + their dependency to be on the central Commons Crypto package once it exists.
- +The initial classes came from the Apache Hadoop.
- +The proposed package name for the new component is org.apache.commons.crypto
.
@@ -87,7 +90,7 @@ private static Properties getComponentProperties() { * by Maven. *
* - * @return the version; may be null if not found + * @return the version; may be {@code null} if not found */ public static String getComponentName() { // Note: the component properties file allows the method to work without needing @@ -103,7 +106,7 @@ public static String getComponentName() { * by Maven. * * - * @return the version; may be null if not found + * @return the version; may be {@code null} if not found */ public static String getComponentVersion() { // Note: the component properties file allows the method to work without needing @@ -114,7 +117,7 @@ public static String getComponentVersion() { /** * The loading error throwable, if loading failed. * - * @return null, unless loading failed. + * @return {@code null}, unless loading failed. */ public static Throwable getLoadingError() { return NativeCodeLoader.getLoadingError(); @@ -127,14 +130,15 @@ public static Throwable getLoadingError() { * @param args See {@link String#format(String, Object...)}. */ private static void info(final String format, final Object... args) { - // TODO Find a better way to do this later. - System.out.println(String.format(format, args)); + if (!quiet) { // suppress output for testing + System.out.println(String.format(format, args)); + } } /** * Checks whether the native code has been successfully loaded for the platform. * - * @return true if the native code has been loaded successfully. + * @return {@code true} if the native code has been loaded successfully. */ public static boolean isNativeCodeLoaded() { return NativeCodeLoader.isNativeCodeLoaded(); @@ -147,6 +151,9 @@ public static boolean isNativeCodeLoaded() { * @throws Exception if getCryptoRandom or getCryptoCipher get error. */ public static void main(final String[] args) throws Exception { + quiet = args.length == 1 && args[0].equals("-q"); + info("jni.library.path=%s", System.getProperty("jni.library.path")); + info("jni.library.name=%s", System.getProperty("jni.library.name")); info("%s %s", getComponentName(), getComponentVersion()); if (isNativeCodeLoaded()) { info("Native code loaded OK: %s", OpenSslInfoNative.NativeVersion()); @@ -154,30 +161,51 @@ public static void main(final String[] args) throws Exception { info("Native built: %s", OpenSslInfoNative.NativeTimeStamp()); info("OpenSSL library loaded OK, version: 0x%s", Long.toHexString(OpenSslInfoNative.OpenSSL())); info("OpenSSL library info: %s", OpenSslInfoNative.OpenSSLVersion(0)); - { // CryptoRandom + info("DLL name: %s", OpenSslInfoNative.DLLName()); + info("DLL path: %s", OpenSslInfoNative.DLLPath()); + info("Additional OpenSSL_version(n) details:"); + for (int j = 1; j < Utils.OPENSSL_VERSION_MAX_INDEX; j++) { // entry 0 is shown above + String data = OpenSslInfoNative.OpenSSLVersion(j); + if (!"not available".equals(data)) { + info("OpenSSLVersion(%d): %s", j, data); + } + } + try { // CryptoRandom final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, - CryptoRandomFactory.RandomProvider.OPENSSL.getClassName()); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, CryptoRandomFactory.RandomProvider.OPENSSL.getClassName()); try (CryptoRandom cryptoRandom = CryptoRandomFactory.getCryptoRandom(props)) { info("Random instance created OK: %s", cryptoRandom); } + } catch (Exception e) { + info("Failed: %s", e); } - { // CryptoCipher + try { // CryptoCipher final Properties props = new Properties(); - props.setProperty(CryptoCipherFactory.CLASSES_KEY, - CryptoCipherFactory.CipherProvider.OPENSSL.getClassName()); - final String transformation = "AES/CTR/NoPadding"; - try (CryptoCipher cryptoCipher = CryptoCipherFactory.getCryptoCipher(transformation, props)) { - info("Cipher %s instance created OK: %s", transformation, cryptoCipher); + props.setProperty(CryptoCipherFactory.CLASSES_KEY, CryptoCipherFactory.CipherProvider.OPENSSL.getClassName()); + try (CryptoCipher cryptoCipher = CryptoCipherFactory.getCryptoCipher(AES.CTR_NO_PADDING, props)) { + info("Cipher %s instance created OK: %s", AES.CTR_NO_PADDING, cryptoCipher); } - } - info("Additional OpenSSL_version(n) details:"); - for (int j = 1; j < 6; j++) { - info("%s: %s", j, OpenSslInfoNative.OpenSSLVersion(j)); + } catch (Exception e) { + info("Failed: %s", e); } } else { - info("Native load failed: %s", getLoadingError()); + Throwable error = getLoadingError(); + String msg = ""; + if (error != null) { + msg = error.getMessage(); + } + info("Native load failed: %s %s", error, msg); } } + /** + * Constructs a new instance. + * + * @deprecated Will be private in the next major release. + */ + @Deprecated + public Crypto() { + // empty + } + } diff --git a/src/main/java/org/apache/commons/crypto/NativeCodeLoader.java b/src/main/java/org/apache/commons/crypto/NativeCodeLoader.java index 64063a3c7..0f773145c 100644 --- a/src/main/java/org/apache/commons/crypto/NativeCodeLoader.java +++ b/src/main/java/org/apache/commons/crypto/NativeCodeLoader.java @@ -17,7 +17,6 @@ */ package org.apache.commons.crypto; -import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -25,11 +24,11 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFileAttributes; -import java.util.Objects; import java.util.Properties; import java.util.UUID; import org.apache.commons.crypto.utils.Utils; +import org.apache.commons.io.IOUtils; /** * A helper to load the native code i.e. libcommons-crypto.so. This handles the @@ -38,68 +37,22 @@ */ final class NativeCodeLoader { - /** - * End of file pseudo-character. - */ - private static final int EOF = -1; + private static final String SIMPLE_NAME = NativeCodeLoader.class.getSimpleName(); - private static final Throwable loadingError; + private static final String NATIVE_LIBNAME = "commons-crypto"; - private final static boolean nativeCodeLoaded; + private static final String NATIVE_LIBNAME_ALT = "lib" + NATIVE_LIBNAME + ".jnilib"; - static { - loadingError = loadLibrary(); // will be null if loaded OK - - nativeCodeLoaded = loadingError == null; - } + private static final Throwable libraryLoadingError; - /** - * Returns the given InputStream if it is already a {@link BufferedInputStream}, - * otherwise creates a BufferedInputStream from the given InputStream. - * - * @param inputStream the InputStream to wrap or return (not null) - * @return the given InputStream or a new {@link BufferedInputStream} for the - * given InputStream - * @throws NullPointerException if the input parameter is null - * @since Apache Commons IO 2.5 - */ - @SuppressWarnings("resource") - private static BufferedInputStream buffer(final InputStream inputStream) { - // reject null early on rather than waiting for IO operation to fail - // not checked by BufferedInputStream - Objects.requireNonNull(inputStream, "inputStream"); - return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream - : new BufferedInputStream(inputStream); - } + private static final boolean libraryLoaded; - /** - * Checks whether in1 and in2 is equal. - * - * @param input1 the input1. - * @param input2 the input2. - * @return true if in1 and in2 is equal, else false. - * @throws IOException if an I/O error occurs. - * @since Apache Commons IO 2.5 - */ - @SuppressWarnings("resource") - private static boolean contentsEquals(final InputStream input1, final InputStream input2) throws IOException { - if (input1 == input2) { - return true; - } - if (input1 == null ^ input2 == null) { - return false; - } - final BufferedInputStream bufferedInput1 = buffer(input1); - final BufferedInputStream bufferedInput2 = buffer(input2); - int ch = bufferedInput1.read(); - while (EOF != ch) { - final int ch2 = bufferedInput2.read(); - if (ch != ch2) { - return false; - } - ch = bufferedInput1.read(); - } - return bufferedInput2.read() == EOF; + static { + debug("%s static init start", SIMPLE_NAME); + libraryLoadingError = loadLibrary(); // will be null if loaded OK + libraryLoaded = libraryLoadingError == null; + debug("%s libraryLoaded = %s, libraryLoadingError = %s", SIMPLE_NAME, libraryLoaded, libraryLoadingError); + debug("%s static init end", SIMPLE_NAME); } /** @@ -129,7 +82,7 @@ private static void debug(final String format, final Object... args) { */ private static File extractLibraryFile(final String libFolderForCurrentOS, final String libraryFileName, final String targetFolder) { - final String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; + final String nativeLibraryFilePath = libFolderForCurrentOS + File.separator + libraryFileName; // Attach UUID to the native library file to ensure multiple class loaders // can read the libcommons-crypto multiple times. @@ -150,10 +103,14 @@ private static File extractLibraryFile(final String libFolderForCurrentOS, final if (isDebug()) { debug("Extracted '%s' to '%s': %,d bytes [%s]", nativeLibraryFilePath, extractedLibFile, byteCount, Files.isExecutable(path) ? "X+" : "X-"); - final PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class); - if (attributes != null) { - debug("Attributes '%s': %s %s %s", extractedLibFile, attributes.permissions(), - attributes.owner(), attributes.group()); + try { + final PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class); + if (attributes != null) { + debug("Attributes '%s': %s %s %s", extractedLibFile, attributes.permissions(), + attributes.owner(), attributes.group()); + } + } catch (final UnsupportedOperationException e) { + debug("Files.readAttributes failed on %s: %s", path, e.getMessage()); } } } finally { @@ -173,7 +130,7 @@ private static File extractLibraryFile(final String libFolderForCurrentOS, final try (InputStream nativeInputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) { try (InputStream extractedLibIn = Files.newInputStream(path)) { debug("Validating '%s'...", extractedLibFile); - if (!contentsEquals(nativeInputStream, extractedLibIn)) { + if (!IOUtils.contentEquals(nativeInputStream, extractedLibIn)) { throw new IllegalStateException(String.format("Failed to write a native library file %s to %s", nativeLibraryFilePath, extractedLibFile)); } @@ -197,25 +154,29 @@ private static File findNativeLibrary() { // Try to load the library in commons-crypto.lib.path */ String nativeLibraryPath = props.getProperty(Crypto.LIB_PATH_KEY); - String nativeLibraryName = props.getProperty(Crypto.LIB_NAME_KEY); + String nativeLibraryName = props.getProperty(Crypto.LIB_NAME_KEY, System.mapLibraryName(NATIVE_LIBNAME)); + + debug("%s nativeLibraryPath %s = %s", SIMPLE_NAME, Crypto.LIB_PATH_KEY, nativeLibraryPath); + debug("%s nativeLibraryName %s = %s", SIMPLE_NAME, Crypto.LIB_NAME_KEY, nativeLibraryName); - // Resolve the library file name with a suffix (e.g., dll, .so, etc.) - if (nativeLibraryName == null) { - nativeLibraryName = System.mapLibraryName("commons-crypto"); - } if (nativeLibraryPath != null) { final File nativeLib = new File(nativeLibraryPath, nativeLibraryName); - if (nativeLib.exists()) { + final boolean exists = nativeLib.exists(); + debug("%s nativeLib %s exists = %s", SIMPLE_NAME, nativeLib, exists); + if (exists) { return nativeLib; } } // Load an OS-dependent native library inside a jar file nativeLibraryPath = "/org/apache/commons/crypto/native/" + OsInfo.getNativeLibFolderPathForCurrentOS(); - boolean hasNativeLib = hasResource(nativeLibraryPath + "/" + nativeLibraryName); + debug("%s nativeLibraryPath = %s", SIMPLE_NAME, nativeLibraryPath); + final String resource = nativeLibraryPath + File.separator + nativeLibraryName; + boolean hasNativeLib = hasResource(resource); + debug("%s resource %s exists = %s", SIMPLE_NAME, resource, hasNativeLib); if (!hasNativeLib) { - final String altName = "libcommons-crypto.jnilib"; - if (OsInfo.getOSName().equals("Mac") && hasResource(nativeLibraryPath + "/" + altName)) { + final String altName = NATIVE_LIBNAME_ALT; + if (OsInfo.getOSName().equals("Mac") && hasResource(nativeLibraryPath + File.separator + altName)) { // Fix for openjdk7 for Mac nativeLibraryName = altName; hasNativeLib = true; @@ -223,15 +184,13 @@ private static File findNativeLibrary() { } if (!hasNativeLib) { - final String errorMessage = String.format("No native library is found for os.name=%s and os.arch=%s", - OsInfo.getOSName(), OsInfo.getArchName()); + final String errorMessage = String.format("No native library is found for os.name=%s and os.arch=%s", OsInfo.getOSName(), OsInfo.getArchName()); throw new IllegalStateException(errorMessage); } // Temporary folder for the native lib. Use the value of // Crypto.LIB_TEMPDIR_KEY or java.io.tmpdir - final String tempFolder = new File( - props.getProperty(Crypto.LIB_TEMPDIR_KEY, System.getProperty("java.io.tmpdir"))).getAbsolutePath(); + final String tempFolder = new File(props.getProperty(Crypto.LIB_TEMPDIR_KEY, System.getProperty("java.io.tmpdir"))).getAbsolutePath(); // Extract and load a native library inside the jar file return extractLibraryFile(nativeLibraryPath, nativeLibraryName, tempFolder); @@ -243,7 +202,7 @@ private static File findNativeLibrary() { * @return null, unless loading failed */ static Throwable getLoadingError() { - return loadingError; + return libraryLoadingError; } /** @@ -266,7 +225,7 @@ private static boolean isDebug() { * @return {@code true} if native is loaded, else {@code false}. */ static boolean isNativeCodeLoaded() { - return nativeCodeLoaded; + return libraryLoaded; } /** @@ -280,13 +239,13 @@ static Throwable loadLibrary() { if (nativeLibFile != null) { // Load extracted or specified native library. final String absolutePath = nativeLibFile.getAbsolutePath(); - debug("System.load('%s')", absolutePath); + debug("%s System.load('%s')", SIMPLE_NAME, absolutePath); System.load(absolutePath); } else { // Load preinstalled library (in the path -Djava.library.path) - final String libname = "commons-crypto"; - debug("System.loadLibrary('%s')", libname); - System.loadLibrary(libname); + final String libName = NATIVE_LIBNAME; + debug("%s System.loadLibrary('%s')", SIMPLE_NAME, libName); + System.loadLibrary(libName); } return null; // OK } catch (final Exception | UnsatisfiedLinkError t) { diff --git a/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java b/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java index 6b4fc7ed5..8c68963e4 100644 --- a/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java +++ b/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java @@ -26,21 +26,21 @@ * and implemented in the file * src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c */ -class OpenSslInfoNative { - - public static final long VERSION_1_0_2X = 0x10002000; - public static final long VERSION_1_1_0X = 0x10100000; +final class OpenSslInfoNative { /** - * Makes the constructor private. + * Return the name used to load the dynamic linked library. + * + * @return the name used to load the library (e.g. crypto.dll) */ - private OpenSslInfoNative() { - } + public static native String DLLName(); /** - * @return version of native + * Return the path to the loaded dynamic linked library. + * [Currently not implemented on Windows] + * @return the path to the library that was loaded; may be {@code null}. */ - public static native String NativeVersion(); + public static native String DLLPath(); /** * @return name of native @@ -53,6 +53,11 @@ private OpenSslInfoNative() { public static native String NativeTimeStamp(); + /** + * @return version of native + */ + public static native String NativeVersion(); + /** * @return the value of OPENSSL_VERSION_NUMBER. */ @@ -65,4 +70,10 @@ private OpenSslInfoNative() { * @return The text variant of the version number and the release date. */ public static native String OpenSSLVersion(int type); + + /** + * Makes the constructor private. + */ + private OpenSslInfoNative() { + } } diff --git a/src/main/java/org/apache/commons/crypto/OsInfo.java b/src/main/java/org/apache/commons/crypto/OsInfo.java index 7d737ef9b..a3dc80936 100644 --- a/src/main/java/org/apache/commons/crypto/OsInfo.java +++ b/src/main/java/org/apache/commons/crypto/OsInfo.java @@ -17,6 +17,7 @@ */ package org.apache.commons.crypto; +import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Locale; @@ -26,6 +27,7 @@ * information from the build environment. */ final class OsInfo { + private final static HashMap
- * Note that implementations must provide a constructor that has 2 parameters:
- *
- * a Properties instance and a String (transformation)
- *
+ * Note that implementations must provide a constructor that has 2 parameters: a Properties instance and a String (transformation)
+ *
This is the same name that was specified in one of the + *
+ * This is the same name that was specified in one of the * {@code CryptoCipherFactory#getInstance} calls that created this * {@code CryptoCipher} object.. + *
* * @return the algorithm name of this {@code CryptoCipher} object. */ String getAlgorithm(); + /** + * Returns the block size (in bytes). + * + * @return the block size (in bytes), or 0 if the underlying algorithm is + * not a block cipher + */ + int getBlockSize(); + /** * Initializes the cipher with mode, key and iv. * @@ -71,7 +118,7 @@ public interface CryptoCipher extends Closeable { * policy files). * @throws InvalidAlgorithmParameterException if the given algorithm * parameters are inappropriate for this cipher, or this cipher - * requires algorithm parameters and {@code params} is null, or + * requires algorithm parameters and {@code params} is {@code null}, or * the given algorithm parameters imply a cryptographic strength * that would exceed the legal limits (as determined from the * configured jurisdiction policy files). @@ -79,19 +126,6 @@ public interface CryptoCipher extends Closeable { void init(int mode, Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException; - /** - * Continues a multiple-part encryption/decryption operation. The data is - * encrypted or decrypted, depending on how this cipher was initialized. - * - * @param inBuffer the input ByteBuffer - * @param outBuffer the output ByteBuffer - * @return int number of bytes stored in {@code output} - * @throws ShortBufferException if there is insufficient space in the output - * buffer - */ - int update(ByteBuffer inBuffer, ByteBuffer outBuffer) - throws ShortBufferException; - /** * Continues a multiple-part encryption/decryption operation. The data is * encrypted or decrypted, depending on how this cipher was initialized. @@ -109,51 +143,17 @@ int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException; /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. + * Continues a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. * * @param inBuffer the input ByteBuffer * @param outBuffer the output ByteBuffer * @return int number of bytes stored in {@code output} - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this cipher is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. - * @throws ShortBufferException if the given output buffer is too small to - * hold the result - */ - int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer) - throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException; - - /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. - * - * @param input the input byte array - * @param inputOffset the offset in input where the input starts - * @param inputLen the input length - * @param output the byte array for the result - * @param outputOffset the offset in output where the result is stored - * @return the number of bytes stored in output - * @throws ShortBufferException if the given output byte array is too small - * to hold the result - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this cipher is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. + * @throws ShortBufferException if there is insufficient space in the output + * buffer */ - int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException, - IllegalBlockSizeException, BadPaddingException; + int update(ByteBuffer inBuffer, ByteBuffer outBuffer) + throws ShortBufferException; /** * Continues a multi-part update of the Additional Authentication @@ -164,6 +164,7 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, * GCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * @@ -192,6 +193,7 @@ default void updateAAD(final byte[] aad) * GCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * diff --git a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java index 8824b3265..c3fbdf9e5 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java +++ b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java @@ -26,36 +26,15 @@ import org.apache.commons.crypto.utils.Utils; /** - * This is the factory class used for creating {@link CryptoCipher} instances. + * Creates {@link CryptoCipher} instances. */ public class CryptoCipherFactory { - /** - * The configuration key of the provider class for JCE cipher. - */ - public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX - + "cipher.jce.provider"; - /** - * The configuration key of the CryptoCipher implementation class. - *- * The value of CLASSES_KEY needs to be the full name of a - * class that implements the - * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface - * The internal classes are listed in the enum - * {@link CipherProvider CipherProvider} - * which can be used to obtain the full class name. - *
- * The value can also be a comma-separated list of class names in - * order of descending priority. - */ - - public static final String CLASSES_KEY = Crypto.CONF_PREFIX - + "cipher.classes"; - /** * Defines the internal CryptoCipher implementations. *
* Usage: + *
** props.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.OPENSSL.getClassName()); * props.setProperty(...); // if required by the implementation @@ -68,6 +47,7 @@ public enum CipherProvider { * The OpenSSL cipher implementation (using JNI) ** This implementation does not use any properties + *
*/ // Please ensure the property description agrees with the implementation OPENSSL(OpenSslCipher.class), @@ -77,6 +57,7 @@ public enum CipherProvider { ** uses the property {@link #JCE_PROVIDER_KEY} * to define the provider name, if present. + *
*/ // Please ensure the property description agrees with the implementation JCE(JceCipher.class); @@ -113,6 +94,28 @@ public Class extends CryptoCipher> getImplClass() { } } + /** + * The configuration key of the provider class for JCE cipher. + */ + public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX + "cipher.jce.provider"; + + /** + * The configuration key of the CryptoCipher implementation class. + *+ * The value of CLASSES_KEY needs to be the full name of a + * class that implements the + * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface + * The internal classes are listed in the enum + * {@link CipherProvider CipherProvider} + * which can be used to obtain the full class name. + *
+ *+ * The value can also be a comma-separated list of class names in + * order of descending priority. + *
+ */ + public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "cipher.classes"; + /** * For AES, the algorithm block is fixed size of 128 bits. * @@ -130,9 +133,34 @@ public Class extends CryptoCipher> getImplClass() { .concat(CipherProvider.JCE.getClassName()); /** - * The private Constructor of {@link CryptoCipherFactory}. + * Gets the cipher class. + * + * @param props The {@code Properties} class represents a set of + * properties. + * @return the cipher class based on the props. */ - private CryptoCipherFactory() { + private static String getCipherClassString(final Properties props) { + String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT); + if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default? + cipherClassString = CLASSES_DEFAULT; + } + return cipherClassString; + } + + /** + * Gets a cipher for algorithm/mode/padding in config value + * commons.crypto.cipher.transformation + * + * @param transformation the name of the transformation, e.g., + * AES/CBC/PKCS5Padding. + * See the Java Cryptography Architecture Standard Algorithm Name Documentation + * for information about standard transformation names. + * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher) + * @throws GeneralSecurityException if JCE cipher initialize failed + */ + public static CryptoCipher getCryptoCipher(final String transformation) + throws GeneralSecurityException { + return getCryptoCipher(transformation, new Properties()); } /** @@ -142,14 +170,12 @@ private CryptoCipherFactory() { * @param transformation algorithm/mode/padding * @return CryptoCipher the cipher (defaults to OpenSslCipher) * @throws GeneralSecurityException if cipher initialize failed - * @throws IllegalArgumentException if no classname(s) were provided + * @throws IllegalArgumentException if no class name(s) were provided */ - public static CryptoCipher getCryptoCipher(final String transformation, final Properties properties) - throws GeneralSecurityException { - + public static CryptoCipher getCryptoCipher(final String transformation, final Properties properties) throws GeneralSecurityException { final Listnames = Utils.splitClassNames(getCipherClassString(properties), ","); if (names.isEmpty()) { - throw new IllegalArgumentException("No classname(s) provided"); + throw new IllegalArgumentException("No class name(s) provided"); } CryptoCipher cipher = null; Exception lastException = null; @@ -158,8 +184,7 @@ public static CryptoCipher getCryptoCipher(final String transformation, final Pr for (final String klass : names) { try { final Class> cls = ReflectionUtils.getClassByName(klass); - cipher = ReflectionUtils.newInstance(cls.asSubclass - (CryptoCipher.class), properties, transformation); + cipher = ReflectionUtils.newInstance(cls.asSubclass(CryptoCipher.class), properties, transformation); break; } catch (final Exception e) { lastException = e; @@ -170,40 +195,14 @@ public static CryptoCipher getCryptoCipher(final String transformation, final Pr if (cipher != null) { return cipher; } - errorMessage.append(" is not available or transformation " + - transformation + " is not supported."); + errorMessage.append(" is not available or transformation " + transformation + " is not supported."); throw new GeneralSecurityException(errorMessage.toString(), lastException); } /** - * Gets a cipher for algorithm/mode/padding in config value - * commons.crypto.cipher.transformation - * - * @param transformation the name of the transformation, e.g., - * AES/CBC/PKCS5Padding. - * See the Java Cryptography Architecture Standard Algorithm Name Documentation - * for information about standard transformation names. - * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher) - * @throws GeneralSecurityException if JCE cipher initialize failed - */ - public static CryptoCipher getCryptoCipher(final String transformation) - throws GeneralSecurityException { - return getCryptoCipher(transformation, new Properties()); - } - - /** - * Gets the cipher class. - * - * @param props The {@code Properties} class represents a set of - * properties. - * @return the cipher class based on the props. + * The private Constructor of {@link CryptoCipherFactory}. */ - private static String getCipherClassString(final Properties props) { - String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT); - if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default? - cipherClassString = CLASSES_DEFAULT; - } - return cipherClassString; + private CryptoCipherFactory() { } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java index 8bdb49364..65b35ee73 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java +++ b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java @@ -33,8 +33,12 @@ /** * Implements the {@link CryptoCipher} using JCE provider. + * + * This class is not public/protected so does not appear in the main Javadoc. Please ensure that property use is documented in the enum + * CryptoRandomFactory.RandomProvider + *
*/ -class JceCipher implements CryptoCipher { +final class JceCipher implements CryptoCipher { private final Cipher cipher; /** @@ -44,35 +48,83 @@ class JceCipher implements CryptoCipher { * @param transformation transformation for JCE cipher (algorithm/mode/padding) * @throws GeneralSecurityException if JCE cipher initialize failed */ - // N.B. this class is not public/protected so does not appear in the main Javadoc - // Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider public JceCipher(final Properties props, final String transformation) throws GeneralSecurityException { - final String provider = props.getProperty(CryptoCipherFactory.JCE_PROVIDER_KEY); - if (provider == null || provider.isEmpty()) { - cipher = Cipher.getInstance(transformation); - } else { - cipher = Cipher.getInstance(transformation, provider); - } + final String provider = props.getProperty(CryptoCipherFactory.JCE_PROVIDER_KEY, ""); + cipher = provider.isEmpty() ? Cipher.getInstance(transformation) : Cipher.getInstance(transformation, provider); } /** - * Returns the block size (in bytes). + * Closes Jce cipher. + */ + @Override + public void close() { + // Do nothing + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. * - * @return the block size (in bytes), or 0 if the underlying algorithm is - * not a block cipher + * @param input the input byte array + * @param inputOffset the offset in input where the input starts + * @param inputLen the input length + * @param output the byte array for the result + * @param outputOffset the offset in output where the result is stored + * @return the number of bytes stored in output + * @throws ShortBufferException if the given output byte array is too small + * to hold the result + * @throws BadPaddingException if this cipher is in decryption mode, and + * (un)padding has been requested, but the decrypted data is not + * bounded by the appropriate padding bytes + * @throws IllegalBlockSizeException if this cipher is a block cipher, no + * padding has been requested (only in encryption mode), and the + * total input length of the data processed by this cipher is not a + * multiple of block size; or if this encryption algorithm is unable + * to process the input data provided. */ @Override - public final int getBlockSize() { - return cipher.getBlockSize(); + public int doFinal(final byte[] input, final int inputOffset, final int inputLen, + final byte[] output, final int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + return cipher.doFinal(input, inputOffset, inputLen, output, + outputOffset); + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. The data is encrypted or decrypted, depending on + * how this cipher was initialized. + * + * @param inBuffer the input ByteBuffer + * @param outBuffer the output ByteBuffer + * @return int number of bytes stored in {@code output} + * @throws BadPaddingException if this cipher is in decryption mode, and + * (un)padding has been requested, but the decrypted data is not + * bounded by the appropriate padding bytes + * @throws IllegalBlockSizeException if this cipher is a block cipher, no + * padding has been requested (only in encryption mode), and the + * total input length of the data processed by this cipher is not a + * multiple of block size; or if this encryption algorithm is unable + * to process the input data provided. + * @throws ShortBufferException if the given output buffer is too small to + * hold the result + */ + @Override + public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) + throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + return cipher.doFinal(inBuffer, outBuffer); } /** * Returns the algorithm name of this {@code CryptoCipher} object. * - *This is the same name that was specified in one of the + *
+ * This is the same name that was specified in one of the * {@code CryptoCipherFactory#getInstance} calls that created this * {@code CryptoCipher} object.. + *
* * @return the algorithm name of this {@code CryptoCipher} object. */ @@ -81,6 +133,17 @@ public String getAlgorithm() { return cipher.getAlgorithm(); } + /** + * Returns the block size (in bytes). + * + * @return the block size (in bytes), or 0 if the underlying algorithm is + * not a block cipher + */ + @Override + public int getBlockSize() { + return cipher.getBlockSize(); + } + /** * Initializes the cipher with mode, key and iv. * @@ -89,7 +152,7 @@ public String getAlgorithm() { * @param params the algorithm parameters * @throws InvalidAlgorithmParameterException if the given algorithm * parameters are inappropriate for this cipher, or this cipher - * requires algorithm parameters and {@code params} is null, or + * requires algorithm parameters and {@code params} is {@code null}, or * the given algorithm parameters imply a cryptographic strength * that would exceed the legal limits (as determined from the * configured jurisdiction policy files). @@ -104,22 +167,6 @@ public void init(final int mode, final Key key, final AlgorithmParameterSpec par cipher.init(mode, key, params); } - /** - * Continues a multiple-part encryption/decryption operation. The data is - * encrypted or decrypted, depending on how this cipher was initialized. - * - * @param inBuffer the input ByteBuffer - * @param outBuffer the output ByteBuffer - * @return int number of bytes stored in {@code output} - * @throws ShortBufferException if there is insufficient space in the output - * buffer - */ - @Override - public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) - throws ShortBufferException { - return cipher.update(inBuffer, outBuffer); - } - /** * Continues a multiple-part encryption/decryption operation. The data is * encrypted or decrypted, depending on how this cipher was initialized. @@ -140,62 +187,23 @@ public int update(final byte[] input, final int inputOffset, final int inputLen, .update(input, inputOffset, inputLen, output, outputOffset); } + /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. The data is encrypted or decrypted, depending on - * how this cipher was initialized. + * Continues a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. * * @param inBuffer the input ByteBuffer * @param outBuffer the output ByteBuffer * @return int number of bytes stored in {@code output} - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this cipher is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. - * @throws ShortBufferException if the given output buffer is too small to - * hold the result - */ - @Override - public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) - throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException { - return cipher.doFinal(inBuffer, outBuffer); - } - - /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. - * - * @param input the input byte array - * @param inputOffset the offset in input where the input starts - * @param inputLen the input length - * @param output the byte array for the result - * @param outputOffset the offset in output where the result is stored - * @return the number of bytes stored in output - * @throws ShortBufferException if the given output byte array is too small - * to hold the result - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this cipher is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. + * @throws ShortBufferException if there is insufficient space in the output + * buffer */ @Override - public int doFinal(final byte[] input, final int inputOffset, final int inputLen, - final byte[] output, final int outputOffset) throws ShortBufferException, - IllegalBlockSizeException, BadPaddingException { - return cipher.doFinal(input, inputOffset, inputLen, output, - outputOffset); + public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) + throws ShortBufferException { + return cipher.update(inBuffer, outBuffer); } - /** * Continues a multi-part update of the Additional Authentication * Data (AAD). @@ -205,11 +213,12 @@ public int doFinal(final byte[] input, final int inputOffset, final int inputLen * either GCM or CCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * * @throws IllegalArgumentException if the {@code aad} - * byte array is null + * byte array is {@code null} * @throws IllegalStateException if this cipher is in a wrong state * (e.g., has not been initialized), does not accept AAD, or if * operating in either GCM or CCM mode and one of the {@code update} @@ -223,6 +232,7 @@ public void updateAAD(final byte[] aad) { cipher.updateAAD(aad); } + /** * Continues a multi-part update of the Additional Authentication * Data (AAD). @@ -232,11 +242,12 @@ public void updateAAD(final byte[] aad) { * either GCM or CCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * * @throws IllegalArgumentException if the {@code aad} - * byte array is null + * byte array is {@code null} * @throws IllegalStateException if this cipher is in a wrong state * (e.g., has not been initialized), does not accept AAD, or if * operating in either GCM or CCM mode and one of the {@code update} @@ -249,13 +260,4 @@ public void updateAAD(final byte[] aad) { public void updateAAD(final ByteBuffer aad) { cipher.updateAAD(aad); } - - - /** - * Closes Jce cipher. - */ - @Override - public void close() { - // Do nothing - } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java index 922085a29..0f21f257c 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java @@ -21,7 +21,6 @@ import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; -import java.util.StringTokenizer; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; @@ -29,6 +28,7 @@ import javax.crypto.ShortBufferException; import org.apache.commons.crypto.Crypto; +import org.apache.commons.crypto.utils.Transformation; import org.apache.commons.crypto.utils.Utils; /** @@ -37,12 +37,6 @@ */ final class OpenSsl { - // Mode constant defined by OpenSsl JNI - public static final int ENCRYPT_MODE = 1; - public static final int DECRYPT_MODE = 0; - - private final OpenSslFeedbackCipher opensslBlockCipher; - /** Currently only support AES/CTR/NoPadding. */ private enum AlgorithmMode { AES_CTR, AES_CBC, AES_GCM, SM4_CTR, SM4_CBC; @@ -55,37 +49,18 @@ private enum AlgorithmMode { * @return the Algorithm mode. * @throws NoSuchAlgorithmException if the algorithm is not available. */ - static int get(final String algorithm, final String mode) - throws NoSuchAlgorithmException { + static int get(final String algorithm, final String mode) throws NoSuchAlgorithmException { try { return AlgorithmMode.valueOf(algorithm + "_" + mode).ordinal(); } catch (final Exception e) { - throw new NoSuchAlgorithmException( - "Doesn't support algorithm: " + algorithm - + " and mode: " + mode); + throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode); } } } + // Mode constant defined by OpenSsl JNI + public static final int ENCRYPT_MODE = 1; - private enum Padding { - NoPadding, PKCS5Padding; - - /** - * Gets the Padding instance. - * - * @param padding the padding. - * @return the value of Padding. - * @throws NoSuchPaddingException if the padding is not available. - */ - static int get(final String padding) throws NoSuchPaddingException { - try { - return Padding.valueOf(padding).ordinal(); - } catch (final Exception e) { - throw new NoSuchPaddingException("Doesn't support padding: " - + padding); - } - } - } + public static final int DECRYPT_MODE = 0; private static final Throwable loadingFailureReason; @@ -105,37 +80,13 @@ static int get(final String padding) throws NoSuchPaddingException { } /** - * Gets the failure reason when loading OpenSsl native. - * - * @return the failure reason; null if it was loaded and initialized successfully - */ - public static Throwable getLoadingFailureReason() { - return loadingFailureReason; - } - - /** - * Constructs a {@link OpenSsl} instance based on context, algorithm and padding. - * - * @param context the context. - * @param algorithm the algorithm. - * @param padding the padding. - */ - private OpenSsl(final long context, final int algorithm, final int padding) { - if (algorithm == AlgorithmMode.AES_GCM.ordinal()) { - opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding); - } else { - opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding); - } - } - - /** - * Return an {@code OpenSslCipher} object that implements the specified + * Gets an {@code OpenSslCipher} that implements the specified * transformation. * * @param transformation the name of the transformation, e.g., * AES/CTR/NoPadding. * @return OpenSslCipher an {@code OpenSslCipher} object - * @throws NoSuchAlgorithmException if {@code transformation} is null, + * @throws NoSuchAlgorithmException if {@code transformation} is {@code null}, * empty, in an invalid format, or if OpenSsl doesn't implement the * specified algorithm. * @throws NoSuchPaddingException if {@code transformation} contains a @@ -147,133 +98,48 @@ public static OpenSsl getInstance(final String transformation) if (loadingFailureReason != null) { throw new IllegalStateException(loadingFailureReason); } - final Transform transform = tokenizeTransformation(transformation); - final int algorithmMode = AlgorithmMode.get(transform.algorithm, - transform.mode); - final int padding = Padding.get(transform.padding); + final Transformation transform = Transformation.parse(transformation); + final int algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode()); + final int padding = transform.getPadding().ordinal(); final long context = OpenSslNative.initContext(algorithmMode, padding); return new OpenSsl(context, algorithmMode, padding); } - /** Nested class for algorithm, mode and padding. */ - private static class Transform { - final String algorithm; - final String mode; - final String padding; - - /** - * Constructs a {@link Transform} based on the algorithm, mode and padding. - * - * @param algorithm the algorithm - * @param mode the mode. - * @param padding the padding. - */ - public Transform(final String algorithm, final String mode, final String padding) { - this.algorithm = algorithm; - this.mode = mode; - this.padding = padding; - } - } - /** - * Gets the tokens of transformation. + * Gets the failure reason when loading OpenSsl native. * - * @param transformation the transformation. - * @return the {@link Transform} instance. - * @throws NoSuchAlgorithmException if the transformation is null. + * @return the failure reason; {@code null} if it was loaded and initialized successfully */ - private static Transform tokenizeTransformation(final String transformation) - throws NoSuchAlgorithmException { - if (transformation == null) { - throw new NoSuchAlgorithmException("No transformation given."); - } - - /* - * Array containing the components of a Cipher transformation: index 0: - * algorithm (e.g., AES) index 1: mode (e.g., CTR) index 2: padding - * (e.g., NoPadding) - */ - final String[] parts = new String[3]; - int count = 0; - final StringTokenizer parser = new StringTokenizer(transformation, "/"); - while (parser.hasMoreTokens() && count < 3) { - parts[count++] = parser.nextToken().trim(); - } - if (count != 3 || parser.hasMoreTokens()) { - throw new NoSuchAlgorithmException( - "Invalid transformation format: " + transformation); - } - return new Transform(parts[0], parts[1], parts[2]); + public static Throwable getLoadingFailureReason() { + return loadingFailureReason; } - /** - * Initialize this cipher with a key and IV. - * - * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE} - * @param key crypto key - * @param params the algorithm parameters - * @throws InvalidAlgorithmParameterException if IV length is wrong - */ - public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) - throws InvalidAlgorithmParameterException { - opensslBlockCipher.init(mode, key, params); - } + private final AbstractOpenSslFeedbackCipher opensslBlockCipher; /** - *- * Continues a multiple-part encryption or decryption operation. The data is - * encrypted or decrypted, depending on how this cipher was initialized. - *
- * - *- * All {@code input.remaining()} bytes starting at - * {@code input.position()} are processed. The result is stored in the - * output buffer. - *
- * - *- * Upon return, the input buffer's position will be equal to its limit; its - * limit will not have changed. The output buffer's position will have - * advanced by n, when n is the value returned by this method; the output - * buffer's limit will not have changed. - *
- * - * If {@code output.remaining()} bytes are insufficient to hold the - * result, a {@code ShortBufferException} is thrown. + * Constructs a {@link OpenSsl} instance based on context, algorithm and padding. * - * @param input the input ByteBuffer - * @param output the output ByteBuffer - * @return int number of bytes stored in {@code output} - * @throws ShortBufferException if there is insufficient space in the output - * buffer + * @param context the context. + * @param algorithm the algorithm. + * @param padding the padding. */ - public int update(final ByteBuffer input, final ByteBuffer output) - throws ShortBufferException { - Utils.checkArgument(input.isDirect() && output.isDirect(), - "Direct buffers are required."); - return opensslBlockCipher.update(input, output); + private OpenSsl(final long context, final int algorithm, final int padding) { + if (algorithm == AlgorithmMode.AES_GCM.ordinal()) { + opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding); + } else { + opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding); + } } - /** - * Continues a multiple-part encryption/decryption operation. The data is - * encrypted or decrypted, depending on how this cipher was initialized. - * - * @param input the input byte array - * @param inputOffset the offset in input where the input starts - * @param inputLen the input length - * @param output the byte array for the result - * @param outputOffset the offset in output where the result is stored - * @return the number of bytes stored in output - * @throws ShortBufferException if there is insufficient space in the output - * byte array - */ - public int update(final byte[] input, final int inputOffset, final int inputLen, - final byte[] output, final int outputOffset) throws ShortBufferException { - return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset); + /** Forcibly clean the context. */ + public void clean() { + if (opensslBlockCipher != null) { + opensslBlockCipher.clean(); + } } /** - * Encrypts or decrypts data in a single-part operation, or finishes a + * Finalizes to encrypt or decrypt data in a single-part operation, or finishes a * multiple-part operation. * * @param input the input byte array @@ -293,18 +159,14 @@ public int update(final byte[] input, final int inputOffset, final int inputLen, * multiple of block size; or if this encryption algorithm is unable * to process the input data provided. */ - public int doFinal(final byte[] input, final int inputOffset, final int inputLen, - final byte[] output, final int outputOffset) - throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException{ + public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { return opensslBlockCipher.doFinal(input, inputOffset, inputLen, output, outputOffset); } /** - ** Finishes a multiple-part operation. The data is encrypted or decrypted, * depending on how this cipher was initialized. - *
* ** The result is stored in the output buffer. Upon return, the output @@ -340,13 +202,78 @@ public int doFinal(final byte[] input, final int inputOffset, final int inputLen * (un)padding has been requested, but the decrypted data is not * bounded by the appropriate padding bytes */ - public int doFinal(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException, - IllegalBlockSizeException, BadPaddingException { + public int doFinal(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { Utils.checkArgument(output.isDirect(), "Direct buffer is required."); return opensslBlockCipher.doFinal(input, output); } + @Override + protected void finalize() throws Throwable { + clean(); + } + + /** + * Initializes this cipher with a key and IV. + * + * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE} + * @param key crypto key + * @param params the algorithm parameters + * @throws InvalidAlgorithmParameterException if IV length is wrong + */ + public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { + opensslBlockCipher.init(mode, key, params); + } + + /** + * Updates a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. + * + * @param input the input byte array + * @param inputOffset the offset in input where the input starts + * @param inputLen the input length + * @param output the byte array for the result + * @param outputOffset the offset in output where the result is stored + * @return the number of bytes stored in output + * @throws ShortBufferException if there is insufficient space in the output + * byte array + */ + public int update(final byte[] input, final int inputOffset, final int inputLen, + final byte[] output, final int outputOffset) throws ShortBufferException { + return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset); + } + + + /** + * Updates a multiple-part encryption or decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. + * + *
+ * All {@code input.remaining()} bytes starting at + * {@code input.position()} are processed. The result is stored in the + * output buffer. + *
+ * + *+ * Upon return, the input buffer's position will be equal to its limit; its + * limit will not have changed. The output buffer's position will have + * advanced by n, when n is the value returned by this method; the output + * buffer's limit will not have changed. + *
+ * + * If {@code output.remaining()} bytes are insufficient to hold the + * result, a {@code ShortBufferException} is thrown. + * + * @param input the input ByteBuffer + * @param output the output ByteBuffer + * @return int number of bytes stored in {@code output} + * @throws ShortBufferException if there is insufficient space in the output + * buffer + */ + public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException { + Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required."); + return opensslBlockCipher.update(input, output); + } /** * Continues a multi-part update of the Additional Authentication @@ -357,25 +284,12 @@ public int doFinal(final ByteBuffer input, final ByteBuffer output) throws Short * either GCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data - * */ public void updateAAD(final byte[] aad) { this.opensslBlockCipher.updateAAD(aad); } - - /** Forcibly clean the context. */ - public void clean() { - if (opensslBlockCipher != null) { - opensslBlockCipher.clean(); - } - } - - @Override - protected void finalize() throws Throwable { - clean(); - } - } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java index 50e73da9b..0ef8fc005 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java @@ -33,8 +33,12 @@ /** * Implements the CryptoCipher using JNI into OpenSSL. + *+ * this class is not public/protected so does not appear in the main Javadoc Please ensure that property use is documented in the enum + * CryptoRandomFactory.RandomProvider + *
*/ -class OpenSslCipher implements CryptoCipher { +final class OpenSslCipher implements CryptoCipher { private final OpenSsl openSslEngine; private boolean initialized; @@ -48,8 +52,6 @@ class OpenSslCipher implements CryptoCipher { * @param transformation transformation for OpenSSL openSslEngine (algorithm/mode/padding) * @throws GeneralSecurityException if OpenSSL openSslEngine initialize failed */ - // N.B. this class is not public/protected so does not appear in the main Javadoc - // Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider public OpenSslCipher(final Properties props, final String transformation) // NOPMD throws GeneralSecurityException { this.transformation = transformation; @@ -63,22 +65,75 @@ public OpenSslCipher(final Properties props, final String transformation) // NOP } /** - * Returns the block size (in bytes). + * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context. + */ + @Override + public void close() { + openSslEngine.clean(); + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. * - * @return the block size (in bytes), or 0 if the underlying algorithm is - * not a block openSslEngine + * @param input the input byte array + * @param inputOffset the offset in input where the input starts + * @param inputLen the input length + * @param output the byte array for the result + * @param outputOffset the offset in output where the result is stored + * @return the number of bytes stored in output + * @throws ShortBufferException if the given output byte array is too small + * to hold the result + * @throws BadPaddingException if this openSslEngine is in decryption mode, and + * (un)padding has been requested, but the decrypted data is not + * bounded by the appropriate padding bytes + * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no + * padding has been requested (only in encryption mode), and the + * total input length of the data processed by this openSslEngine is not a + * multiple of block size; or if this encryption algorithm is unable + * to process the input data provided. */ @Override - public final int getBlockSize() { - return CryptoCipherFactory.AES_BLOCK_SIZE; + public int doFinal(final byte[] input, final int inputOffset, final int inputLen, + final byte[] output, final int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + return openSslEngine.doFinal(input, inputOffset, inputLen, output, outputOffset); + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. The data is encrypted or decrypted, depending on + * how this openSslEngine was initialized. + * + * @param inBuffer the input ByteBuffer + * @param outBuffer the output ByteBuffer + * @return int number of bytes stored in {@code output} + * @throws BadPaddingException if this openSslEngine is in decryption mode, and + * (un)padding has been requested, but the decrypted data is not + * bounded by the appropriate padding bytes + * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no + * padding has been requested (only in encryption mode), and the + * total input length of the data processed by this openSslEngine is not a + * multiple of block size; or if this encryption algorithm is unable + * to process the input data provided. + * @throws ShortBufferException if the given output buffer is too small to + * hold the result + */ + @Override + public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) + throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + return openSslEngine.doFinal(inBuffer, outBuffer); } /** * Returns the algorithm name of this {@code CryptoCipher} object. * - *This is the same name that was specified in one of the + *
+ * This is the same name that was specified in one of the * {@code CryptoCipherFactory#getInstance} calls that created this * {@code CryptoCipher} object.. + *
* * @return the algorithm name of this {@code CryptoCipher} object. */ @@ -87,6 +142,17 @@ public String getAlgorithm() { return transformation; } + /** + * Returns the block size (in bytes). + * + * @return the block size (in bytes), or 0 if the underlying algorithm is + * not a block openSslEngine + */ + @Override + public int getBlockSize() { + return CryptoCipherFactory.AES_BLOCK_SIZE; + } + /** * Initializes the openSslEngine with mode, key and iv. * @@ -102,30 +168,11 @@ public void init(final int mode, final Key key, final AlgorithmParameterSpec par Objects.requireNonNull(key, "key"); Objects.requireNonNull(params, "params"); - int cipherMode = OpenSsl.DECRYPT_MODE; - if (mode == Cipher.ENCRYPT_MODE) { - cipherMode = OpenSsl.ENCRYPT_MODE; - } + final int cipherMode = mode == Cipher.ENCRYPT_MODE ? OpenSsl.ENCRYPT_MODE: OpenSsl.DECRYPT_MODE; openSslEngine.init(cipherMode, key.getEncoded(), params); initialized = true; } - /** - * Continues a multiple-part encryption/decryption operation. The data is - * encrypted or decrypted, depending on how this openSslEngine was initialized. - * - * @param inBuffer the input ByteBuffer - * @param outBuffer the output ByteBuffer - * @return int number of bytes stored in {@code output} - * @throws ShortBufferException if there is insufficient space in the output - * buffer - */ - @Override - public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) - throws ShortBufferException { - return openSslEngine.update(inBuffer, outBuffer); - } - /** * Continues a multiple-part encryption/decryption operation. The data is * encrypted or decrypted, depending on how this openSslEngine was initialized. @@ -146,61 +193,23 @@ public int update(final byte[] input, final int inputOffset, final int inputLen, .update(input, inputOffset, inputLen, output, outputOffset); } + /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. The data is encrypted or decrypted, depending on - * how this openSslEngine was initialized. + * Continues a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this openSslEngine was initialized. * * @param inBuffer the input ByteBuffer * @param outBuffer the output ByteBuffer * @return int number of bytes stored in {@code output} - * @throws BadPaddingException if this openSslEngine is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this openSslEngine is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. - * @throws ShortBufferException if the given output buffer is too small to - * hold the result - */ - @Override - public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) - throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException { - return openSslEngine.doFinal(inBuffer, outBuffer); - } - - /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. - * - * @param input the input byte array - * @param inputOffset the offset in input where the input starts - * @param inputLen the input length - * @param output the byte array for the result - * @param outputOffset the offset in output where the result is stored - * @return the number of bytes stored in output - * @throws ShortBufferException if the given output byte array is too small - * to hold the result - * @throws BadPaddingException if this openSslEngine is in decryption mode, and - * (un)padding has been requested, but the decrypted data is not - * bounded by the appropriate padding bytes - * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no - * padding has been requested (only in encryption mode), and the - * total input length of the data processed by this openSslEngine is not a - * multiple of block size; or if this encryption algorithm is unable - * to process the input data provided. + * @throws ShortBufferException if there is insufficient space in the output + * buffer */ @Override - public int doFinal(final byte[] input, final int inputOffset, final int inputLen, - final byte[] output, final int outputOffset) throws ShortBufferException, - IllegalBlockSizeException, BadPaddingException { - return openSslEngine.doFinal(input, inputOffset, inputLen, output,outputOffset); + public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) + throws ShortBufferException { + return openSslEngine.update(inBuffer, outBuffer); } - /** * Continues a multi-part update of the Additional Authentication * Data (AAD). @@ -210,11 +219,12 @@ public int doFinal(final byte[] input, final int inputOffset, final int inputLen * either GCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * * @throws IllegalArgumentException if the {@code aad} - * byte array is null + * byte array is {@code null} * @throws IllegalStateException if this opensslEngine is in a wrong state * (e.g., has not been initialized), does not accept AAD, or if * operating in either GCM mode and one of the {@code update} @@ -239,6 +249,7 @@ public void updateAAD(final byte[] aad) throws IllegalArgumentException, openSslEngine.updateAAD(aad); } + /** * Continues a multi-part update of the Additional Authentication * Data (AAD). @@ -248,11 +259,12 @@ public void updateAAD(final byte[] aad) throws IllegalArgumentException, * either GCM mode, all AAD must be supplied before beginning * operations on the ciphertext (via the {@code update} and * {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * * @throws IllegalArgumentException if the {@code aad} - * byte array is null + * byte array is {@code null} * @throws IllegalStateException if this opensslEngine is in a wrong state * (e.g., has not been initialized), does not accept AAD, or if * operating in either GCM mode and one of the {@code update} @@ -279,13 +291,4 @@ public void updateAAD(final ByteBuffer aad) throws IllegalArgumentException, aad.get(aadBytes); openSslEngine.updateAAD(aadBytes); } - - - /** - * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context. - */ - @Override - public void close() { - openSslEngine.clean(); - } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java index 6ca17bb90..367da3691 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java @@ -1,20 +1,20 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.cipher; import java.nio.ByteBuffer; @@ -30,79 +30,39 @@ * This class do the real work(Encryption/Decryption) for non-authenticated modes, such as CTR, CBC. ** It will call the OpenSSL API to implement encryption/decryption + *
*/ -class OpenSslCommonMode extends OpenSslFeedbackCipher { +final class OpenSslCommonMode extends AbstractOpenSslFeedbackCipher { OpenSslCommonMode(final long context, final int algorithmMode, final int padding) { super(context, algorithmMode, padding); } - @Override - public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) - throws InvalidAlgorithmParameterException { - this.cipherMode = mode; - final byte[] iv; - if (params instanceof IvParameterSpec) { - iv = ((IvParameterSpec) params).getIV(); - } else { - // other AlgorithmParameterSpec is not supported now. - throw new InvalidAlgorithmParameterException("Illegal parameters"); - } - context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); - } - - @Override - public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException { - checkState(); - - final int len = OpenSslNative.update(context, input, input.position(), - input.remaining(), output, output.position(), - output.remaining()); - input.position(input.limit()); - output.position(output.position() + len); - - return len; - } - - @Override - public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) - throws ShortBufferException { - checkState(); - - return OpenSslNative.updateByteArray(context, input, inputOffset, - inputLen, output, outputOffset, output.length - outputOffset); - } - @Override public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { checkState(); final int outputLength = output.length; - int len = OpenSslNative.updateByteArray(context, input, inputOffset, - inputLen, output, outputOffset, outputLength - outputOffset); + int len = OpenSslNative.updateByteArray(context, input, inputOffset, inputLen, output, outputOffset, outputLength - outputOffset); - len += OpenSslNative.doFinalByteArray(context, output, outputOffset + len, - outputLength - outputOffset - len); + len += OpenSslNative.doFinalByteArray(context, output, outputOffset + len, outputLength - outputOffset - len); return len; } @Override - public int doFinal(final ByteBuffer input, final ByteBuffer output) - throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + public int doFinal(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { checkState(); int totalLen = 0; - int len = OpenSslNative.update(context, input, input.position(), - input.remaining(), output, output.position(), output.remaining()); + int len = OpenSslNative.update(context, input, input.position(), input.remaining(), output, output.position(), output.remaining()); totalLen += len; input.position(input.limit()); output.position(output.position() + len); - len = OpenSslNative.doFinal(context, output, output.position(), - output.remaining()); + len = OpenSslNative.doFinal(context, output, output.position(), output.remaining()); totalLen += len; output.position(output.position() + len); @@ -110,10 +70,38 @@ public int doFinal(final ByteBuffer input, final ByteBuffer output) return totalLen; } + @Override + public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { + this.cipherMode = mode; + final byte[] iv; + if (!(params instanceof IvParameterSpec)) { + // other AlgorithmParameterSpec is not supported now. + throw new InvalidAlgorithmParameterException("Illegal parameters"); + } + iv = ((IvParameterSpec) params).getIV(); + context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); + } + + @Override + public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException { + checkState(); + + return OpenSslNative.updateByteArray(context, input, inputOffset, inputLen, output, outputOffset, output.length - outputOffset); + } + + @Override + public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException { + checkState(); + + final int len = OpenSslNative.update(context, input, input.position(), input.remaining(), output, output.position(), output.remaining()); + input.position(input.limit()); + output.position(output.position() + len); + + return len; + } + @Override public void updateAAD(final byte[] aad) { - throw new UnsupportedOperationException( - "The underlying Cipher implementation " - + "does not support this method"); + throw new UnsupportedOperationException("The underlying Cipher implementation does not support this method"); } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java index 85a8d23c7..44af6b031 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java @@ -18,7 +18,7 @@ package org.apache.commons.crypto.cipher; /** - * This enum is defined for OpensslNative.ctrl() to allow various cipher + * This enum is defined for OpenSslNative.ctrl() to allow various cipher * specific parameters to be determined and set. * see the macro definitions in openssl/evp.h */ diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java index 79950edaf..5ef211bad 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java @@ -36,13 +36,15 @@ * * @since 1.1 */ -class OpenSslGaloisCounterMode extends OpenSslFeedbackCipher { +final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher { + static final int DEFAULT_TAG_LEN = 16; // buffer for AAD data; if consumed, set as null private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); + private int tagBitLen = -1; + private static final int BITS_TO_BYTES_SHIFT_COUNT = 3; // >> 3 divides by 8 == Byte.SIZE - static final int DEFAULT_TAG_LEN = 16; // buffer for storing input in decryption, not used for encryption private ByteArrayOutputStream inBuffer; @@ -52,75 +54,9 @@ public OpenSslGaloisCounterMode(final long context, final int algorithmMode, fin } @Override - public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) - throws InvalidAlgorithmParameterException { - - if (aadBuffer == null) { - aadBuffer = new ByteArrayOutputStream(); - } else { - aadBuffer.reset(); - } - - this.cipherMode = mode; - final byte[] iv; - if (params instanceof GCMParameterSpec) { - final GCMParameterSpec gcmParam = (GCMParameterSpec) params; - iv = gcmParam.getIV(); - this.tagBitLen = gcmParam.getTLen(); - } else { - // other AlgorithmParameterSpec is not supported now. - throw new InvalidAlgorithmParameterException("Illegal parameters"); - } - - if (this.cipherMode == OpenSsl.DECRYPT_MODE) { - inBuffer = new ByteArrayOutputStream(); - } - - context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); - } - - @Override - public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException { - checkState(); - - processAAD(); - - final int len; - if (this.cipherMode == OpenSsl.DECRYPT_MODE) { - // store internally until doFinal(decrypt) is called because - // spec mentioned that only return recovered data after tag - // is successfully verified - final int inputLen = input.remaining(); - final byte[] inputBuf = new byte[inputLen]; - input.get(inputBuf, 0, inputLen); - inBuffer.write(inputBuf, 0, inputLen); - return 0; - } - len = OpenSslNative.update(context, input, input.position(), - input.remaining(), output, output.position(), - output.remaining()); - input.position(input.limit()); - output.position(output.position() + len); - - return len; - } - - @Override - public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) - throws ShortBufferException { - checkState(); - - processAAD(); - - if (this.cipherMode == OpenSsl.DECRYPT_MODE) { - // store internally until doFinal(decrypt) is called because - // spec mentioned that only return recovered data after tag - // is successfully verified - inBuffer.write(input, inputOffset, inputLen); - return 0; - } - return OpenSslNative.updateByteArray(context, input, inputOffset, - inputLen, output, outputOffset, output.length - outputOffset); + public void clean() { + super.clean(); + aadBuffer = null; } @Override @@ -212,7 +148,6 @@ public int doFinal(final ByteBuffer input, final ByteBuffer output) // retrieve tag tag.put(inputFinal, inputFinal.length - getTagLen(), getTagLen()); - tag.flip(); } else { // if no buffered input, just use the input directly @@ -228,8 +163,8 @@ public int doFinal(final ByteBuffer input, final ByteBuffer output) // retrieve tag tag.put(input); - tag.flip(); } + tag.flip(); // set tag to EVP_Cipher for integrity verification in doFinal evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(), @@ -261,36 +196,6 @@ public int doFinal(final ByteBuffer input, final ByteBuffer output) return totalLen; } - @Override - public void clean() { - super.clean(); - aadBuffer = null; - } - - @Override - public void updateAAD(final byte[] aad) { - // must be called after initialized. - if (aadBuffer != null) { - aadBuffer.write(aad, 0, aad.length); - } else { - // update has already been called - throw new IllegalStateException - ("Update has been called; no more AAD data"); - } - } - - private void processAAD() { - if (aadBuffer != null && aadBuffer.size() > 0) { - OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), - 0, aadBuffer.size(), null, 0, 0); - aadBuffer = null; - } - } - - private int getTagLen() { - return tagBitLen < 0 ? DEFAULT_TAG_LEN : tagBitLen >> 3; - } - /** * Wraps of OpenSslNative.ctrl(long context, int type, int arg, byte[] data) * Since native interface EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) is generic, @@ -300,7 +205,7 @@ private int getTagLen() { * @param context The cipher context address * @param type CtrlValues * @param arg argument like a tag length - * @param data byte buffer or null + * @param data byte buffer or {@code null} * @return return 0 if there is any error, else return 1. */ private int evpCipherCtxCtrl(final long context, final int type, final int arg, final ByteBuffer data) { @@ -316,4 +221,96 @@ private int evpCipherCtxCtrl(final long context, final int type, final int arg, return 0; } } + + private int getTagLen() { + return tagBitLen < 0 ? DEFAULT_TAG_LEN : tagBitLen >> BITS_TO_BYTES_SHIFT_COUNT; + } + + @Override + public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + + if (aadBuffer == null) { + aadBuffer = new ByteArrayOutputStream(); + } else { + aadBuffer.reset(); + } + + this.cipherMode = mode; + final byte[] iv; + if (!(params instanceof GCMParameterSpec)) { + // other AlgorithmParameterSpec is not supported now. + throw new InvalidAlgorithmParameterException("Illegal parameters"); + } + final GCMParameterSpec gcmParam = (GCMParameterSpec) params; + iv = gcmParam.getIV(); + this.tagBitLen = gcmParam.getTLen(); + + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + inBuffer = new ByteArrayOutputStream(); + } + + context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); + } + + private void processAAD() { + if (aadBuffer != null && aadBuffer.size() > 0) { + OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), 0, aadBuffer.size(), null, 0, 0); + aadBuffer = null; + } + } + + @Override + public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) + throws ShortBufferException { + checkState(); + + processAAD(); + + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + // store internally until doFinal(decrypt) is called because + // spec mentioned that only return recovered data after tag + // is successfully verified + inBuffer.write(input, inputOffset, inputLen); + return 0; + } + return OpenSslNative.updateByteArray(context, input, inputOffset, + inputLen, output, outputOffset, output.length - outputOffset); + } + + @Override + public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException { + checkState(); + + processAAD(); + + final int len; + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + // store internally until doFinal(decrypt) is called because + // spec mentioned that only return recovered data after tag + // is successfully verified + final int inputLen = input.remaining(); + final byte[] inputBuf = new byte[inputLen]; + input.get(inputBuf, 0, inputLen); + inBuffer.write(inputBuf, 0, inputLen); + return 0; + } + len = OpenSslNative.update(context, input, input.position(), + input.remaining(), output, output.position(), + output.remaining()); + input.position(input.limit()); + output.position(output.position() + len); + + return len; + } + + @Override + public void updateAAD(final byte[] aad) { + // must be called after initialized. + if (aadBuffer == null) { + // update has already been called + throw new IllegalStateException("Update has been called; no more AAD data"); + } + aadBuffer.write(aad, 0, aad.length); + } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java index cb6136228..83b26be98 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java @@ -20,30 +20,59 @@ import java.nio.ByteBuffer; /** - * JNI interface of {@link OpenSsl} implementation. The native method in this - * class is defined in OpenSslNative.h (generated by javah). + * JNI implementation for OpenSSL 1.x called from {@link OpenSsl}. The native methods in this + * class are defined in OpenSslNative.h (generated by javah). */ -class OpenSslNative { +final class OpenSslNative { /** - * The private constructor of {@link OpenSslNative}. + * Cleans the context at native. + * + * @param context The cipher context address */ - private OpenSslNative() { - } + public static native void clean(long context); /** - * Declares a native method to initialize JNI field and method IDs. + * Allows various cipher specific parameters to be determined and set. + * + * it will call OpenSSL's API + * int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) + * In OpenSSL, data type of ptr can be char* or long*. Here, we map java's + * byte[] to native void*ptr. Note that the byte order is ByteOrder.nativeOrder. + * + * @param context The cipher context address + * @param type CtrlValues + * @param arg argument like a tag length + * @param data byte buffer or {@code null} + * @return return 0 if there is any error, else return 1. */ - public native static void initIDs(); + public static native int ctrl(long context, int type, int arg, byte[] data); /** - * Declares a native method to initialize the cipher context. + * Finishes a multiple-part operation. The data is encrypted or decrypted, + * depending on how this cipher was initialized. * - * @param algorithm The algorithm name of cipher - * @param padding The padding name of cipher - * @return the context address of cipher + * @param context The cipher context address + * @param output The byte buffer for the result + * @param offset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public static native int doFinal(long context, ByteBuffer output, + int offset, int maxOutputLength); + + /** + * Finishes a multiple-part operation. The data is encrypted or decrypted, + * depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param output The byte array for the result + * @param offset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output */ - public native static long initContext(int algorithm, int padding); + public static native int doFinalByteArray(long context, byte[] output, + int offset, int maxOutputLength); /** * Declares a native method to initialize the cipher context. @@ -56,9 +85,23 @@ private OpenSslNative() { * @param iv crypto iv * @return the context address of cipher */ - public native static long init(long context, int mode, int alg, + public static native long init(long context, int mode, int alg, int padding, byte[] key, byte[] iv); + /** + * Declares a native method to initialize the cipher context. + * + * @param algorithm The algorithm name of cipher + * @param padding The padding name of cipher + * @return the context address of cipher + */ + public static native long initContext(int algorithm, int padding); + + /** + * Declares a native method to initialize JNI field and method IDs. + */ + public static native void initIDs(); + /** * Continues a multiple-part encryption/decryption operation. The data is * encrypted or decrypted, depending on how this cipher was initialized. @@ -72,7 +115,7 @@ public native static long init(long context, int mode, int alg, * @param maxOutputLength The maximum length for output * @return The number of bytes stored in output */ - public native static int update(long context, ByteBuffer input, + public static native int update(long context, ByteBuffer input, int inputOffset, int inputLength, ByteBuffer output, int outputOffset, int maxOutputLength); @@ -89,7 +132,7 @@ public native static int update(long context, ByteBuffer input, * @param maxOutputLength The maximum length for output * @return The number of bytes stored in output */ - public native static int updateByteArray(long context, byte[] input, + public static native int updateByteArray(long context, byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength); @@ -106,57 +149,13 @@ public native static int updateByteArray(long context, byte[] input, * @param maxOutputLength The maximum length for output * @return The number of bytes stored in output */ - public native static int updateByteArrayByteBuffer(long context, byte[] input, + public static native int updateByteArrayByteBuffer(long context, byte[] input, int inputOffset, int inputLength, ByteBuffer output, int outputOffset, int maxOutputLength); /** - * Finishes a multiple-part operation. The data is encrypted or decrypted, - * depending on how this cipher was initialized. - * - * @param context The cipher context address - * @param output The byte buffer for the result - * @param offset The offset in output where the result is stored - * @param maxOutputLength The maximum length for output - * @return The number of bytes stored in output - */ - public native static int doFinal(long context, ByteBuffer output, - int offset, int maxOutputLength); - - /** - * Finishes a multiple-part operation. The data is encrypted or decrypted, - * depending on how this cipher was initialized. - * - * @param context The cipher context address - * @param output The byte array for the result - * @param offset The offset in output where the result is stored - * @param maxOutputLength The maximum length for output - * @return The number of bytes stored in output + * Hides this constructor from external access. */ - public native static int doFinalByteArray(long context, byte[] output, - int offset, int maxOutputLength); - - /** - * allows various cipher specific parameters to be determined and set. - * - * it will call OpenSSL's API - * int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) - * In OpenSSL, data type of ptr can be char* or long*. Here, we map java's - * byte[] to native void*ptr. Note that the byte order is ByteOrder.nativeOrder. - * - * @param context The cipher context address - * @param type CtrlValues - * @param arg argument like a tag length - * @param data byte buffer or null - * @return return 0 if there is any error, else return 1. - */ - public native static int ctrl(long context, int type, int arg, byte[] data); - - - /** - * Cleans the context at native. - * - * @param context The cipher context address - */ - public native static void clean(long context); + private OpenSslNative() { + } } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/LibreSsl20XNativeJna.java similarity index 63% rename from src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java rename to src/main/java/org/apache/commons/crypto/jna/LibreSsl20XNativeJna.java index e8ab05f43..66561fb8c 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java +++ b/src/main/java/org/apache/commons/crypto/jna/LibreSsl20XNativeJna.java @@ -26,7 +26,8 @@ import com.sun.jna.NativeLong; import com.sun.jna.ptr.PointerByReference; -class OpenSsl10XNativeJna { +// There is no OpenSSL 2.x version; this is for LibreSSL +final class LibreSsl20XNativeJna implements OpenSslInterfaceNativeJna { static final boolean INIT_OK; @@ -36,9 +37,8 @@ class OpenSsl10XNativeJna { boolean ok = false; Throwable thrown = null; try { - final String libName = System.getProperty(Crypto.CONF_PREFIX + OpenSsl10XNativeJna.class.getSimpleName(), - "crypto"); - OpenSslJna.debug("Native.register('%s')%n", libName); + final String libName = System.getProperty(Crypto.CONF_PREFIX + OpenSslNativeJna.class.getSimpleName(), "crypto"); + OpenSslJna.debug("Native.register('%s')", libName); Native.register(libName); ok = true; } catch (final Exception | UnsatisfiedLinkError e) { @@ -49,30 +49,62 @@ class OpenSsl10XNativeJna { } } + // Try to keep methods aligned across versions + /** - * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier + * Gets engine by id. + * + * @param id + * engine id. + * @return engine instance */ - public static native NativeLong SSLeay(); + public static native PointerByReference ENGINE_by_id(String id); /** - * Retrieves version/build information about OpenSSL library. + * Cleanups before program exit, it will avoid memory leaks. * - * @param type - * type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON... - * @return A pointer to a constant string describing the version of the OpenSSL library or - * giving information about the library build. + * @return 0 on success, 1 otherwise. */ - public static native String SSLeay_version(int type); + public static native int ENGINE_cleanup(); /** - * Registers the error strings for all libcrypto functions. + * Releases all functional references. + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. */ - public static native void ERR_load_crypto_strings(); + public static native int ENGINE_finish(PointerByReference e); /** - * @return the earliest error code from the thread's error queue without modifying it. + * Frees the structural reference + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. */ - public static native NativeLong ERR_peek_error(); + public static native int ENGINE_free(PointerByReference e); + + /** + * Obtains a functional reference from an existing structural reference. + * + * @param e + * engine reference + * @return zero if the ENGINE was not already operational and couldn't be successfully + * initialized + */ + public static native int ENGINE_init(PointerByReference e); + + /** + * Sets the engine as the default for random number generation. + * + * @param e + * engine reference. + * @param flags + * ENGINE_METHOD_RAND. + * @return zero if failed. + */ + public static native int ENGINE_set_default(PointerByReference e, int flags); /** * Generates a human-readable string representing the error code e. @@ -87,62 +119,106 @@ class OpenSsl10XNativeJna { */ public static native String ERR_error_string(NativeLong err, char[] null_); + // TODO: NOT USED? /** - * Creates a cipher context. - * - * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure. - */ - public static native PointerByReference EVP_CIPHER_CTX_new(); - - /** - * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset - * - * @param p - * cipher context + * Registers the error strings for all libcrypto functions. */ - public static native void EVP_CIPHER_CTX_init(PointerByReference p); + public static native void ERR_load_crypto_strings(); /** - * Enables or disables padding - * - * @param c - * cipher context - * @param pad - * If the pad parameter is zero then no padding is performed - * @return always returns 1 + * @return the earliest error code from the thread's error queue without modifying it. */ - public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad); + public static native NativeLong ERR_peek_error(); /** - * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode + * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode. */ public static native PointerByReference EVP_aes_128_cbc(); /** - * @return an OpenSSL AES EVP cipher instance with a 128-bit key CTR mode + * @return an OpenSSL AES EVP cipher instance with a 128-bit key CTR mode. */ public static native PointerByReference EVP_aes_128_ctr(); /** - * @return an OpenSSL AES EVP cipher instance with a 192-bit key CBC mode + * @return an OpenSSL AES EVP cipher instance with a 192-bit key CBC mode. */ public static native PointerByReference EVP_aes_192_cbc(); /** - * @return an OpenSSL AES EVP cipher instance with a 192-bit key CTR mode + * @return an OpenSSL AES EVP cipher instance with a 192-bit key CTR mode. */ public static native PointerByReference EVP_aes_192_ctr(); /** - * @return an OpenSSL AES EVP cipher instance with a 256-bit key CBC mode + * @return an OpenSSL AES EVP cipher instance with a 256-bit key CBC mode. */ public static native PointerByReference EVP_aes_256_cbc(); /** - * @return an OpenSSL AES EVP cipher instance with a 256-bit key CTR mode + * @return an OpenSSL AES EVP cipher instance with a 256-bit key CTR mode. */ public static native PointerByReference EVP_aes_256_ctr(); + /** + * Clears all information from a cipher context and free up any allocated * memory associate + * with it. + * + * @param c + * openssl evp cipher + */ + public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c); + + /** + * Clears all information from a cipher context and free up any allocated memory associate with + * it, including ctx itself. + * + * @param c + * openssl evp cipher + */ + public static native void EVP_CIPHER_CTX_free(PointerByReference c); + + // TODO: NOT USED? + /** + * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset. + * + * @param p + * cipher context + */ + public static native void EVP_CIPHER_CTX_init(PointerByReference p); + + /** + * Creates a cipher context. + * + * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure. + */ + public static native PointerByReference EVP_CIPHER_CTX_new(); + + /** + * Enables or disables padding. + * + * @param c + * cipher context. + * @param pad + * If the pad parameter is zero then no padding is performed. + * @return always returns 1 + */ + public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad); + + /** + * Finishes a multiple-part operation. + * + * @param ctx + * cipher context + * @param bout + * output byte buffer + * @param outl + * output length + * @return 1 for success and 0 for failure. + */ + public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout, + int[] outl); + /** * Init a cipher. * @@ -163,6 +239,8 @@ class OpenSsl10XNativeJna { public static native int EVP_CipherInit_ex(PointerByReference ctx, PointerByReference cipher, PointerByReference impl, byte[] key, byte[] iv, int enc); + // ENGINE API: https://www.openssl.org/docs/man1.0.2/man3/engine.html + /** * Continues a multiple-part encryption/decryption operation. * @@ -182,120 +260,187 @@ public static native int EVP_CipherUpdate(PointerByReference ctx, ByteBuffer bou ByteBuffer in, int inl); /** - * Finishes a multiple-part operation. + * Generates random data. * - * @param ctx - * cipher context - * @param bout - * output byte buffer - * @param outl - * output length - * @return 1 for success and 0 for failure. - */ - public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout, - int[] outl); - - /** - * Clears all information from a cipher context and free up any allocated memory associate with - * it, including ctx itself. - * - * @param c - * openssl evp cipher - */ - public static native void EVP_CIPHER_CTX_free(PointerByReference c); - - /** - * Clears all information from a cipher context and free up any allocated * memory associate - * with it. - * - * @param c - * openssl evp cipher + * @param buf + * the bytes for generated random. + * @param num + * buffer length. + * @return 1 on success, 0 otherwise. */ - public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c); + public static native int RAND_bytes(ByteBuffer buf, int num); // Random generator /** - * OpenSSL uses for random number generation + * OpenSSL uses for random number generation. * - * @return pointers to the respective methods + * @return pointers to the respective methods. */ public static native PointerByReference RAND_get_rand_method(); /** * OpenSSL uses for random number generation. * - * @return pointers to the respective methods + * @return pointers to the respective methods. */ public static native PointerByReference RAND_SSLeay(); /** - * Generates random data - * - * @param buf - * the bytes for generated random. - * @param num - * buffer length - * @return 1 on success, 0 otherwise. + * TODO (does not appear to be used yet) + * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier */ - public static native int RAND_bytes(ByteBuffer buf, int num); + public static native NativeLong SSLeay(); /** - * Releases all functional references. + * Retrieves version/build information about OpenSSL library. + * This is returned by {@link OpenSslNativeJna#OpenSSLVersion(int)} * - * @param e - * engine reference. - * @return 0 on success, 1 otherwise. + * @param type + * type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON... + * @return A pointer to a constant string describing the version of the OpenSSL library or + * giving information about the library build. */ - public static native int ENGINE_finish(PointerByReference e); + public static native String SSLeay_version(int type); - /** - * Frees the structural reference - * - * @param e - * engine reference. - * @return 0 on success, 1 otherwise. - */ - public static native int ENGINE_free(PointerByReference e); + @Override + public PointerByReference _ENGINE_by_id(final String string) { + return ENGINE_by_id(string); + } - /** - * Cleanups before program exit, it will avoid memory leaks. - * - * @return 0 on success, 1 otherwise. - */ - public static native int ENGINE_cleanup(); + @Override + public int _ENGINE_cleanup() { + return ENGINE_cleanup(); + } - /** - * Obtains a functional reference from an existing structural reference. - * - * @param e - * engine reference - * @return zero if the ENGINE was not already operational and couldn't be successfully - * initialized - */ - public static native int ENGINE_init(PointerByReference e); + @Override + public int _ENGINE_finish(final PointerByReference rdrandEngine) { + return ENGINE_finish(rdrandEngine); + } - /** - * Sets the engine as the default for random number generation. - * - * @param e - * engine reference - * @param flags - * ENGINE_METHOD_RAND - * @return zero if failed. - */ - public static native int ENGINE_set_default(PointerByReference e, int flags); + @Override + public int _ENGINE_free(final PointerByReference rdrandEngine) { + return ENGINE_free(rdrandEngine); + } - /** - * Gets engine by id - * - * @param id - * engine id - * @return engine instance - */ - public static native PointerByReference ENGINE_by_id(String id); + @Override + public int _ENGINE_init(final PointerByReference rdrandEngine) { + return ENGINE_init(rdrandEngine); + } - /** - * Initializes the engine. - */ - public static native void ENGINE_load_rdrand(); + @Override + public void _ENGINE_load_rdrand() { + // Not available + } + + @Override + public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) { + return ENGINE_set_default(rdrandEngine, flags); + } + + @Override + public String _ERR_error_string(final NativeLong err, final char[] buff) { + return ERR_error_string(err, buff); + } + + @Override + public NativeLong _ERR_peek_error() { + return ERR_peek_error(); + } + + @Override + public PointerByReference _EVP_aes_128_cbc() { + return EVP_aes_128_cbc(); + } + + @Override + public PointerByReference _EVP_aes_128_ctr() { + return EVP_aes_128_ctr(); + } + + @Override + public PointerByReference _EVP_aes_192_cbc() { + return EVP_aes_192_cbc(); + } + + @Override + public PointerByReference _EVP_aes_192_ctr() { + return EVP_aes_192_ctr(); + } + + @Override + public PointerByReference _EVP_aes_256_cbc() { + return EVP_aes_256_cbc(); + } + + @Override + public PointerByReference _EVP_aes_256_ctr() { + return EVP_aes_256_ctr(); + } + + @Override + public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) { + EVP_CIPHER_CTX_cleanup(context); + } + + @Override + public void _EVP_CIPHER_CTX_free(final PointerByReference context) { + EVP_CIPHER_CTX_free(context); + } + + @Override + public PointerByReference _EVP_CIPHER_CTX_new() { + return EVP_CIPHER_CTX_new(); + } + + @Override + public int _EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding) { + return EVP_CIPHER_CTX_set_padding(context, padding); + } + + @Override + public int _EVP_CipherFinal_ex(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen) { + return EVP_CipherFinal_ex(context, outBuffer, outlen); + } + + @Override + public int _EVP_CipherInit_ex(final PointerByReference context, final PointerByReference algo, final PointerByReference impl, final byte[] encoded, + final byte[] iv, final int cipherMode) { + return EVP_CipherInit_ex(context, algo, impl, encoded, iv, cipherMode); + } + + @Override + public int _EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen, final ByteBuffer inBuffer, + final int remaining) { + return EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, remaining); + } + + @Override + public Throwable _INIT_ERROR() { + return INIT_ERROR; + } + + @Override + public boolean _INIT_OK() { + return INIT_OK; + } + + @Override + public String _OpenSSL_version(final int i) { + return SSLeay_version(i); + } + + @Override + public int _RAND_bytes(final ByteBuffer buf, final int length) { + return RAND_bytes(buf, length) ; + } + + @Override + public PointerByReference _RAND_get_rand_method() { + return RAND_get_rand_method(); + } + + @Override + public PointerByReference _RAND_SSLeay() { + return RAND_SSLeay(); + } } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java index 1c81235cb..fcecc2830 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java @@ -26,7 +26,7 @@ import com.sun.jna.NativeLong; import com.sun.jna.ptr.PointerByReference; -class OpenSsl11XNativeJna { +final class OpenSsl11XNativeJna implements OpenSslInterfaceNativeJna { static final boolean INIT_OK; @@ -36,9 +36,8 @@ class OpenSsl11XNativeJna { boolean ok = false; Throwable thrown = null; try { - final String libName = System.getProperty(Crypto.CONF_PREFIX + OpenSsl11XNativeJna.class.getSimpleName(), - "crypto"); - OpenSslJna.debug("Native.register('%s')%n", libName); + final String libName = System.getProperty(Crypto.CONF_PREFIX + OpenSslNativeJna.class.getSimpleName(), "crypto"); + OpenSslJna.debug("Native.register('%s')", libName); Native.register(libName); ok = true; } catch (final Exception | UnsatisfiedLinkError e) { @@ -49,10 +48,55 @@ class OpenSsl11XNativeJna { } } + // Try to keep methods aligned across versions + /** - * @return the earliest error code from the thread's error queue without modifying it. + * Gets engine by id + * + * @param id + * engine id + * @return engine instance */ - public static native NativeLong ERR_peek_error(); + public static native PointerByReference ENGINE_by_id(String id); + + /** + * Releases all functional references. + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. + */ + public static native int ENGINE_finish(PointerByReference e); + + /** + * Frees the structural reference + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. + */ + public static native int ENGINE_free(PointerByReference e); + + /** + * Obtains a functional reference from an existing structural reference. + * + * @param e + * engine reference + * @return zero if the ENGINE was not already operational and couldn't be successfully + * initialized + */ + public static native int ENGINE_init(PointerByReference e); + + /** + * Sets the engine as the default for random number generation. + * + * @param e + * engine reference + * @param flags + * ENGINE_METHOD_RAND + * @return zero if failed. + */ + public static native int ENGINE_set_default(PointerByReference e, int flags); /** * Generates a human-readable string representing the error code e. @@ -68,22 +112,9 @@ class OpenSsl11XNativeJna { public static native String ERR_error_string(NativeLong err, char[] null_); /** - * Creates a cipher context. - * - * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure. - */ - public static native PointerByReference EVP_CIPHER_CTX_new(); - - /** - * Enables or disables padding - * - * @param c - * cipher context - * @param pad - * If the pad parameter is zero then no padding is performed - * @return always returns 1 + * @return the earliest error code from the thread's error queue without modifying it. */ - public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad); + public static native NativeLong ERR_peek_error(); /** * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode @@ -115,6 +146,58 @@ class OpenSsl11XNativeJna { */ public static native PointerByReference EVP_aes_256_ctr(); + /** + * Clears all information from a cipher context and free up any allocated memory associate with + * it, including ctx itself. + * + * @param c + * openssl evp cipher + */ + public static native void EVP_CIPHER_CTX_free(PointerByReference c); + + /** + * Creates a cipher context. + * + * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure. + */ + public static native PointerByReference EVP_CIPHER_CTX_new(); + + /** + * Clears all information from a cipher context and free up any allocated * memory associate + * with it. + * + * @param c + * openssl evp cipher + */ + + /** + * Enables or disables padding + * + * @param c + * cipher context + * @param pad + * If the pad parameter is zero then no padding is performed + * @return always returns 1 + */ + public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad); + + /** + * Finishes a multiple-part operation. + * + * @param ctx + * cipher context + * @param bout + * output byte buffer + * @param outl + * output length + * @return 1 for success and 0 for failure. + */ + public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout, + int[] outl); + + // ENGINE API: https://www.openssl.org/docs/man1.1.1/man3/ENGINE_add.html + // (The above page includes all the ENGINE functions used below) + /** * Init a cipher. * @@ -154,43 +237,15 @@ public static native int EVP_CipherUpdate(PointerByReference ctx, ByteBuffer bou ByteBuffer in, int inl); /** - * Finishes a multiple-part operation. - * - * @param ctx - * cipher context - * @param bout - * output byte buffer - * @param outl - * output length - * @return 1 for success and 0 for failure. - */ - public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout, - int[] outl); - - /** - * Clears all information from a cipher context and free up any allocated memory associate with - * it, including ctx itself. - * - * @param c - * openssl evp cipher - */ - public static native void EVP_CIPHER_CTX_free(PointerByReference c); - - /** - * Clears all information from a cipher context and free up any allocated * memory associate - * with it. - * - * @param c - * openssl evp cipher - */ - - // Random generator - /** - * OpenSSL uses for random number generation + * Retrieves version/build information about OpenSSL library. * - * @return pointers to the respective methods + * @see OpenSSL_version + * @param type + * type can be OPENSSL_VERSION, OPENSSL_CFLAGS, OPENSSL_BUILT_ON... + * @return A pointer to a constant string describing the version of the OpenSSL library or + * giving information about the library build. */ - public static native PointerByReference RAND_get_rand_method(); + public static native String OpenSSL_version(int type); /** * Generates random data @@ -203,61 +258,154 @@ public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer b */ public static native int RAND_bytes(ByteBuffer buf, int num); + // Random generator /** - * Releases all functional references. + * OpenSSL uses for random number generation * - * @param e - * engine reference. - * @return 0 on success, 1 otherwise. + * @return pointers to the respective methods */ - public static native int ENGINE_finish(PointerByReference e); + public static native PointerByReference RAND_get_rand_method(); - /** - * Frees the structural reference - * - * @param e - * engine reference. - * @return 0 on success, 1 otherwise. - */ - public static native int ENGINE_free(PointerByReference e); + @Override + public PointerByReference _ENGINE_by_id(final String string) { + return ENGINE_by_id(string); + } - /** - * Obtains a functional reference from an existing structural reference. - * - * @param e - * engine reference - * @return zero if the ENGINE was not already operational and couldn't be successfully - * initialized - */ - public static native int ENGINE_init(PointerByReference e); + @Override + public int _ENGINE_cleanup() { + return 0; // Not available + } - /** - * Sets the engine as the default for random number generation. - * - * @param e - * engine reference - * @param flags - * ENGINE_METHOD_RAND - * @return zero if failed. - */ - public static native int ENGINE_set_default(PointerByReference e, int flags); + @Override + public int _ENGINE_finish(final PointerByReference rdrandEngine) { + return ENGINE_finish(rdrandEngine); + } - /** - * Gets engine by id - * - * @param id - * engine id - * @return engine instance - */ - public static native PointerByReference ENGINE_by_id(String id); + @Override + public int _ENGINE_free(final PointerByReference rdrandEngine) { + return ENGINE_free(rdrandEngine); + } + + @Override + public int _ENGINE_init(final PointerByReference rdrandEngine) { + return ENGINE_init(rdrandEngine); + } + + @Override + public void _ENGINE_load_rdrand() { + // Not available + } + + @Override + public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) { + return ENGINE_set_default(rdrandEngine, flags); + } + + @Override + public String _ERR_error_string(final NativeLong err, final char[] buff) { + return ERR_error_string(err, buff); + } + + @Override + public NativeLong _ERR_peek_error() { + return ERR_peek_error(); + } + + @Override + public PointerByReference _EVP_aes_128_cbc() { + return EVP_aes_128_cbc(); + } + + @Override + public PointerByReference _EVP_aes_128_ctr() { + return EVP_aes_128_ctr(); + } + + @Override + public PointerByReference _EVP_aes_192_cbc() { + return EVP_aes_192_cbc(); + } + + @Override + public PointerByReference _EVP_aes_192_ctr() { + return EVP_aes_192_ctr(); + } + + @Override + public PointerByReference _EVP_aes_256_cbc() { + return EVP_aes_256_cbc(); + } + + @Override + public PointerByReference _EVP_aes_256_ctr() { + return EVP_aes_256_ctr(); + } + + @Override + public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) { + // Not available + } + + @Override + public void _EVP_CIPHER_CTX_free(final PointerByReference context) { + EVP_CIPHER_CTX_free(context); + } + + @Override + public PointerByReference _EVP_CIPHER_CTX_new() { + return EVP_CIPHER_CTX_new(); + } + + @Override + public int _EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding) { + return EVP_CIPHER_CTX_set_padding(context, padding); + } + + @Override + public int _EVP_CipherFinal_ex(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen) { + return EVP_CipherFinal_ex(context, outBuffer, outlen); + } + + @Override + public int _EVP_CipherInit_ex(final PointerByReference context, final PointerByReference algo, final PointerByReference impl, final byte[] encoded, + final byte[] iv, final int cipherMode) { + return EVP_CipherInit_ex(context, algo, impl, encoded, iv, cipherMode); + } + + @Override + public int _EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen, final ByteBuffer inBuffer, + final int remaining) { + return EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, remaining); + } + + @Override + public Throwable _INIT_ERROR() { + return INIT_ERROR; + } + + @Override + public boolean _INIT_OK() { + return INIT_OK; + } + + @Override + public String _OpenSSL_version(final int i) { + return OpenSSL_version(i); + } + + @Override + public int _RAND_bytes(final ByteBuffer buf, final int length) { + return RAND_bytes(buf, length) ; + } + + @Override + public PointerByReference _RAND_get_rand_method() { + return RAND_get_rand_method(); + } + + @Override + public PointerByReference _RAND_SSLeay() { + return null; // Not available + } - /** - * Retrieves version/build information about OpenSSL library. - * - * @param type - * type can be OPENSSL_VERSION, OPENSSL_CFLAGS, OPENSSL_BUILT_ON... - * @return A pointer to a constant string describing the version of the OpenSSL library or - * giving information about the library build. - */ - public static native String OpenSSL_version(int type); } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl30XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSsl30XNativeJna.java new file mode 100644 index 000000000..ff2aeaf44 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSsl30XNativeJna.java @@ -0,0 +1,413 @@ + /* + * 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. + */ + +package org.apache.commons.crypto.jna; + +import java.nio.ByteBuffer; + +import org.apache.commons.crypto.Crypto; + +import com.sun.jna.Native; +import com.sun.jna.NativeLong; +import com.sun.jna.ptr.PointerByReference; + +// Currently this is the same as OpenSsl11XNativeJna +// This may change if additional methods need to be added +final class OpenSsl30XNativeJna implements OpenSslInterfaceNativeJna { + + static final boolean INIT_OK; + + static final Throwable INIT_ERROR; + + static { + boolean ok = false; + Throwable thrown = null; + try { + final String libName = System.getProperty(Crypto.CONF_PREFIX + OpenSslNativeJna.class.getSimpleName(), "crypto"); + OpenSslJna.debug("Native.register('%s')", libName); + Native.register(libName); + ok = true; + } catch (final Exception | UnsatisfiedLinkError e) { + thrown = e; + } finally { + INIT_OK = ok; + INIT_ERROR = thrown; + } + } + + // Try to keep methods aligned across versions + + /** + * Gets engine by id + * + * @param id + * engine id + * @return engine instance + */ + public static native PointerByReference ENGINE_by_id(String id); + + /** + * Releases all functional references. + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. + */ + public static native int ENGINE_finish(PointerByReference e); + + /** + * Frees the structural reference + * + * @param e + * engine reference. + * @return 0 on success, 1 otherwise. + */ + public static native int ENGINE_free(PointerByReference e); + + /** + * Obtains a functional reference from an existing structural reference. + * + * @param e + * engine reference + * @return zero if the ENGINE was not already operational and couldn't be successfully + * initialized + */ + public static native int ENGINE_init(PointerByReference e); + + /** + * Sets the engine as the default for random number generation. + * + * @param e + * engine reference + * @param flags + * ENGINE_METHOD_RAND + * @return zero if failed. + */ + public static native int ENGINE_set_default(PointerByReference e, int flags); + + /** + * Generates a human-readable string representing the error code e. + * + * @see ERR_error_string + * + * @param err + * the error code + * @param null_ + * buf is NULL, the error string is placed in a static buffer + * @return the human-readable error messages. + */ + public static native String ERR_error_string(NativeLong err, char[] null_); + + /** + * @return the earliest error code from the thread's error queue without modifying it. + */ + public static native NativeLong ERR_peek_error(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode + */ + public static native PointerByReference EVP_aes_128_cbc(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 128-bit key CTR mode + */ + public static native PointerByReference EVP_aes_128_ctr(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 192-bit key CBC mode + */ + public static native PointerByReference EVP_aes_192_cbc(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 192-bit key CTR mode + */ + public static native PointerByReference EVP_aes_192_ctr(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 256-bit key CBC mode + */ + public static native PointerByReference EVP_aes_256_cbc(); + + /** + * @return an OpenSSL AES EVP cipher instance with a 256-bit key CTR mode + */ + public static native PointerByReference EVP_aes_256_ctr(); + + /** + * Clears all information from a cipher context and free up any allocated memory associate with + * it, including ctx itself. + * + * @param c + * openssl evp cipher + */ + public static native void EVP_CIPHER_CTX_free(PointerByReference c); + + /** + * Creates a cipher context. + * + * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure. + */ + public static native PointerByReference EVP_CIPHER_CTX_new(); + + /** + * Clears all information from a cipher context and free up any allocated * memory associate + * with it. + * + * @param c + * openssl evp cipher + */ + + /** + * Enables or disables padding + * + * @param c + * cipher context + * @param pad + * If the pad parameter is zero then no padding is performed + * @return always returns 1 + */ + public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad); + + /** + * Finishes a multiple-part operation. + * + * @param ctx + * cipher context + * @param bout + * output byte buffer + * @param outl + * output length + * @return 1 for success and 0 for failure. + */ + public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout, + int[] outl); + + // ENGINE API: https://www.openssl.org/docs/man1.1.1/man3/ENGINE_add.html + // (The above page includes all the ENGINE functions used below) + + /** + * Init a cipher. + * + * @param ctx + * cipher context + * @param cipher + * evp cipher instance + * @param impl + * engine + * @param key + * key + * @param iv + * iv + * @param enc + * 1 for encryption, 0 for decryption + * @return 1 for success and 0 for failure. + */ + public static native int EVP_CipherInit_ex(PointerByReference ctx, PointerByReference cipher, + PointerByReference impl, byte[] key, byte[] iv, int enc); + + /** + * Continues a multiple-part encryption/decryption operation. + * + * @param ctx + * cipher context + * @param bout + * output byte buffer + * @param outl + * output length + * @param in + * input byte buffer + * @param inl + * input length + * @return 1 for success and 0 for failure. + */ + public static native int EVP_CipherUpdate(PointerByReference ctx, ByteBuffer bout, int[] outl, + ByteBuffer in, int inl); + + /** + * Retrieves version/build information about OpenSSL library. + * + * @see OpenSSL_version + * @param type + * type can be OPENSSL_VERSION, OPENSSL_CFLAGS, OPENSSL_BUILT_ON... + * @return A pointer to a constant string describing the version of the OpenSSL library or + * giving information about the library build. + */ + public static native String OpenSSL_version(int type); + + /** + * Generates random data + * + * @param buf + * the bytes for generated random. + * @param num + * buffer length + * @return 1 on success, 0 otherwise. + */ + public static native int RAND_bytes(ByteBuffer buf, int num); + + // Random generator + /** + * OpenSSL uses for random number generation + * + * @return pointers to the respective methods + */ + public static native PointerByReference RAND_get_rand_method(); + + @Override + public PointerByReference _ENGINE_by_id(final String string) { + return ENGINE_by_id(string); + } + + @Override + public int _ENGINE_cleanup() { + return 0; // Not available + } + + @Override + public int _ENGINE_finish(final PointerByReference rdrandEngine) { + return ENGINE_finish(rdrandEngine); + } + + @Override + public int _ENGINE_free(final PointerByReference rdrandEngine) { + return ENGINE_free(rdrandEngine); + } + + @Override + public int _ENGINE_init(final PointerByReference rdrandEngine) { + return ENGINE_init(rdrandEngine); + } + + @Override + public void _ENGINE_load_rdrand() { + // Not available + } + + @Override + public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) { + return ENGINE_set_default(rdrandEngine, flags); + } + + @Override + public String _ERR_error_string(final NativeLong err, final char[] buff) { + return ERR_error_string(err, buff); + } + + @Override + public NativeLong _ERR_peek_error() { + return ERR_peek_error(); + } + + @Override + public PointerByReference _EVP_aes_128_cbc() { + return EVP_aes_128_cbc(); + } + + @Override + public PointerByReference _EVP_aes_128_ctr() { + return EVP_aes_128_ctr(); + } + + @Override + public PointerByReference _EVP_aes_192_cbc() { + return EVP_aes_192_cbc(); + } + + @Override + public PointerByReference _EVP_aes_192_ctr() { + return EVP_aes_192_ctr(); + } + + @Override + public PointerByReference _EVP_aes_256_cbc() { + return EVP_aes_256_cbc(); + } + + @Override + public PointerByReference _EVP_aes_256_ctr() { + return EVP_aes_256_ctr(); + } + + @Override + public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) { + // Not available + } + + @Override + public void _EVP_CIPHER_CTX_free(final PointerByReference context) { + EVP_CIPHER_CTX_free(context); + } + + @Override + public PointerByReference _EVP_CIPHER_CTX_new() { + return EVP_CIPHER_CTX_new(); + } + + @Override + public int _EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding) { + return EVP_CIPHER_CTX_set_padding(context, padding); + } + + @Override + public int _EVP_CipherFinal_ex(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen) { + return EVP_CipherFinal_ex(context, outBuffer, outlen); + } + + @Override + public int _EVP_CipherInit_ex(final PointerByReference context, final PointerByReference algo, final PointerByReference impl, final byte[] encoded, + final byte[] iv, final int cipherMode) { + return EVP_CipherInit_ex(context, algo, impl, encoded, iv, cipherMode); + } + + @Override + public int _EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen, final ByteBuffer inBuffer, + final int remaining) { + return EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, remaining); + } + + @Override + public Throwable _INIT_ERROR() { + return INIT_ERROR; + } + + @Override + public boolean _INIT_OK() { + return INIT_OK; + } + + @Override + public String _OpenSSL_version(final int i) { + return OpenSSL_version(i); + } + + @Override + public int _RAND_bytes(final ByteBuffer buf, final int length) { + return RAND_bytes(buf, length) ; + } + + @Override + public PointerByReference _RAND_get_rand_method() { + return RAND_get_rand_method(); + } + + @Override + public PointerByReference _RAND_SSLeay() { + return null; // Not available + } + +} diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java new file mode 100644 index 000000000..a9c895fc5 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java @@ -0,0 +1,97 @@ + /* + * 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. + */ + +package org.apache.commons.crypto.jna; + +import java.nio.ByteBuffer; + +import com.sun.jna.NativeLong; +import com.sun.jna.ptr.PointerByReference; + +/** + * This interface defines the API for the native code. + *+ * All methods are listed here; individual implementations may not support them all. + *
+ */ +interface OpenSslInterfaceNativeJna { + + PointerByReference _ENGINE_by_id(final String string); + + /** + * TODO Appears to be deprecated as of OpenSSL 1.1.0. + * + * @return See OpenSSL. + */ + int _ENGINE_cleanup(); + + int _ENGINE_finish(final PointerByReference rdrandEngine); + + int _ENGINE_free(final PointerByReference rdrandEngine); + + int _ENGINE_init(final PointerByReference rdrandEngine); + + void _ENGINE_load_rdrand(); + + int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags); + + String _ERR_error_string(final NativeLong err, final char[] buff); + + NativeLong _ERR_peek_error(); + + PointerByReference _EVP_aes_128_cbc(); + + PointerByReference _EVP_aes_128_ctr(); + + PointerByReference _EVP_aes_192_cbc(); + + PointerByReference _EVP_aes_192_ctr(); + + PointerByReference _EVP_aes_256_cbc(); + + PointerByReference _EVP_aes_256_ctr(); + + void _EVP_CIPHER_CTX_cleanup(final PointerByReference context); + + void _EVP_CIPHER_CTX_free(final PointerByReference context); + + PointerByReference _EVP_CIPHER_CTX_new(); + + int _EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding); + + int _EVP_CipherFinal_ex(final PointerByReference context, final ByteBuffer outBuffer, + final int[] outlen); + + int _EVP_CipherInit_ex(final PointerByReference context, final PointerByReference algo, + final PointerByReference impl, final byte[] encoded, final byte[] iv, final int cipherMode); + + int _EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer, + final int[] outlen, final ByteBuffer inBuffer, final int remaining); + + Throwable _INIT_ERROR(); + + boolean _INIT_OK(); + + String _OpenSSL_version(final int i); + + int _RAND_bytes(final ByteBuffer buf, final int length); + + PointerByReference _RAND_get_rand_method(); + + PointerByReference _RAND_SSLeay(); +} diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJna.java index 41d3299f2..66238c0ce 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJna.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJna.java @@ -13,41 +13,49 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto.jna; +import java.util.Objects; + import org.apache.commons.crypto.Crypto; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.random.CryptoRandom; +import org.apache.commons.crypto.utils.Utils; /** - * Public class to give access to the package protected class objects + * Provides access to package protected class objects and a {@link #main(String[])} method that prints version information. */ public final class OpenSslJna { + private final static String KEY_DEBUG = Crypto.CONF_PREFIX + "debug"; + /** * Logs debug messages. * * @param format See {@link String#format(String, Object...)}. * @param args See {@link String#format(String, Object...)}. */ - static void debug(final String format, final Object... args) { + static void debug(final Object format, final Object... args) { // TODO Find a better way to do this later. - if (Boolean.getBoolean(Crypto.CONF_PREFIX + "debug")) { - System.out.println(String.format(format, args)); + if (Boolean.getBoolean(KEY_DEBUG)) { + System.out.println(String.format(Objects.toString(format), args)); } } /** - * @return The cipher class of JNA implementation + * Gets the cipher class of JNA implementation. + * + * @return The cipher class of JNA implementation. */ public static Class extends CryptoCipher> getCipherClass() { return OpenSslJnaCipher.class; } /** - * @return The random class of JNA implementation + * Gets the random class of JNA implementation. + * + * @return The random class of JNA implementation. */ public static Class extends CryptoRandom> getRandomClass() { return OpenSslJnaCryptoRandom.class; @@ -65,26 +73,47 @@ private static void info(final String format, final Object... args) { } /** - * @return the error of JNA + * Gets the error from the JNA. + * + * @return the error from the JNA. */ public static Throwable initialisationError() { return OpenSslNativeJna.INIT_ERROR; } /** - * @return true if JNA native loads successfully + * Tests whether NA native loads successfully. + * + * @return {@code true} if JNA native loaded successfully. */ public static boolean isEnabled() { return OpenSslNativeJna.INIT_OK; } - public static void main(final String[] args) { - info("isEnabled(): %s", isEnabled()); + /** + * Main API. + * + * @param args command line arguments. + * @throws Throwable Throws value from {@link #initialisationError()}. + */ + public static void main(final String[] args) throws Throwable { + // These are used by JNA code if defined: + info("jna.library.path=%s", System.getProperty("jna.library.path")); + info("jna.platform.library.path=%s", System.getProperty("jna.platform.library.path")); + info("commons.crypto.OpenSslNativeJna=%s\n", System.getProperty("commons.crypto.OpenSslNativeJna")); + // can set jna.debug_load=true for loading info + info(Crypto.getComponentName() + " OpenSslJna: enabled = %s, version = 0x%08X", isEnabled(), OpenSslNativeJna.VERSION); final Throwable initialisationError = initialisationError(); - info("initialisationError(): %s", initialisationError); if (initialisationError != null) { + info("initialisationError(): %s", initialisationError); System.err.flush(); // helpful for stack traces to not mix in other output. - initialisationError.printStackTrace(); + throw initialisationError; // propagate to make error obvious + } + for (int i = 0; i <= Utils.OPENSSL_VERSION_MAX_INDEX; i++) { + String data = OpenSSLVersion(i); + if (!"not available".equals(data)) { + info("OpenSSLVersion(%d): %s", i, data); + } } } @@ -98,4 +127,14 @@ public static void main(final String[] args) { static String OpenSSLVersion(final int type) { return OpenSslNativeJna.OpenSSLVersion(type); } + + /** + * Constructs a new instance. + * + * @deprecated Will be private in the next major release. + */ + @Deprecated + public OpenSslJna() { + // empty + } } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java index d1e8ab2cb..5819f69e3 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java @@ -1,20 +1,20 @@ /* - * 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. - */ + * 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. + */ package org.apache.commons.crypto.jna; import java.nio.ByteBuffer; @@ -26,17 +26,16 @@ import java.security.spec.AlgorithmParameterSpec; import java.util.Objects; import java.util.Properties; -import java.util.StringTokenizer; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.cipher.CryptoCipherFactory; +import org.apache.commons.crypto.utils.Transformation; import com.sun.jna.NativeLong; import com.sun.jna.ptr.PointerByReference; @@ -44,13 +43,40 @@ /** * Implements the CryptoCipher using JNA into OpenSSL. */ -class OpenSslJnaCipher implements CryptoCipher { +final class OpenSslJnaCipher implements CryptoCipher { + + private static final int AES_128_ENCODED_KEYLEN = 16; + private static final int AES_192_ENCODED_KEYLEN = 24; + private static final int AES_256_ENCODED_KEYLEN = 32; + + /** + * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding. + */ + private enum AlgorithmMode { + AES_CTR, AES_CBC; + /** + * Gets the AlgorithmMode instance. + * + * @param algorithm the algorithm name + * @param mode the mode name + * @return the AlgorithmMode instance + * @throws NoSuchAlgorithmException if the algorithm is not support + */ + static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException { + try { + return AlgorithmMode.valueOf(algorithm + "_" + mode); + } catch (final Exception e) { + throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode); + } + } + } private PointerByReference algo; private final PointerByReference context; - private final AlgorithmMode algMode; + private final AlgorithmMode algorithmMode; private final int padding; private final String transformation; + private final int IV_LENGTH = 16; /** @@ -66,18 +92,113 @@ public OpenSslJnaCipher(final Properties props, final String transformation) // throw new GeneralSecurityException("Could not enable JNA access", OpenSslJna.initialisationError()); } this.transformation = transformation; - final Transform transform = tokenizeTransformation(transformation); - algMode = AlgorithmMode.get(transform.algorithm, transform.mode); + final Transformation transform = Transformation.parse(transformation); + algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode()); - if (algMode != AlgorithmMode.AES_CBC && algMode != AlgorithmMode.AES_CTR) { - throw new GeneralSecurityException("unknown algorithm " + transform.algorithm + "_" + transform.mode); + if (algorithmMode != AlgorithmMode.AES_CBC && algorithmMode != AlgorithmMode.AES_CTR) { + throw new GeneralSecurityException("Unknown algorithm " + transform.getAlgorithm() + "_" + transform.getMode()); } - padding = Padding.get(transform.padding); + padding = transform.getPadding().ordinal(); context = OpenSslNativeJna.EVP_CIPHER_CTX_new(); } + /** + * Closes the OpenSSL cipher. Clean the OpenSsl native context. + */ + @Override + public void close() { + if (context != null) { + OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context); + // Freeing the context multiple times causes a JVM crash + // A work-round is to only free it at finalize time + // TODO is that sufficient? + // OpenSslNativeJna.EVP_CIPHER_CTX_free(context); + } + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. + * + * @param input the input byte array + * @param inputOffset the offset in input where the input starts + * @param inputLen the input length + * @param output the byte array for the result + * @param outputOffset the offset in output where the result is stored + * @return the number of bytes stored in output + * @throws ShortBufferException if the given output byte array is too small + * to hold the result + * @throws BadPaddingException if this cipher is in decryption mode, and + * (un)padding has been requested, but the + * decrypted data is not bounded by the + * appropriate padding bytes + * @throws IllegalBlockSizeException if this cipher is a block cipher, no + * padding has been requested (only in + * encryption mode), and the total input + * length of the data processed by this cipher + * is not a multiple of block size; or if this + * encryption algorithm is unable to process + * the input data provided. + */ + @Override + public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, + final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset); + final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen); + return doFinal(inputBuf, outputBuf); + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. The data is encrypted or decrypted, depending on how + * this cipher was initialized. + * + * @param inBuffer the input ByteBuffer + * @param outBuffer the output ByteBuffer + * @return int number of bytes stored in {@code output} + * @throws BadPaddingException if this cipher is in decryption mode, and + * (un)padding has been requested, but the + * decrypted data is not bounded by the + * appropriate padding bytes + * @throws IllegalBlockSizeException if this cipher is a block cipher, no + * padding has been requested (only in + * encryption mode), and the total input + * length of the data processed by this cipher + * is not a multiple of block size; or if this + * encryption algorithm is unable to process + * the input data provided. + * @throws ShortBufferException if the given output buffer is too small to + * hold the result + */ + @Override + public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + final int uptLen = update(inBuffer, outBuffer); + final int[] outlen = new int[1]; + throwOnError(OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen)); + final int len = uptLen + outlen[0]; + outBuffer.position(outBuffer.position() + outlen[0]); + return len; + } + + @Override + protected void finalize() throws Throwable { + OpenSslNativeJna.EVP_CIPHER_CTX_free(context); + super.finalize(); + } + + @Override + public String getAlgorithm() { + return transformation; + } + + @Override + public int getBlockSize() { + return CryptoCipherFactory.AES_BLOCK_SIZE; + } + /** * Initializes the cipher with mode, key and iv. * @@ -92,33 +213,28 @@ public void init(final int mode, final Key key, final AlgorithmParameterSpec par throws InvalidKeyException, InvalidAlgorithmParameterException { Objects.requireNonNull(key, "key"); Objects.requireNonNull(params, "params"); - int cipherMode = OpenSslNativeJna.OOSL_JNA_DECRYPT_MODE; - if (mode == Cipher.ENCRYPT_MODE) { - cipherMode = OpenSslNativeJna.OOSL_JNA_ENCRYPT_MODE; - } - final byte[] iv; - if (params instanceof IvParameterSpec) { - iv = ((IvParameterSpec) params).getIV(); - } else { + final int cipherMode = mode == Cipher.ENCRYPT_MODE ? OpenSslNativeJna.OOSL_JNA_ENCRYPT_MODE : OpenSslNativeJna.OOSL_JNA_DECRYPT_MODE; + if (!(params instanceof IvParameterSpec)) { // other AlgorithmParameterSpec such as GCMParameterSpec is not // supported now. throw new InvalidAlgorithmParameterException("Illegal parameters"); } + final byte[] iv = ((IvParameterSpec) params).getIV(); - if ((algMode == AlgorithmMode.AES_CBC || algMode == AlgorithmMode.AES_CTR) && iv.length != IV_LENGTH) { + if ((algorithmMode == AlgorithmMode.AES_CBC || algorithmMode == AlgorithmMode.AES_CTR) && iv.length != IV_LENGTH) { throw new InvalidAlgorithmParameterException("Wrong IV length: must be 16 bytes long"); } final int keyEncodedLength = key.getEncoded().length; - if (algMode == AlgorithmMode.AES_CBC) { + if (algorithmMode == AlgorithmMode.AES_CBC) { switch (keyEncodedLength) { - case 16: + case AES_128_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_128_cbc(); break; - case 24: + case AES_192_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_192_cbc(); break; - case 32: + case AES_256_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_256_cbc(); break; default: @@ -127,13 +243,13 @@ public void init(final int mode, final Key key, final AlgorithmParameterSpec par } else { switch (keyEncodedLength) { - case 16: + case AES_128_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_128_ctr(); break; - case 24: + case AES_192_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_192_ctr(); break; - case 32: + case AES_256_ENCODED_KEYLEN: algo = OpenSslNativeJna.EVP_aes_256_ctr(); break; default: @@ -141,31 +257,24 @@ public void init(final int mode, final Key key, final AlgorithmParameterSpec par } } - final int retVal = OpenSslNativeJna.EVP_CipherInit_ex(context, algo, null, key.getEncoded(), iv, cipherMode); - throwOnError(retVal); - OpenSslNativeJna.EVP_CIPHER_CTX_set_padding(context, padding); + throwOnError(OpenSslNativeJna.EVP_CipherInit_ex(context, algo, null, key.getEncoded(), iv, cipherMode)); + throwOnError(OpenSslNativeJna.EVP_CIPHER_CTX_set_padding(context, padding)); } /** - * Continues a multiple-part encryption/decryption operation. The data is - * encrypted or decrypted, depending on how this cipher was initialized. - * - * @param inBuffer the input ByteBuffer - * @param outBuffer the output ByteBuffer - * @return int number of bytes stored in {@code output} - * @throws ShortBufferException if there is insufficient space in the output - * buffer + * @param retVal the result value of error. */ - @Override - public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException { - final int[] outlen = new int[1]; - final int retVal = OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, - inBuffer.remaining()); - throwOnError(retVal); - final int len = outlen[0]; - inBuffer.position(inBuffer.limit()); - outBuffer.position(outBuffer.position() + len); - return len; + private void throwOnError(final int retVal) { + if (retVal != 1) { + final NativeLong err = OpenSslNativeJna.ERR_peek_error(); + final String errdesc = OpenSslNativeJna.ERR_error_string(err, null); + + if (context != null) { + OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context); + } + throw new IllegalStateException( + "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc); + } } /** @@ -190,71 +299,25 @@ public int update(final byte[] input, final int inputOffset, final int inputLen, } /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. The data is encrypted or decrypted, depending on how - * this cipher was initialized. + * Continues a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. * * @param inBuffer the input ByteBuffer * @param outBuffer the output ByteBuffer * @return int number of bytes stored in {@code output} - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the - * decrypted data is not bounded by the - * appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in - * encryption mode), and the total input - * length of the data processed by this cipher - * is not a multiple of block size; or if this - * encryption algorithm is unable to process - * the input data provided. - * @throws ShortBufferException if the given output buffer is too small to - * hold the result + * @throws ShortBufferException if there is insufficient space in the output + * buffer */ @Override - public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) - throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { - final int uptLen = update(inBuffer, outBuffer); + public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException { final int[] outlen = new int[1]; - final int retVal = OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen); - throwOnError(retVal); - final int len = uptLen + outlen[0]; - outBuffer.position(outBuffer.position() + outlen[0]); + throwOnError(OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, inBuffer.remaining())); + final int len = outlen[0]; + inBuffer.position(inBuffer.limit()); + outBuffer.position(outBuffer.position() + len); return len; } - /** - * Encrypts or decrypts data in a single-part operation, or finishes a - * multiple-part operation. - * - * @param input the input byte array - * @param inputOffset the offset in input where the input starts - * @param inputLen the input length - * @param output the byte array for the result - * @param outputOffset the offset in output where the result is stored - * @return the number of bytes stored in output - * @throws ShortBufferException if the given output byte array is too small - * to hold the result - * @throws BadPaddingException if this cipher is in decryption mode, and - * (un)padding has been requested, but the - * decrypted data is not bounded by the - * appropriate padding bytes - * @throws IllegalBlockSizeException if this cipher is a block cipher, no - * padding has been requested (only in - * encryption mode), and the total input - * length of the data processed by this cipher - * is not a multiple of block size; or if this - * encryption algorithm is unable to process - * the input data provided. - */ - @Override - public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, - final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { - final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset); - final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen); - return doFinal(inputBuf, outputBuf); - } - /** * Continues a multi-part update of the Additional Authentication Data (AAD). *@@ -262,10 +325,11 @@ public int doFinal(final byte[] input, final int inputOffset, final int inputLen * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode, * all AAD must be supplied before beginning operations on the ciphertext (via * the {@code update} and {@code doFinal} methods). + *
* * @param aad the buffer containing the Additional Authentication Data * - * @throws IllegalArgumentException if the {@code aad} byte array is null + * @throws IllegalArgumentException if the {@code aad} byte array is {@code null} * @throws IllegalStateException if this opensslEngine is in a wrong * state (e.g., has not been initialized), * does not accept AAD, or if operating in @@ -291,10 +355,11 @@ public void updateAAD(final byte[] aad) * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode, * all AAD must be supplied before beginning operations on the ciphertext (via * the {@code update} and {@code doFinal} methods). + * * * @param aad the buffer containing the Additional Authentication Data * - * @throws IllegalArgumentException if the {@code aad} byte array is null + * @throws IllegalArgumentException if the {@code aad} byte array is {@code null} * @throws IllegalStateException if this opensslEngine is in a wrong * state (e.g., has not been initialized), * does not accept AAD, or if operating in @@ -312,145 +377,4 @@ public void updateAAD(final ByteBuffer aad) // TODO: implement GCM mode using Jna throw new UnsupportedOperationException("This is unsupported in Jna Cipher"); } - - /** - * Closes the OpenSSL cipher. Clean the OpenSsl native context. - */ - @Override - public void close() { - if (context != null) { - OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context); - // Freeing the context multiple times causes a JVM crash - // A work-round is to only free it at finalize time - // TODO is that sufficient? -// OpenSslNativeJna.EVP_CIPHER_CTX_free(context); - } - } - - /** - * @param retVal the result value of error. - */ - private void throwOnError(final int retVal) { - if (retVal != 1) { - final NativeLong err = OpenSslNativeJna.ERR_peek_error(); - final String errdesc = OpenSslNativeJna.ERR_error_string(err, null); - - if (context != null) { - OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context); - } - throw new IllegalStateException( - "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc); - } - } - - // TODO DUPLICATED CODE, needs cleanup - /** Nested class for algorithm, mode and padding. */ - private static class Transform { - final String algorithm; - final String mode; - final String padding; - - /** - * Constructor of Transform. - * - * @param algorithm the algorithm name - * @param mode the mode name - * @param padding the padding name - */ - public Transform(final String algorithm, final String mode, final String padding) { - this.algorithm = algorithm; - this.mode = mode; - this.padding = padding; - } - } - - /** - * Tokenize the transformation. - * - * @param transformation current transformation - * @return the Transform - * @throws NoSuchAlgorithmException if the algorithm is not supported - */ - private static Transform tokenizeTransformation(final String transformation) throws NoSuchAlgorithmException { - if (transformation == null) { - throw new NoSuchAlgorithmException("No transformation given."); - } - - /* - * Array containing the components of a Cipher transformation: index 0: - * algorithm (e.g., AES) index 1: mode (e.g., CTR) index 2: padding (e.g., - * NoPadding) - */ - final String[] parts = new String[3]; - int count = 0; - final StringTokenizer parser = new StringTokenizer(transformation, "/"); - while (parser.hasMoreTokens() && count < 3) { - parts[count++] = parser.nextToken().trim(); - } - if (count != 3 || parser.hasMoreTokens()) { - throw new NoSuchAlgorithmException("Invalid transformation format: " + transformation); - } - return new Transform(parts[0], parts[1], parts[2]); - } - - /** - * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding. - */ - private enum AlgorithmMode { - AES_CTR, AES_CBC; - - /** - * Gets the AlgorithmMode instance. - * - * @param algorithm the algorithm name - * @param mode the mode name - * @return the AlgorithmMode instance - * @throws NoSuchAlgorithmException if the algorithm is not support - */ - static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException { - try { - return AlgorithmMode.valueOf(algorithm + "_" + mode); - } catch (final Exception e) { - throw new NoSuchAlgorithmException("Doesn't support algorithm: " + algorithm + " and mode: " + mode); - } - } - } - - /** - * Padding of JNA. - */ - private enum Padding { - NoPadding, PKCS5Padding; - - /** - * Gets the Padding instance. - * - * @param padding the padding name - * @return the AlgorithmMode instance - * @throws NoSuchPaddingException if the algorithm is not support - */ - static int get(final String padding) throws NoSuchPaddingException { - try { - return Padding.valueOf(padding).ordinal(); - } catch (final Exception e) { - throw new NoSuchPaddingException("Doesn't support padding: " + padding); - } - } - } - - @Override - public int getBlockSize() { - return CryptoCipherFactory.AES_BLOCK_SIZE; - } - - @Override - public String getAlgorithm() { - return transformation; - } - - @Override - protected void finalize() throws Throwable { - OpenSslNativeJna.EVP_CIPHER_CTX_free(context); - super.finalize(); - } } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java index 204bce7bc..d25756067 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java @@ -21,10 +21,8 @@ import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.util.Properties; -import java.util.Random; import org.apache.commons.crypto.random.CryptoRandom; -import org.apache.commons.crypto.utils.Utils; import com.sun.jna.NativeLong; import com.sun.jna.ptr.PointerByReference; @@ -46,9 +44,10 @@ * @see * http://en.wikipedia.org/wiki/RdRand */ -class OpenSslJnaCryptoRandom extends Random implements CryptoRandom { +final class OpenSslJnaCryptoRandom implements CryptoRandom { + + private static final int ENGINE_METHOD_RAND = 0x0008; - private static final long serialVersionUID = -7128193502768749585L; private final boolean rdrandEnabled; private final transient PointerByReference rdrandEngine; @@ -56,7 +55,7 @@ class OpenSslJnaCryptoRandom extends Random implements CryptoRandom { * Constructs a {@link OpenSslJnaCryptoRandom}. * * @param props the configuration properties (not used) - * @throws GeneralSecurityException if could not enable JNA access + * @throws GeneralSecurityException if JNA access could not be enabled */ public OpenSslJnaCryptoRandom(final Properties props) //NOPMD throws GeneralSecurityException { @@ -68,13 +67,12 @@ public OpenSslJnaCryptoRandom(final Properties props) //NOPMD try { OpenSslNativeJna.ENGINE_load_rdrand(); rdrandEngine = OpenSslNativeJna.ENGINE_by_id("rdrand"); - final int ENGINE_METHOD_RAND = 0x0008; - if(rdrandEngine != null) { + if (rdrandEngine != null) { final int rc = OpenSslNativeJna.ENGINE_init(rdrandEngine); - if(rc != 0) { + if (rc != 0) { final int rc2 = OpenSslNativeJna.ENGINE_set_default(rdrandEngine, ENGINE_METHOD_RAND); - if(rc2 != 0) { + if (rc2 != 0) { rdrandLoaded = true; } } @@ -86,80 +84,18 @@ public OpenSslJnaCryptoRandom(final Properties props) //NOPMD rdrandEnabled = rdrandLoaded; - if(!rdrandLoaded) { - closeRdrandEngine(); + if (!rdrandLoaded) { + closeRdrandEngine(false); } } - /** - * Generates a user-specified number of random bytes. It's thread-safe. - * - * @param bytes the array to be filled in with random bytes. - */ - @Override - public void nextBytes(final byte[] bytes) { - - synchronized (OpenSslJnaCryptoRandom.class) { - //this method is synchronized for now - //to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done - - if(rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) { - close(); - throw new IllegalStateException("rdrand should be used but default is detected"); - } - - final int byteLength = bytes.length; - final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength); - final int retVal = OpenSslNativeJna.RAND_bytes(buf, byteLength); - throwOnError(retVal); - buf.rewind(); - buf.get(bytes,0, byteLength); - } - } - - /** - * Overrides {@link OpenSslJnaCryptoRandom}. For {@link OpenSslJnaCryptoRandom}, - * we don't need to set seed. - * - * @param seed the initial seed. - */ - @Override - public void setSeed(final long seed) { - // Self-seeding. - } - - /** - * Overrides Random#next(). Generates an integer containing the - * user-specified number of random bits(right justified, with leading - * zeros). - * - * @param numBits number of random bits to be generated, where 0 - * {@literal <=} {@code numBits} {@literal <=} 32. - * @return int an {@code int} containing the user-specified number of - * random bits (right justified, with leading zeros). - */ - @Override - final protected int next(final int numBits) { - Utils.checkArgument(numBits >= 0 && numBits <= 32); - final int numBytes = (numBits + 7) / 8; - final byte[] b = new byte[numBytes]; - int next = 0; - - nextBytes(b); - for (int i = 0; i < numBytes; i++) { - next = (next << 8) + (b[i] & 0xFF); - } - - return next >>> (numBytes * 8 - numBits); - } - /** * Overrides {@link java.lang.AutoCloseable#close()}. Closes OpenSSL context * if native enabled. */ @Override public void close() { - closeRdrandEngine(); + closeRdrandEngine(true); OpenSslNativeJna.ENGINE_cleanup(); //cleanup locks @@ -169,32 +105,61 @@ public void close() { /** * Closes the rdrand engine. + * @param closing {@code true} when called while closing. */ - private void closeRdrandEngine() { + private void closeRdrandEngine(final boolean closing) { - if(rdrandEngine != null) { - OpenSslNativeJna.ENGINE_finish(rdrandEngine); - OpenSslNativeJna.ENGINE_free(rdrandEngine); + if (rdrandEngine != null) { + throwOnError(OpenSslNativeJna.ENGINE_finish(rdrandEngine), closing); + throwOnError(OpenSslNativeJna.ENGINE_free(rdrandEngine), closing); } } /** * Checks if rdrand engine is used to retrieve random bytes * - * @return true if rdrand is used, false if default engine is used + * @return {@code true} if rdrand is used, {@code false} if default engine is used */ public boolean isRdrandEnabled() { return rdrandEnabled; } + /** + * Generates a user-specified number of random bytes. It's thread-safe. + * + * @param bytes the array to be filled in with random bytes. + */ + @Override + public void nextBytes(final byte[] bytes) { + + synchronized (OpenSslJnaCryptoRandom.class) { + // this method is synchronized for now + // to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done + + if (rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) { + close(); + throw new IllegalStateException("rdrand should be used but default is detected"); + } + + final int byteLength = bytes.length; + final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength); + throwOnError(OpenSslNativeJna.RAND_bytes(buf, byteLength), false); + buf.rewind(); + buf.get(bytes, 0, byteLength); + } + } + /** * @param retVal the result value of error. + * @param closing {@code true} when called while closing. */ - private void throwOnError(final int retVal) { + private void throwOnError(final int retVal, final boolean closing) { if (retVal != 1) { final NativeLong err = OpenSslNativeJna.ERR_peek_error(); final String errdesc = OpenSslNativeJna.ERR_error_string(err, null); - close(); + if (!closing) { + close(); + } throw new IllegalStateException("return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc); } } diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java index e9fbd0435..44348c67e 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java @@ -27,7 +27,7 @@ import com.sun.jna.NativeLong; import com.sun.jna.ptr.PointerByReference; -class OpenSslNativeJna { +final class OpenSslNativeJna { static final int OPENSSL_INIT_ENGINE_RDRAND = 0x00000200; @@ -38,232 +38,168 @@ class OpenSslNativeJna { static final Throwable INIT_ERROR; - public static final long VERSION; - public static final long VERSION_1_0_X = 0x10000000; - public static final long VERSION_1_1_X = 0x10100000; + /** Full version from JNA call. */ + static final long VERSION; + + /** Major Minor version from JNA call, without the maintenance level. */ + static final long VERSION_X_Y; + + static final long VERSION_1_1_X = 0x10100000; + static final long VERSION_2_0_X = 0x20000000; + static final long VERSION_3_0_X = 0x30000000; + static final long VERSION_3_1_X = 0x30100000; + + private static final OpenSslInterfaceNativeJna JnaImplementation; static { + OpenSslJna.debug("OpenSslNativeJna static init start"); final String libraryName = System.getProperty(Crypto.CONF_PREFIX + OpenSslNativeJna.class.getSimpleName(), "crypto"); - OpenSslJna.debug("NativeLibrary.getInstance('%s')%n", libraryName); + OpenSslJna.debug("OpenSslNativeJna NativeLibrary.getInstance('%s')", libraryName); + @SuppressWarnings("resource") // NativeLibrary.getInstance returns a singleton final NativeLibrary crypto = NativeLibrary.getInstance(libraryName); - Function version = null; + OpenSslJna.debug("OpenSslNativeJna NativeLibrary.getInstance('%s') -> %s", libraryName, crypto); + Function versionFunction = null; try { - version = crypto.getFunction("SSLeay"); + versionFunction = crypto.getFunction("SSLeay"); // Needed for LibreSSL 2.x } catch (final UnsatisfiedLinkError e) { - // Swallow the Error. + versionFunction = crypto.getFunction("OpenSSL_version_num"); } + // Must find one of the above two functions; else give up - if (version == null) { - VERSION = VERSION_1_1_X; - } else { - VERSION = VERSION_1_0_X; - } + VERSION = versionFunction.invokeLong(new Object[]{}); + //CHECKSTYLE:OFF + VERSION_X_Y = VERSION & 0xffff0000; // keep only major.minor checkstyle: + //CHECKSTYLE:ON - if (VERSION == VERSION_1_1_X) { - INIT_OK = OpenSsl11XNativeJna.INIT_OK; - } else { - INIT_OK = OpenSsl10XNativeJna.INIT_OK; - } + OpenSslJna.debug(String.format("OpenSslNativeJna detected version 0x%x => 0x%x", VERSION, VERSION_X_Y)); - if (INIT_OK) { - INIT_ERROR = null; - } else if (VERSION == VERSION_1_1_X) { - INIT_ERROR = OpenSsl11XNativeJna.INIT_ERROR; - } else { - INIT_ERROR = OpenSsl10XNativeJna.INIT_ERROR; - } + if (VERSION_X_Y == VERSION_1_1_X) { + OpenSslJna.debug("Creating OpenSsl11XNativeJna"); + JnaImplementation = new OpenSsl11XNativeJna(); + } else if (VERSION_X_Y == VERSION_2_0_X) { + OpenSslJna.debug("Creating LibreSsl20XNativeJna"); + JnaImplementation = new LibreSsl20XNativeJna(); + } else if (VERSION_X_Y == VERSION_3_0_X || VERSION_X_Y == VERSION_3_1_X) { // assume these are the same + OpenSslJna.debug("Creating OpenSsl30XNativeJna"); + JnaImplementation = new OpenSsl30XNativeJna(); + } else { + throw new UnsupportedOperationException(String.format("Unsupported Version: %x", VERSION_X_Y)); + } + + INIT_OK = JnaImplementation._INIT_OK(); + + INIT_ERROR = INIT_OK ? null : JnaImplementation._INIT_ERROR(); + OpenSslJna.debug("OpenSslNativeJna INIT_OK = %s, INIT_ERROR = '%s', JnaImplementation = %s", INIT_OK, INIT_ERROR, JnaImplementation.getClass()); + OpenSslJna.debug("OpenSslNativeJna static init end"); } public static PointerByReference ENGINE_by_id(final String string) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.ENGINE_by_id(string); - } - return OpenSsl10XNativeJna.ENGINE_by_id(string); + return JnaImplementation._ENGINE_by_id(string); } - public static void ENGINE_finish(final PointerByReference rdrandEngine) { - if (VERSION == VERSION_1_1_X) { - OpenSsl11XNativeJna.ENGINE_finish(rdrandEngine); - } else { - OpenSsl10XNativeJna.ENGINE_finish(rdrandEngine); - } + public static int ENGINE_cleanup() { + return JnaImplementation._ENGINE_cleanup(); } - public static void ENGINE_free(final PointerByReference rdrandEngine) { - if (VERSION == VERSION_1_1_X) { - OpenSsl11XNativeJna.ENGINE_free(rdrandEngine); - } else { - OpenSsl10XNativeJna.ENGINE_free(rdrandEngine); - } + public static int ENGINE_finish(final PointerByReference rdrandEngine) { + return JnaImplementation._ENGINE_finish(rdrandEngine); + } + + public static int ENGINE_free(final PointerByReference rdrandEngine) { + return JnaImplementation._ENGINE_free(rdrandEngine); } public static int ENGINE_init(final PointerByReference rdrandEngine) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.ENGINE_init(rdrandEngine); - } - return OpenSsl10XNativeJna.ENGINE_init(rdrandEngine); + return JnaImplementation._ENGINE_init(rdrandEngine); + } + + public static void ENGINE_load_rdrand() { + JnaImplementation._ENGINE_load_rdrand(); } public static int ENGINE_set_default(final PointerByReference rdrandEngine, final int eNGINE_METHOD_RAND) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.ENGINE_set_default(rdrandEngine, eNGINE_METHOD_RAND); - } - return OpenSsl10XNativeJna.ENGINE_set_default(rdrandEngine, eNGINE_METHOD_RAND); + return JnaImplementation._ENGINE_set_default(rdrandEngine, eNGINE_METHOD_RAND); } - public static String ERR_error_string(final NativeLong err, final Object object) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.ERR_error_string(err, null); - } - return OpenSsl10XNativeJna.ERR_error_string(err, null); + public static String ERR_error_string(final NativeLong err, final char[] object) { + return JnaImplementation._ERR_error_string(err, null); } public static NativeLong ERR_peek_error() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.ERR_peek_error(); - } - return OpenSsl10XNativeJna.ERR_peek_error(); + return JnaImplementation._ERR_peek_error(); } public static PointerByReference EVP_aes_128_cbc() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_128_cbc(); - } - return OpenSsl10XNativeJna.EVP_aes_128_cbc(); + return JnaImplementation._EVP_aes_128_cbc(); } public static PointerByReference EVP_aes_128_ctr() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_128_ctr(); - } - return OpenSsl10XNativeJna.EVP_aes_128_ctr(); + return JnaImplementation._EVP_aes_128_ctr(); } public static PointerByReference EVP_aes_192_cbc() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_192_cbc(); - } - return OpenSsl10XNativeJna.EVP_aes_192_cbc(); + return JnaImplementation._EVP_aes_192_cbc(); } public static PointerByReference EVP_aes_192_ctr() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_192_ctr(); - } - return OpenSsl10XNativeJna.EVP_aes_192_ctr(); + return JnaImplementation._EVP_aes_192_ctr(); } public static PointerByReference EVP_aes_256_cbc() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_256_cbc(); - } - return OpenSsl10XNativeJna.EVP_aes_256_cbc(); + return JnaImplementation._EVP_aes_256_cbc(); } public static PointerByReference EVP_aes_256_ctr() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_aes_256_ctr(); - } - return OpenSsl10XNativeJna.EVP_aes_256_ctr(); + return JnaImplementation._EVP_aes_256_ctr(); + } + + public static void EVP_CIPHER_CTX_cleanup(final PointerByReference context) { + JnaImplementation._EVP_CIPHER_CTX_cleanup(context); } public static void EVP_CIPHER_CTX_free(final PointerByReference context) { - if (VERSION == VERSION_1_1_X) { - OpenSsl11XNativeJna.EVP_CIPHER_CTX_free(context); - } else { - OpenSsl10XNativeJna.EVP_CIPHER_CTX_free(context); - } + JnaImplementation._EVP_CIPHER_CTX_free(context); } public static PointerByReference EVP_CIPHER_CTX_new() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_CIPHER_CTX_new(); - } - return OpenSsl10XNativeJna.EVP_CIPHER_CTX_new(); + return JnaImplementation._EVP_CIPHER_CTX_new(); } - public static void EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding) { - if (VERSION == VERSION_1_1_X) { - OpenSsl11XNativeJna.EVP_CIPHER_CTX_set_padding(context, padding); - } else { - OpenSsl10XNativeJna.EVP_CIPHER_CTX_set_padding(context, padding); - } + public static int EVP_CIPHER_CTX_set_padding(final PointerByReference context, final int padding) { + return JnaImplementation._EVP_CIPHER_CTX_set_padding(context, padding); } public static int EVP_CipherFinal_ex(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen); - } - return OpenSsl10XNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen); + return JnaImplementation._EVP_CipherFinal_ex(context, outBuffer, outlen); } public static int EVP_CipherInit_ex(final PointerByReference context, final PointerByReference algo, final Object object, final byte[] encoded, final byte[] iv, final int cipherMode) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_CipherInit_ex(context, algo, null, encoded, iv, - cipherMode); - } - return OpenSsl10XNativeJna.EVP_CipherInit_ex(context, algo, null, encoded, iv, - cipherMode); + return JnaImplementation._EVP_CipherInit_ex(context, algo, null, encoded, iv, cipherMode); } public static int EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer, final int[] outlen, final ByteBuffer inBuffer, final int remaining) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, - remaining); - } - return OpenSsl10XNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, - remaining); + return JnaImplementation._EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, remaining); + } + + public static String OpenSSLVersion(final int i) { + return JnaImplementation._OpenSSL_version(i); } public static int RAND_bytes(final ByteBuffer buf, final int length) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.RAND_bytes(buf, length); - } - return OpenSsl10XNativeJna.RAND_bytes(buf, length); + return JnaImplementation._RAND_bytes(buf, length); } public static PointerByReference RAND_get_rand_method() { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.RAND_get_rand_method(); - } - return OpenSsl10XNativeJna.RAND_get_rand_method(); + return JnaImplementation._RAND_get_rand_method(); } public static PointerByReference RAND_SSLeay() { - if (VERSION == VERSION_1_1_X) { - return null; - } - return OpenSsl10XNativeJna.RAND_SSLeay(); + return JnaImplementation._RAND_SSLeay(); } - public static String OpenSSLVersion(final int i) { - if (VERSION == VERSION_1_1_X) { - return OpenSsl11XNativeJna.OpenSSL_version(i); - } - return OpenSsl10XNativeJna.SSLeay_version(i); - } - - public static void ENGINE_load_rdrand() { - if (VERSION == VERSION_1_1_X) { - return; - } - OpenSsl10XNativeJna.ENGINE_load_rdrand(); - } - - public static void ENGINE_cleanup() { - if (VERSION == VERSION_1_1_X) { - return; - } - OpenSsl10XNativeJna.ENGINE_cleanup(); - } - - public static void EVP_CIPHER_CTX_cleanup(final PointerByReference context) { - if (VERSION == VERSION_1_1_X) { - return; - } - OpenSsl10XNativeJna.EVP_CIPHER_CTX_cleanup(context); + private OpenSslNativeJna() { } -} \ No newline at end of file +} diff --git a/src/main/java/org/apache/commons/crypto/random/CryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/CryptoRandom.java index e0d956f7d..c60712865 100644 --- a/src/main/java/org/apache/commons/crypto/random/CryptoRandom.java +++ b/src/main/java/org/apache/commons/crypto/random/CryptoRandom.java @@ -20,9 +20,10 @@ import java.io.Closeable; /** - * The interface for CryptoRandom. + * Generates random bytes. *- * Note that implementations must provide a constructor that takes a Properties instance + * Note that implementations must provide a constructor that takes a Properties instance. + *
*/ public interface CryptoRandom extends Closeable { diff --git a/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java b/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java index 9ea4d3b99..8acdf9c8d 100644 --- a/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java +++ b/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java @@ -26,50 +26,10 @@ import org.apache.commons.crypto.utils.Utils; /** - * This is the factory class used for creating {@link CryptoRandom} instances + * Creates {@link CryptoRandom} instances */ public class CryptoRandomFactory { - // security random related configuration keys - /** - * The configuration key of the file path for secure random device. - */ - public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX - + "secure.random.device.file.path"; - - /** - * The default value ({@value}) of the file path for secure random device. - */ - // Note: this is public mainly for use by the Javadoc - public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom"; - - /** - * The configuration key of the algorithm of secure random. - */ - public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX - + "secure.random.java.algorithm"; - - /** - * The default value ({@value}) of the algorithm of secure random. - */ - // Note: this is public mainly for use by the Javadoc - public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG"; - - /** - * The configuration key of the CryptoRandom implementation class. - *- * The value of the CLASSES_KEY needs to be the full name of a - * class that implements the - * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface - * The internal classes are listed in the enum - * {@link RandomProvider RandomProvider} - * which can be used to obtain the full class name. - *
- * The value can also be a comma-separated list of class names in - * order of descending priority. - */ - public static final String CLASSES_KEY = Crypto.CONF_PREFIX - + "secure.random.classes"; /** * Defines the internal CryptoRandom implementations. *
@@ -144,6 +104,44 @@ public Class extends CryptoRandom> getImplClass() { } } + // security random related configuration keys + /** + * The configuration key of the file path for secure random device. + */ + public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX + "secure.random.device.file.path"; + + /** + * The default value ({@value}) of the file path for secure random device. + */ + // Note: this is public mainly for use by the Javadoc + public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom"; + + /** + * The configuration key of the algorithm of secure random. + */ + public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX + "secure.random.java.algorithm"; + + /** + * The default value ({@value}) of the algorithm of secure random. + */ + // Note: this is public mainly for use by the Javadoc + public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG"; + + /** + * The configuration key of the CryptoRandom implementation class. + *
+ * The value of the CLASSES_KEY needs to be the full name of a + * class that implements the + * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface + * The internal classes are listed in the enum + * {@link RandomProvider RandomProvider} + * which can be used to obtain the full class name. + *
+ * The value can also be a comma-separated list of class names in + * order of descending priority. + */ + public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "secure.random.classes"; + /** * The default value (OPENSSL,JAVA) used when creating a {@link org.apache.commons.crypto.cipher.CryptoCipher}. */ @@ -152,12 +150,6 @@ public Class extends CryptoRandom> getImplClass() { .concat(",") .concat(RandomProvider.JAVA.getClassName()); - /** - * The private constructor of {@link CryptoRandomFactory}. - */ - private CryptoRandomFactory() { - } - /** * Gets a CryptoRandom instance using the default implementation * as defined by {@link #CLASSES_DEFAULT} @@ -181,7 +173,7 @@ public static CryptoRandom getCryptoRandom() throws GeneralSecurityException { * @param props the configuration properties. * @return CryptoRandom the cryptoRandom object. * @throws GeneralSecurityException if cannot create the {@link CryptoRandom} class - * @throws IllegalArgumentException if no classname(s) are provided + * @throws IllegalArgumentException if no class name(s) are provided */ public static CryptoRandom getCryptoRandom(final Properties props) throws GeneralSecurityException { @@ -192,20 +184,20 @@ public static CryptoRandom getCryptoRandom(final Properties props) final StringBuilder errorMessage = new StringBuilder(); CryptoRandom random = null; Exception lastException = null; - for (final String klassName : names) { + for (final String className : names) { try { - final Class> klass = ReflectionUtils.getClassByName(klassName); + final Class> klass = ReflectionUtils.getClassByName(className); random = (CryptoRandom) ReflectionUtils.newInstance(klass, props); break; } catch (final ClassCastException e) { lastException = e; - errorMessage.append("Class: [" + klassName + "] is not a CryptoRandom."); + errorMessage.append("Class: [" + className + "] is not a CryptoRandom."); } catch (final ClassNotFoundException e) { lastException = e; - errorMessage.append("CryptoRandom: [" + klassName + "] not found."); + errorMessage.append("CryptoRandom: [" + className + "] not found."); } catch (final Exception e) { lastException = e; - errorMessage.append("CryptoRandom: [" + klassName + "] failed with " + e.getMessage()); + errorMessage.append("CryptoRandom: [" + className + "] failed with " + e.getMessage()); } } @@ -229,4 +221,10 @@ private static String getRandomClassString(final Properties props) { } return randomClassString; } + + /** + * The private constructor of {@link CryptoRandomFactory}. + */ + private CryptoRandomFactory() { + } } diff --git a/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java index 519eb65d5..97afe6730 100644 --- a/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java +++ b/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java @@ -1,64 +1,57 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.random; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Properties; -import java.util.Random; + +import org.apache.commons.crypto.utils.Utils; /** * A CryptoRandom of Java implementation. + *
+ * This class is not public/protected so does not appear in the main Javadoc Please ensure that property use is documented in the enum + * CryptoRandomFactory.RandomProvider + *
*/ -class JavaCryptoRandom extends Random implements CryptoRandom { - - /** - * Generated serialVersionUID. - */ - private static final long serialVersionUID = 5517475898166660050L; +final class JavaCryptoRandom implements CryptoRandom { - private SecureRandom instance; + private final SecureRandom instance; /** * Constructs a {@link JavaCryptoRandom}. * - * @param properties the configuration properties. - * Uses the key {@link CryptoRandomFactory#JAVA_ALGORITHM_KEY} - * to get the name of the algorithm, with a default of - * {@link CryptoRandomFactory#JAVA_ALGORITHM_DEFAULT} + * @param properties the configuration properties. Uses the key {@link CryptoRandomFactory#JAVA_ALGORITHM_KEY} to get the name of the algorithm, with a + * default of {@link CryptoRandomFactory#JAVA_ALGORITHM_DEFAULT} */ - // N.B. this class is not public/protected so does not appear in the main Javadoc - // Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider public JavaCryptoRandom(final Properties properties) { - try { - instance = SecureRandom - .getInstance(properties - .getProperty( - CryptoRandomFactory.JAVA_ALGORITHM_KEY, - CryptoRandomFactory.JAVA_ALGORITHM_DEFAULT)); - } catch (final NoSuchAlgorithmException e) { - instance = new SecureRandom(); - } + SecureRandom tmp; + try { + tmp = SecureRandom.getInstance(properties.getProperty(CryptoRandomFactory.JAVA_ALGORITHM_KEY, CryptoRandomFactory.JAVA_ALGORITHM_DEFAULT)); + } catch (final NoSuchAlgorithmException e) { + tmp = new SecureRandom(); + } + instance = tmp; } /** - * Overrides {@link java.lang.AutoCloseable#close()}. For - * {@link JavaCryptoRandom}, we don't need to recycle resource. + * Overrides {@link java.lang.AutoCloseable#close()}. For {@link JavaCryptoRandom}, we don't need to recycle resource. */ @Override public void close() { @@ -66,9 +59,21 @@ public void close() { } /** - * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes - * and places them into a user-supplied byte array. The number of random - * bytes produced is equal to the length of the byte array. + * Overrides Random#next(). Generates an integer containing the user-specified number of random bits(right justified, with leading zeros). + * + * @param numBits number of random bits to be generated, where 0 {@literal <=} {@code numBits} {@literal <=} 32. + * @return int an {@code int} containing the user-specified number of random bits (right justified, with leading zeros). + */ + protected int next(final int numBits) { + Utils.checkArgument(numBits >= 0 && numBits <= Integer.SIZE); + // Can't simply invoke instance.next(bits) here, because that is package protected. + // But, this should do. + return instance.nextInt() >>> (Integer.SIZE - numBits); + } + + /** + * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes and places them into a user-supplied byte array. The number of random bytes + * produced is equal to the length of the byte array. * * @param bytes the array to be filled in with random bytes. */ diff --git a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandom.java index 1cc18b6ab..c20e83e80 100644 --- a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandom.java +++ b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandom.java @@ -22,7 +22,6 @@ import java.util.Random; import org.apache.commons.crypto.Crypto; -import org.apache.commons.crypto.utils.Utils; /** *@@ -30,19 +29,18 @@ *
* *- * If using an Intel chipset with RDRAND, the high-performance hardware random - * number generator will be used and it's much faster than SecureRandom. If - * RDRAND is unavailable, default OpenSSL secure random generator will be used. - * It's still faster and can generate strong random bytes. + * If using an Intel chipset with RDRAND, the high-performance hardware random number generator will be used and it's much faster than SecureRandom. If RDRAND + * is unavailable, default OpenSSL secure random generator will be used. It's still faster and can generate strong random bytes. + *
+ *+ * This class is not public/protected so does not appear in the main Javadoc Please ensure that property use is documented in the enum + * CryptoRandomFactory.RandomProvider *
* - * @see - * https://wiki.openssl.org/index.php/Random_Numbers - * @see - * http://en.wikipedia.org/wiki/RdRand + * @see https://wiki.openssl.org/index.php/Random_Numbers + * @see http://en.wikipedia.org/wiki/RdRand */ -class OpenSslCryptoRandom extends Random implements CryptoRandom { - private static final long serialVersionUID = -7828193502768789584L; +final class OpenSslCryptoRandom implements CryptoRandom { private static final boolean nativeEnabled; @@ -65,7 +63,7 @@ class OpenSslCryptoRandom extends Random implements CryptoRandom { // Check that nextRandBytes works (is this really needed?) try { checkNative(); - } catch (GeneralSecurityException e) { + } catch (final GeneralSecurityException e) { throw new IllegalStateException(e); } if (!OpenSslCryptoRandomNative.nextRandBytes(new byte[1])) { @@ -85,7 +83,7 @@ private static void checkNative() throws GeneralSecurityException { /** * Judges whether native library was successfully loaded and initialized. * - * @return true if library was loaded and initialized + * @return {@code true} if library was loaded and initialized */ public static boolean isNativeCodeEnabled() { return nativeEnabled; @@ -97,8 +95,6 @@ public static boolean isNativeCodeEnabled() { * @param props the configuration properties - not used * @throws GeneralSecurityException if the native library could not be initialized successfully */ - // N.B. this class is not public/protected so does not appear in the main Javadoc - // Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider public OpenSslCryptoRandom(final Properties props) throws GeneralSecurityException { // NOPMD checkNative(); } @@ -112,33 +108,9 @@ public void close() { // noop } - /** - * Overrides Random#next(). Generates an integer containing the - * user-specified number of random bits(right justified, with leading - * zeros). - * - * @param numBits number of random bits to be generated, where 0 - * {@literal <=} {@code numBits} {@literal <=} 32. - * @return int an {@code int} containing the user-specified number of - * random bits (right justified, with leading zeros). - */ - @Override - final protected int next(final int numBits) { - Utils.checkArgument(numBits >= 0 && numBits <= 32); - final int numBytes = (numBits + 7) / 8; - final byte[] b = new byte[numBytes]; - int next = 0; - - nextBytes(b); - for (int i = 0; i < numBytes; i++) { - next = (next << 8) + (b[i] & 0xFF); - } - - return next >>> (numBytes * 8 - numBits); - } - /** * Generates a user-specified number of random bytes. It's thread-safe. + * Overrides {@link Random}. * * @param bytes the array to be filled in with random bytes. */ @@ -151,14 +123,4 @@ public void nextBytes(final byte[] bytes) { } } - /** - * Overrides {@link OpenSslCryptoRandom}. For {@link OpenSslCryptoRandom}, - * we don't need to set seed. - * - * @param seed the initial seed. - */ - @Override - public void setSeed(final long seed) { - // Self-seeding. - } } diff --git a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java index d4ee5435d..3f7078ddd 100644 --- a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java +++ b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java @@ -24,26 +24,26 @@ * and implemented in the file * src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c */ -class OpenSslCryptoRandomNative { - - /** - * The private constructor of {@link OpenSslCryptoRandomNative}. - */ - private OpenSslCryptoRandomNative() { - } +final class OpenSslCryptoRandomNative { /** * Declares a native method to initialize SR. */ - public native static void initSR(); + public static native void initSR(); /** - * Judges whether use {@link OpenSslCryptoRandomNative} to generate the + * Judges whether to use {@link OpenSslCryptoRandomNative} to generate the * user-specified number of random bits. * * @param bytes the array to be filled in with random bytes. - * @return true if use {@link OpenSslCryptoRandomNative} to generate the + * @return {@code true} if use {@link OpenSslCryptoRandomNative} to generate the * user-specified number of random bits. */ - public native static boolean nextRandBytes(byte[] bytes); + public static native boolean nextRandBytes(byte[] bytes); + + /** + * The private constructor of {@link OpenSslCryptoRandomNative}. + */ + private OpenSslCryptoRandomNative() { + } } diff --git a/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java index 5e145cf8f..6e4592317 100644 --- a/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java +++ b/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java @@ -21,17 +21,17 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; -import java.util.Random; import org.apache.commons.crypto.utils.IoUtils; /** - * A Random implementation that uses random bytes sourced from the operating - * system. + * A Random implementation that uses random bytes sourced from the operating system. + *+ * This class is not public/protected so does not appear in the main Javadoc Please ensure that property use is documented in the enum + * CryptoRandomFactory.RandomProvider + *
*/ -class OsCryptoRandom extends Random implements CryptoRandom { - - private static final long serialVersionUID = 6391500337172057900L; +final class OsCryptoRandom implements CryptoRandom { private static final int RESERVOIR_LENGTH = 8192; @@ -41,22 +41,6 @@ class OsCryptoRandom extends Random implements CryptoRandom { private int pos = reservoir.length; - /** - * Fills the reservoir. - * - * @param min the length. - */ - private void fillReservoir(final int min) { - if (pos >= reservoir.length - min) { - try { - IoUtils.readFully(stream, reservoir, 0, reservoir.length); - } catch (final IOException e) { - throw new IllegalStateException("failed to fill reservoir", e); - } - pos = 0; - } - } - /** * Constructs a {@link OsCryptoRandom}. * @@ -65,12 +49,8 @@ private void fillReservoir(final int min) { * path to the random device, default is * {@link CryptoRandomFactory#DEVICE_FILE_PATH_DEFAULT} */ - // N.B. this class is not public/protected so does not appear in the main Javadoc - // Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider public OsCryptoRandom(final Properties props) { - final File randomDevFile = new File( - props.getProperty(CryptoRandomFactory.DEVICE_FILE_PATH_KEY, - CryptoRandomFactory.DEVICE_FILE_PATH_DEFAULT)); + final File randomDevFile = new File(props.getProperty(CryptoRandomFactory.DEVICE_FILE_PATH_KEY, CryptoRandomFactory.DEVICE_FILE_PATH_DEFAULT)); try { close(); @@ -87,6 +67,33 @@ public OsCryptoRandom(final Properties props) { } } + /** + * Overrides {@link java.lang.AutoCloseable#close()}. Closes the OS stream. + */ + @Override + synchronized public void close() { + if (stream != null) { + IoUtils.closeQuietly(stream); + stream = null; + } + } + + /** + * Fills the reservoir. + * + * @param min the length. + */ + private void fillReservoir(final int min) { + if (pos >= reservoir.length - min) { + try { + IoUtils.readFully(stream, reservoir, 0, reservoir.length); + } catch (final IOException e) { + throw new IllegalStateException("failed to fill reservoir", e); + } + pos = 0; + } + } + /** * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes * and places them into a user-supplied byte array. The number of random @@ -107,33 +114,4 @@ synchronized public void nextBytes(final byte[] bytes) { } } - /** - * Overrides Random#next(). Generates the next pseudorandom number. - * Subclasses should override this, as this is used by all other methods. - * - * @param nbits random bits. - * @return the next pseudorandom value from this random number generator's - * sequence. - */ - @Override - synchronized protected int next(final int nbits) { - fillReservoir(4); - int n = 0; - for (int i = 0; i < 4; i++) { - n = (n << 8) | (reservoir[pos++] & 0xff); - } - return n & (0xffffffff >> (32 - nbits)); - } - - /** - * Overrides {@link java.lang.AutoCloseable#close()}. Closes the OS stream. - */ - @Override - synchronized public void close() { - if (stream != null) { - IoUtils.closeQuietly(stream); - stream = null; - } - } - } diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java index 27afbf96d..520ca8dfb 100644 --- a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java @@ -39,18 +39,16 @@ import org.apache.commons.crypto.stream.input.ChannelInput; import org.apache.commons.crypto.stream.input.Input; import org.apache.commons.crypto.stream.input.StreamInput; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; /** * CryptoInputStream reads input data and decrypts data in stream manner. It * supports any mode of operations such as AES CBC/CTR/GCM mode in concept.It is * not thread-safe. - * */ -public class CryptoInputStream extends InputStream implements - ReadableByteChannel { - private final byte[] oneByteBuf = new byte[1]; +public class CryptoInputStream extends InputStream implements ReadableByteChannel { /** * The configuration key of the buffer size for stream. @@ -58,6 +56,92 @@ public class CryptoInputStream extends InputStream implements public static final String STREAM_BUFFER_SIZE_KEY = Crypto.CONF_PREFIX + "stream.buffer.size"; + // stream related configuration keys + /** + * The default value of the buffer size for stream. + */ + private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192; + + private static final int MIN_BUFFER_SIZE = 512; + + /** + * The index value when the end of the stream has been reached {@code -1}. + * + * @since 1.1 + */ + public static final int EOS = -1; + + /** + * Checks and floors buffer size. + * + * @param cipher the {@link CryptoCipher} instance. + * @param bufferSize the buffer size. + * @return the remaining buffer size. + */ + static int checkBufferSize(final CryptoCipher cipher, final int bufferSize) { + Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE, + "Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + "."); + return bufferSize - bufferSize % cipher.getBlockSize(); + } + + /** + * Checks whether the cipher is supported streaming. + * + * @param cipher the {@link CryptoCipher} instance. + * @throws IOException if an I/O error occurs. + */ + static void checkStreamCipher(final CryptoCipher cipher) throws IOException { + if (!cipher.getAlgorithm().equals(AES.CTR_NO_PADDING)) { + throw new IOException(AES.CTR_NO_PADDING + " is required"); + } + } + + /** + * Forcibly free the direct buffer. + * + * @param buffer the bytebuffer to be freed. + */ + static void freeDirectBuffer(final ByteBuffer buffer) { + if (buffer != null) { + try { + /* + * Using reflection to implement sun.nio.ch.DirectBuffer.cleaner() .clean(); + */ + final String SUN_CLASS = "sun.nio.ch.DirectBuffer"; + final Class>[] interfaces = buffer.getClass().getInterfaces(); + final Object[] EMPTY_OBJECT_ARRAY = {}; + + for (final Class> clazz : interfaces) { + if (clazz.getName().equals(SUN_CLASS)) { + /* DirectBuffer#cleaner() */ + final Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner"); + final Object cleaner = getCleaner.invoke(buffer, EMPTY_OBJECT_ARRAY); + /* Cleaner#clean() */ + final Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean"); + cleanMethod.invoke(cleaner, EMPTY_OBJECT_ARRAY); + return; + } + } + } catch (final ReflectiveOperationException e) { // NOPMD + // Ignore the Reflection exception. + } + } + } + + /** + * Reads crypto buffer size. + * + * @param props The {@code Properties} class represents a set of + * properties. + * @return the buffer size. + * */ + static int getBufferSize(final Properties props) { + final String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, ""); + return bufferSizeStr.isEmpty() ? CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT : Integer.parseInt(bufferSizeStr); + } + + private final byte[] oneByteBuf = new byte[1]; + /** The CryptoCipher instance. */ final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further @@ -93,20 +177,70 @@ public class CryptoInputStream extends InputStream implements */ ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further - // stream related configuration keys /** - * The default value of the buffer size for stream. + * Constructs a {@link CryptoInputStream}. + * + * @param input the input data. + * @param cipher the cipher instance. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. */ - private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192; + protected CryptoInputStream(final Input input, final CryptoCipher cipher, final int bufferSize, + final Key key, final AlgorithmParameterSpec params) throws IOException { + this.input = input; + this.cipher = cipher; + this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize); - private static final int MIN_BUFFER_SIZE = 512; + this.key = key; + this.params = params; + if (!(params instanceof IvParameterSpec)) { + // other AlgorithmParameterSpec such as GCMParameterSpec is not + // supported now. + throw new IOException("Illegal parameters"); + } + + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + cipher.getBlockSize()); + outBuffer.limit(0); + + initCipher(); + } /** - * The index value when the end of the stream has been reached {@code -1}. + * Constructs a {@link CryptoInputStream}. * - * @since 1.1 + * @param cipher the cipher instance. + * @param inputStream the input stream. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. */ - public static final int EOS = -1; + @SuppressWarnings("resource") // Closing the instance closes the StreamInput + protected CryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, + final int bufferSize, final Key key, final AlgorithmParameterSpec params) + throws IOException { + this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, params); + } + + /** + * Constructs a {@link CryptoInputStream}. + * + * @param channel the ReadableByteChannel instance. + * @param cipher the cipher instance. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. + */ + @SuppressWarnings("resource") // Closing the instance closes the ChannelInput + protected CryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, + final int bufferSize, final Key key, final AlgorithmParameterSpec params) + throws IOException { + this(new ChannelInput(channel), cipher, bufferSize, key, params); + } /** * Constructs a {@link CryptoInputStream}. @@ -153,282 +287,166 @@ public CryptoInputStream(final String transformation, } /** - * Constructs a {@link CryptoInputStream}. + * Overrides the {@link InputStream#available()}. Returns an estimate of the + * number of bytes that can be read (or skipped over) from this input stream + * without blocking by the next invocation of a method for this input + * stream. * - * @param cipher the cipher instance. - * @param inputStream the input stream. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking or {@code 0} when + * it reaches the end of the input stream. * @throws IOException if an I/O error occurs. */ - protected CryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, - final int bufferSize, final Key key, final AlgorithmParameterSpec params) - throws IOException { - this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, params); - } + @Override + public int available() throws IOException { + checkStream(); - /** - * Constructs a {@link CryptoInputStream}. - * - * @param channel the ReadableByteChannel instance. - * @param cipher the cipher instance. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. - * @throws IOException if an I/O error occurs. - */ - protected CryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, - final int bufferSize, final Key key, final AlgorithmParameterSpec params) - throws IOException { - this(new ChannelInput(channel), cipher, bufferSize, key, params); + return input.available() + outBuffer.remaining(); } /** - * Constructs a {@link CryptoInputStream}. + * Checks whether the stream is closed. * - * @param input the input data. - * @param cipher the cipher instance. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. * @throws IOException if an I/O error occurs. */ - protected CryptoInputStream(final Input input, final CryptoCipher cipher, final int bufferSize, - final Key key, final AlgorithmParameterSpec params) throws IOException { - this.input = input; - this.cipher = cipher; - this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize); - - this.key = key; - this.params = params; - if (!(params instanceof IvParameterSpec)) { - // other AlgorithmParameterSpec such as GCMParameterSpec is not - // supported now. - throw new IOException("Illegal parameters"); + protected void checkStream() throws IOException { + if (closed) { + throw new IOException("Stream closed"); } - - inBuffer = ByteBuffer.allocateDirect(this.bufferSize); - outBuffer = ByteBuffer.allocateDirect(this.bufferSize - + cipher.getBlockSize()); - outBuffer.limit(0); - - initCipher(); } /** - * Overrides the {@link java.io.InputStream#read()}. Reads the next byte of - * data from the input stream. + * Overrides the {@link InputStream#close()}. Closes this input stream and + * releases any system resources associated with the stream. * - * @return the next byte of data, or {@code EOS (-1)} if the end of the - * stream is reached. * @throws IOException if an I/O error occurs. */ @Override - public int read() throws IOException { - int n; - while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD - /* no op */ + public void close() throws IOException { + if (closed) { + return; } - return (n == EOS) ? EOS : oneByteBuf[0] & 0xff; + + input.close(); + freeBuffers(); + cipher.close(); + super.close(); + closed = true; } /** - * Overrides the {@link java.io.InputStream#read(byte[], int, int)}. - * Decryption is buffer based. If there is data in {@link #outBuffer}, then - * read it out of this buffer. If there is no data in {@link #outBuffer}, - * then read more from the underlying stream and do the decryption. + * Does the decryption using inBuffer as input and outBuffer as output. Upon + * return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(). * - * @param array the buffer into which the decrypted data is read. - * @param off the buffer offset. - * @param len the maximum number of decrypted data bytes to read. - * @return int the total number of decrypted data bytes read into the - * buffer. * @throws IOException if an I/O error occurs. */ - @Override - public int read(final byte[] array, final int off, final int len) throws IOException { - checkStream(); - Objects.requireNonNull(array, "array"); - if (off < 0 || len < 0 || len > array.length - off) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return 0; - } + protected void decrypt() throws IOException { + // Prepare the input buffer and clear the out buffer + inBuffer.flip(); + outBuffer.clear(); - final int remaining = outBuffer.remaining(); - if (remaining > 0) { - // Satisfy the read with the existing data - final int n = Math.min(len, remaining); - outBuffer.get(array, off, n); - return n; - } - // No data in the out buffer, try read new data and decrypt it - // we loop for new data - int nd = 0; - while (nd == 0) { - nd = decryptMore(); - } - if (nd < 0) { - return nd; + try { + cipher.update(inBuffer, outBuffer); + } catch (final ShortBufferException e) { + throw new IOException(e); } - final int n = Math.min(len, outBuffer.remaining()); - outBuffer.get(array, off, n); - return n; + // Clear the input buffer and prepare out buffer + inBuffer.clear(); + outBuffer.flip(); } /** - * Overrides the {@link java.io.InputStream#skip(long)}. Skips over and - * discards {@code n} bytes of data from this input stream. + * Does final of the cipher to end the decrypting stream. * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ - @Override - public long skip(final long n) throws IOException { - Utils.checkArgument(n >= 0, "Negative skip length."); - checkStream(); - - if (n == 0) { - return 0; - } - - long remaining = n; - int nd; - - while (remaining > 0) { - if (remaining <= outBuffer.remaining()) { - // Skip in the remaining buffer - final int pos = outBuffer.position() + (int) remaining; - outBuffer.position(pos); - - remaining = 0; - break; - } - remaining -= outBuffer.remaining(); - outBuffer.clear(); + protected void decryptFinal() throws IOException { + // Prepare the input buffer and clear the out buffer + inBuffer.flip(); + outBuffer.clear(); - // we loop for new data - nd = 0; - while (nd == 0) { - nd = decryptMore(); - } - if (nd < 0) { - break; - } + try { + cipher.doFinal(inBuffer, outBuffer); + finalDone = true; + } catch (final ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { + throw new IOException(e); } - return n - remaining; + // Clear the input buffer and prepare out buffer + inBuffer.clear(); + outBuffer.flip(); } /** - * Overrides the {@link InputStream#available()}. Returns an estimate of the - * number of bytes that can be read (or skipped over) from this input stream - * without blocking by the next invocation of a method for this input - * stream. + * Decrypts more data by reading the under layer stream. The decrypted data + * will be put in the output buffer. If the end of the under stream reached, + * we will do final of the cipher to finish all the decrypting of data. * - * @return an estimate of the number of bytes that can be read (or skipped - * over) from this input stream without blocking or {@code 0} when - * it reaches the end of the input stream. + * @return The number of decrypted data. + * return -1 (if end of the decrypted stream) + * return 0 (no data now, but could have more later) * @throws IOException if an I/O error occurs. */ - @Override - public int available() throws IOException { - checkStream(); + protected int decryptMore() throws IOException { + if (finalDone) { + return EOS; + } - return input.available() + outBuffer.remaining(); - } + final int n = input.read(inBuffer); + if (n < 0) { + // The stream is end, finalize the cipher stream + decryptFinal(); - /** - * Overrides the {@link InputStream#close()}. Closes this input stream and - * releases any system resources associated with the stream. - * - * @throws IOException if an I/O error occurs. - */ - @Override - public void close() throws IOException { - if (closed) { - return; - } + // Satisfy the read with the remaining + final int remaining = outBuffer.remaining(); + if (remaining > 0) { + return remaining; + } - input.close(); - freeBuffers(); - cipher.close(); - super.close(); - closed = true; + // End of the stream + return EOS; + } + if (n == 0) { + // No data is read, but the stream is not end yet + return 0; + } + decrypt(); + return outBuffer.remaining(); } - /** - * Overrides the {@link InputStream#markSupported()}. - * - * @return false,the {@link CtrCryptoInputStream} don't support the mark - * method. - */ - @Override - public boolean markSupported() { - return false; + /** Forcibly free the direct buffers. */ + protected void freeBuffers() { + CryptoInputStream.freeDirectBuffer(inBuffer); + CryptoInputStream.freeDirectBuffer(outBuffer); } /** - * Overrides the {@link java.nio.channels.Channel#isOpen()}. + * Gets the buffer size. * - * @return {@code true} if, and only if, this channel is open. + * @return the bufferSize. */ - @Override - public boolean isOpen() { - return !closed; + protected int getBufferSize() { + return bufferSize; } /** - * Overrides the - * {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a - * sequence of bytes from this channel into the given buffer. + * Gets the internal CryptoCipher. * - * @param dst The buffer into which bytes are to be transferred. - * @return The number of bytes read, possibly zero, or {@code EOS (-1)} if the - * channel has reached end-of-stream. - * @throws IOException if an I/O error occurs. + * @return the cipher instance. */ - @Override - public int read(final ByteBuffer dst) throws IOException { - checkStream(); - int remaining = outBuffer.remaining(); - if (remaining <= 0) { - // Decrypt more data - // we loop for new data - int nd = 0; - while (nd == 0) { - nd = decryptMore(); - } - - if (nd < 0) { - return EOS; - } - } - - // Copy decrypted data from outBuffer to dst - remaining = outBuffer.remaining(); - final int toRead = dst.remaining(); - if (toRead <= remaining) { - final int limit = outBuffer.limit(); - outBuffer.limit(outBuffer.position() + toRead); - dst.put(outBuffer); - outBuffer.limit(limit); - return toRead; - } - dst.put(outBuffer); - return remaining; + protected CryptoCipher getCipher() { + return cipher; } /** - * Gets the buffer size. + * Gets the input. * - * @return the bufferSize. + * @return the input. */ - protected int getBufferSize() { - return bufferSize; + protected Input getInput() { + return input; } /** @@ -440,15 +458,6 @@ protected Key getKey() { return key; } - /** - * Gets the internal CryptoCipher. - * - * @return the cipher instance. - */ - protected CryptoCipher getCipher() { - return cipher; - } - /** * Gets the specification of cryptographic parameters. * @@ -458,15 +467,6 @@ protected AlgorithmParameterSpec getParams() { return params; } - /** - * Gets the input. - * - * @return the input. - */ - protected Input getInput() { - return input; - } - /** * Initializes the cipher. * @@ -481,172 +481,172 @@ protected void initCipher() throws IOException { } /** - * Decrypts more data by reading the under layer stream. The decrypted data - * will be put in the output buffer. If the end of the under stream reached, - * we will do final of the cipher to finish all the decrypting of data. + * Overrides the {@link java.nio.channels.Channel#isOpen()}. * - * @return The number of decrypted data. - * return -1 (if end of the decrypted stream) - * return 0 (no data now, but could have more later) - * @throws IOException if an I/O error occurs. + * @return {@code true} if, and only if, this channel is open. */ - protected int decryptMore() throws IOException { - if (finalDone) { - return EOS; - } - - final int n = input.read(inBuffer); - if (n < 0) { - // The stream is end, finalize the cipher stream - decryptFinal(); - - // Satisfy the read with the remaining - final int remaining = outBuffer.remaining(); - if (remaining > 0) { - return remaining; - } - - // End of the stream - return EOS; - } else if (n == 0) { - // No data is read, but the stream is not end yet - return 0; - } else { - decrypt(); - return outBuffer.remaining(); - } + @Override + public boolean isOpen() { + return !closed; } /** - * Does the decryption using inBuffer as input and outBuffer as output. Upon - * return, inBuffer is cleared; the decrypted data starts at - * outBuffer.position() and ends at outBuffer.limit(). + * Overrides the {@link InputStream#markSupported()}. * - * @throws IOException if an I/O error occurs. + * @return {@code false} if the {@link CtrCryptoInputStream} don't support the mark + * method. */ - protected void decrypt() throws IOException { - // Prepare the input buffer and clear the out buffer - inBuffer.flip(); - outBuffer.clear(); - - try { - cipher.update(inBuffer, outBuffer); - } catch (final ShortBufferException e) { - throw new IOException(e); - } - - // Clear the input buffer and prepare out buffer - inBuffer.clear(); - outBuffer.flip(); + @Override + public boolean markSupported() { + return false; } /** - * Does final of the cipher to end the decrypting stream. + * Overrides the {@link java.io.InputStream#read()}. Reads the next byte of + * data from the input stream. * + * @return the next byte of data, or {@code EOS (-1)} if the end of the + * stream is reached. * @throws IOException if an I/O error occurs. */ - protected void decryptFinal() throws IOException { - // Prepare the input buffer and clear the out buffer - inBuffer.flip(); - outBuffer.clear(); - - try { - cipher.doFinal(inBuffer, outBuffer); - finalDone = true; - } catch (final ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { - throw new IOException(e); + @Override + public int read() throws IOException { + int n; + while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD + /* no op */ } - - // Clear the input buffer and prepare out buffer - inBuffer.clear(); - outBuffer.flip(); + return n == EOS ? EOS : oneByteBuf[0] & Utils.BYTE_MASK; } /** - * Checks whether the stream is closed. + * Overrides the {@link java.io.InputStream#read(byte[], int, int)}. + * Decryption is buffer based. If there is data in {@link #outBuffer}, then + * read it out of this buffer. If there is no data in {@link #outBuffer}, + * then read more from the underlying stream and do the decryption. * + * @param array the buffer into which the decrypted data is read. + * @param off the buffer offset. + * @param len the maximum number of decrypted data bytes to read. + * @return int the total number of decrypted data bytes read into the + * buffer. * @throws IOException if an I/O error occurs. */ - protected void checkStream() throws IOException { - if (closed) { - throw new IOException("Stream closed"); + @Override + public int read(final byte[] array, final int off, final int len) throws IOException { + checkStream(); + Objects.requireNonNull(array, "array"); + if (off < 0 || len < 0 || len > array.length - off) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; } - } - /** Forcibly free the direct buffers. */ - protected void freeBuffers() { - CryptoInputStream.freeDirectBuffer(inBuffer); - CryptoInputStream.freeDirectBuffer(outBuffer); + final int remaining = outBuffer.remaining(); + if (remaining > 0) { + // Satisfy the read with the existing data + final int n = Math.min(len, remaining); + outBuffer.get(array, off, n); + return n; + } + // No data in the out buffer, try read new data and decrypt it + // we loop for new data + int nd = 0; + while (nd == 0) { + nd = decryptMore(); + } + if (nd < 0) { + return nd; + } + + final int n = Math.min(len, outBuffer.remaining()); + outBuffer.get(array, off, n); + return n; } /** - * Forcibly free the direct buffer. + * Overrides the + * {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a + * sequence of bytes from this channel into the given buffer. * - * @param buffer the bytebuffer to be freed. + * @param dst The buffer into which bytes are to be transferred. + * @return The number of bytes read, possibly zero, or {@code EOS (-1)} if the + * channel has reached end-of-stream. + * @throws IOException if an I/O error occurs. */ - static void freeDirectBuffer(final ByteBuffer buffer) { - try { - /* Using reflection to implement sun.nio.ch.DirectBuffer.cleaner() - .clean(); */ - final String SUN_CLASS = "sun.nio.ch.DirectBuffer"; - final Class>[] interfaces = buffer.getClass().getInterfaces(); - final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - for (final Class> clazz : interfaces) { - if (clazz.getName().equals(SUN_CLASS)) { - /* DirectBuffer#cleaner() */ - final Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner"); - final Object cleaner = getCleaner.invoke(buffer, EMPTY_OBJECT_ARRAY); - /* Cleaner#clean() */ - final Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean"); - cleanMethod.invoke(cleaner, EMPTY_OBJECT_ARRAY); - return; - } + @Override + public int read(final ByteBuffer dst) throws IOException { + checkStream(); + int remaining = outBuffer.remaining(); + if (remaining <= 0) { + // Decrypt more data + // we loop for new data + int nd = 0; + while (nd == 0) { + nd = decryptMore(); + } + + if (nd < 0) { + return EOS; } - } catch (final ReflectiveOperationException e) { // NOPMD - // Ignore the Reflection exception. } - } - /** - * Reads crypto buffer size. - * - * @param props The {@code Properties} class represents a set of - * properties. - * @return the buffer size. - * */ - static int getBufferSize(final Properties props) { - final String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY); - if (bufferSizeStr == null || bufferSizeStr.isEmpty()) { - return CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT; + // Copy decrypted data from outBuffer to dst + remaining = outBuffer.remaining(); + final int toRead = dst.remaining(); + if (toRead <= remaining) { + final int limit = outBuffer.limit(); + outBuffer.limit(outBuffer.position() + toRead); + dst.put(outBuffer); + outBuffer.limit(limit); + return toRead; } - return Integer.parseInt(bufferSizeStr); + dst.put(outBuffer); + return remaining; } /** - * Checks whether the cipher is supported streaming. + * Overrides the {@link java.io.InputStream#skip(long)}. Skips over and + * discards {@code n} bytes of data from this input stream. * - * @param cipher the {@link CryptoCipher} instance. + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ - static void checkStreamCipher(final CryptoCipher cipher) - throws IOException { - if (!cipher.getAlgorithm().equals("AES/CTR/NoPadding")) { - throw new IOException("AES/CTR/NoPadding is required"); + @Override + public long skip(final long n) throws IOException { + Utils.checkArgument(n >= 0, "Negative skip length."); + checkStream(); + + if (n == 0) { + return 0; } - } - /** - * Checks and floors buffer size. - * - * @param cipher the {@link CryptoCipher} instance. - * @param bufferSize the buffer size. - * @return the remaining buffer size. - */ - static int checkBufferSize(final CryptoCipher cipher, final int bufferSize) { - Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE, - "Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + "."); - return bufferSize - bufferSize - % cipher.getBlockSize(); + long remaining = n; + int nd; + + while (remaining > 0) { + if (remaining <= outBuffer.remaining()) { + // Skip in the remaining buffer + final int pos = outBuffer.position() + (int) remaining; + outBuffer.position(pos); + + remaining = 0; + break; + } + remaining -= outBuffer.remaining(); + outBuffer.clear(); + + // we loop for new data + nd = 0; + while (nd == 0) { + nd = decryptMore(); + } + if (nd < 0) { + break; + } + } + + return n - remaining; } } diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java index 443b2b307..668436b99 100644 --- a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java @@ -82,6 +82,57 @@ public class CryptoOutputStream extends OutputStream implements */ ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further + /** + * Constructs a {@link CryptoOutputStream}. + * + * @param output the output stream. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. + */ + protected CryptoOutputStream(final Output output, final CryptoCipher cipher, + final int bufferSize, final Key key, final AlgorithmParameterSpec params) + throws IOException { + + this.output = output; + this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize); + this.cipher = cipher; + + this.key = key; + this.params = params; + + if (!(params instanceof IvParameterSpec)) { + // other AlgorithmParameterSpec such as GCMParameterSpec is not + // supported now. + throw new IOException("Illegal parameters"); + } + + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + + cipher.getBlockSize()); + + initCipher(); + } + + /** + * Constructs a {@link CryptoOutputStream}. + * + * @param outputStream the output stream. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. + */ + @SuppressWarnings("resource") // Closing the instance closes the StreamOutput + protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher, + final int bufferSize, final Key key, final AlgorithmParameterSpec params) + throws IOException { + this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params); + } + /** * Constructs a {@link CryptoOutputStream}. * @@ -128,22 +179,6 @@ public CryptoOutputStream(final String transformation, } - /** - * Constructs a {@link CryptoOutputStream}. - * - * @param outputStream the output stream. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. - * @throws IOException if an I/O error occurs. - */ - protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher, - final int bufferSize, final Key key, final AlgorithmParameterSpec params) - throws IOException { - this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params); - } - /** * Constructs a {@link CryptoOutputStream}. * @@ -154,6 +189,7 @@ protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher * @param params the algorithm parameters. * @throws IOException if an I/O error occurs. */ + @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput protected CryptoOutputStream(final WritableByteChannel channel, final CryptoCipher cipher, final int bufferSize, final Key key, final AlgorithmParameterSpec params) throws IOException { @@ -161,101 +197,16 @@ protected CryptoOutputStream(final WritableByteChannel channel, final CryptoCiph } /** - * Constructs a {@link CryptoOutputStream}. - * - * @param output the output stream. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. - * @throws IOException if an I/O error occurs. - */ - protected CryptoOutputStream(final Output output, final CryptoCipher cipher, - final int bufferSize, final Key key, final AlgorithmParameterSpec params) - throws IOException { - - this.output = output; - this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize); - this.cipher = cipher; - - this.key = key; - this.params = params; - - if (!(params instanceof IvParameterSpec)) { - // other AlgorithmParameterSpec such as GCMParameterSpec is not - // supported now. - throw new IOException("Illegal parameters"); - } - - inBuffer = ByteBuffer.allocateDirect(this.bufferSize); - outBuffer = ByteBuffer.allocateDirect(this.bufferSize - + cipher.getBlockSize()); - - initCipher(); - } - - /** - * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the - * specified byte to this output stream. - * - * @param b the data. - * @throws IOException if an I/O error occurs. - */ - @Override - public void write(final int b) throws IOException { - oneByteBuf[0] = (byte) (b & 0xff); - write(oneByteBuf, 0, oneByteBuf.length); - } - - /** - * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}. - * Encryption is buffer based. If there is enough room in {@link #inBuffer}, - * then write to this buffer. If {@link #inBuffer} is full, then do - * encryption and write data to the underlying stream. + * Checks whether the stream is closed. * - * @param array the data. - * @param off the start offset in the data. - * @param len the number of bytes to write. * @throws IOException if an I/O error occurs. */ - @Override - public void write(final byte[] array, int off, int len) throws IOException { - checkStream(); - Objects.requireNonNull(array, "array"); - final int arrayLength = array.length; - if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) { - throw new IndexOutOfBoundsException(); - } - - while (len > 0) { - final int remaining = inBuffer.remaining(); - if (len < remaining) { - inBuffer.put(array, off, len); - len = 0; - } else { - inBuffer.put(array, off, remaining); - off += remaining; - len -= remaining; - encrypt(); - } + protected void checkStream() throws IOException { + if (closed) { + throw new IOException("Stream closed"); } } - /** - * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt - * the data in the buffer and write to the underlying stream, then do the - * flush. - * - * @throws IOException if an I/O error occurs. - */ - @Override - public void flush() throws IOException { - checkStream(); - encrypt(); - output.flush(); - super.flush(); - } - /** * Overrides the {@link OutputStream#close()}. Closes this output stream and * releases any system resources associated with this stream. @@ -279,68 +230,6 @@ public void close() throws IOException { } } - /** - * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel - * is open. - * - * @return {@code true} if, and only if, this channel is open - */ - @Override - public boolean isOpen() { - return !closed; - } - - /** - * Overrides the - * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a - * sequence of bytes to this channel from the given buffer. - * - * @param src The buffer from which bytes are to be retrieved. - * @return The number of bytes written, possibly zero. - * @throws IOException if an I/O error occurs. - */ - @Override - public int write(final ByteBuffer src) throws IOException { - checkStream(); - final int len = src.remaining(); - int remaining = len; - while (remaining > 0) { - final int space = inBuffer.remaining(); - if (remaining < space) { - inBuffer.put(src); - remaining = 0; - } else { - // to void copy twice, we set the limit to copy directly - final int oldLimit = src.limit(); - final int newLimit = src.position() + space; - src.limit(newLimit); - - inBuffer.put(src); - - // restore the old limit - src.limit(oldLimit); - - remaining -= space; - encrypt(); - } - } - - return len; - } - - /** - * Initializes the cipher. - * - * @throws IOException if an I/O error occurs. - */ - protected void initCipher() throws IOException { - try { - cipher.init(Cipher.ENCRYPT_MODE, key, params); - } catch (final GeneralSecurityException e) { - throw new IOException(e); - } - } - /** * Does the encryption, input is {@link #inBuffer} and output is * {@link #outBuffer}. @@ -392,14 +281,18 @@ protected void encryptFinal() throws IOException { } /** - * Checks whether the stream is closed. + * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt + * the data in the buffer and write to the underlying stream, then do the + * flush. * * @throws IOException if an I/O error occurs. */ - protected void checkStream() throws IOException { - if (closed) { - throw new IOException("Stream closed"); - } + @Override + public void flush() throws IOException { + checkStream(); + encrypt(); + output.flush(); + super.flush(); } /** Forcibly free the direct buffers. */ @@ -409,12 +302,12 @@ protected void freeBuffers() { } /** - * Gets the outBuffer. + * Gets the buffer size. * - * @return the outBuffer. + * @return the buffer size. */ - protected ByteBuffer getOutBuffer() { - return outBuffer; + protected int getBufferSize() { + return bufferSize; } /** @@ -427,20 +320,129 @@ protected CryptoCipher getCipher() { } /** - * Gets the buffer size. + * Gets the inBuffer. * - * @return the buffer size. + * @return the inBuffer. */ - protected int getBufferSize() { - return bufferSize; + protected ByteBuffer getInBuffer() { + return inBuffer; } /** - * Gets the inBuffer. + * Gets the outBuffer. * - * @return the inBuffer. + * @return the outBuffer. */ - protected ByteBuffer getInBuffer() { - return inBuffer; + protected ByteBuffer getOutBuffer() { + return outBuffer; + } + + /** + * Initializes the cipher. + * + * @throws IOException if an I/O error occurs. + */ + protected void initCipher() throws IOException { + try { + cipher.init(Cipher.ENCRYPT_MODE, key, params); + } catch (final GeneralSecurityException e) { + throw new IOException(e); + } + } + + /** + * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel + * is open. + * + * @return {@code true} if, and only if, this channel is open + */ + @Override + public boolean isOpen() { + return !closed; + } + + /** + * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}. + * Encryption is buffer based. If there is enough room in {@link #inBuffer}, + * then write to this buffer. If {@link #inBuffer} is full, then do + * encryption and write data to the underlying stream. + * + * @param array the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final byte[] array, int off, int len) throws IOException { + checkStream(); + Objects.requireNonNull(array, "array"); + final int arrayLength = array.length; + if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) { + throw new IndexOutOfBoundsException(); + } + + while (len > 0) { + final int remaining = inBuffer.remaining(); + if (len < remaining) { + inBuffer.put(array, off, len); + len = 0; + } else { + inBuffer.put(array, off, remaining); + off += remaining; + len -= remaining; + encrypt(); + } + } + } + + /** + * Overrides the + * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a + * sequence of bytes to this channel from the given buffer. + * + * @param src The buffer from which bytes are to be retrieved. + * @return The number of bytes written, possibly zero. + * @throws IOException if an I/O error occurs. + */ + @Override + public int write(final ByteBuffer src) throws IOException { + checkStream(); + final int len = src.remaining(); + int remaining = len; + while (remaining > 0) { + final int space = inBuffer.remaining(); + if (remaining < space) { + inBuffer.put(src); + remaining = 0; + } else { + // to void copy twice, we set the limit to copy directly + final int oldLimit = src.limit(); + final int newLimit = src.position() + space; + src.limit(newLimit); + + inBuffer.put(src); + + // restore the old limit + src.limit(oldLimit); + + remaining -= space; + encrypt(); + } + } + + return len; + } + + /** + * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the + * specified byte to this output stream. + * + * @param b the data. + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final int b) throws IOException { + oneByteBuf[0] = (byte) (b & Utils.BYTE_MASK); + write(oneByteBuf, 0, oneByteBuf.length); } } diff --git a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java index 47058f3be..89c193ae8 100644 --- a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java +++ b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java @@ -26,13 +26,13 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.stream.input.ChannelInput; import org.apache.commons.crypto.stream.input.Input; import org.apache.commons.crypto.stream.input.StreamInput; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; /** @@ -50,6 +50,48 @@ * The underlying stream offset is maintained as state. It is not thread-safe. */ public class CtrCryptoInputStream extends CryptoInputStream { + /** + *+ * This method is only for Counter (CTR) mode. Generally the CryptoCipher + * calculates the IV and maintain encryption context internally.For example + * a Cipher will maintain its encryption context internally when we do + * encryption/decryption using the CryptoCipher#update interface. + *
+ *+ * Encryption/Decryption is not always on the entire file. For example, in + * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In + * these situations, the counter is derived from the file position. + *
+ * The IV can be calculated by combining the initial IV and the counter with + * a lossless operation (concatenation, addition, or XOR). + * + * @see + * http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29 + * + * @param initIV initial IV + * @param counter counter for input stream position + * @param IV the IV for input stream position + */ + static void calculateIV(final byte[] initIV, long counter, final byte[] IV) { + int i = IV.length; // IV length + + Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE); + Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE); + + int j = 0; // counter bytes index + int sum = 0; + while (i-- > 0) { + // (sum >>> Byte.SIZE) is the carry for addition + sum = (initIV[i] & Utils.BYTE_MASK) + (sum >>> Byte.SIZE); // NOPMD + if (j++ < Long.BYTES) { // Big-endian, and long is 8 bytes length + sum += (byte) counter & Utils.BYTE_MASK; + counter >>>= Long.BYTES; + } + IV[i] = (byte) sum; + } + } + /** * Underlying stream offset */ @@ -80,31 +122,41 @@ public class CtrCryptoInputStream extends CryptoInputStream { /** * Constructs a {@link CtrCryptoInputStream}. * - * @param properties The {@code Properties} class represents a set of - * properties. - * @param inputStream the input stream. + * @param input the input data. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @throws IOException if an I/O error occurs. */ - public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key, - final byte[] iv) throws IOException { - this(properties, inputStream, key, iv, 0); + protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv) throws IOException { + this(input, cipher, bufferSize, key, iv, 0); } /** * Constructs a {@link CtrCryptoInputStream}. * - * @param properties The {@code Properties} class represents a set of - * properties. - * @param channel the ReadableByteChannel instance. + * @param input the input data. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ - public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel, - final byte[] key, final byte[] iv) throws IOException { - this(properties, channel, key, iv, 0); + protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) + throws IOException { + super(input, cipher, bufferSize, AES.newSecretKeySpec(key), + new IvParameterSpec(iv)); + + this.initIV = iv.clone(); + this.iv = iv.clone(); + + CryptoInputStream.checkStreamCipher(cipher); + + resetStreamOffset(streamOffset); } /** @@ -125,31 +177,35 @@ protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher /** * Constructs a {@link CtrCryptoInputStream}. * - * @param channel the ReadableByteChannel instance. - * @param cipher the cipher instance. + * @param inputStream the InputStream instance. + * @param cipher the CryptoCipher instance. * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv) throws IOException { - this(channel, cipher, bufferSize, key, iv, 0); + @SuppressWarnings("resource") // Closing the instance closes the StreamInput + protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) + throws IOException { + this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv, + streamOffset); } /** * Constructs a {@link CtrCryptoInputStream}. * - * @param input the input data. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. + * @param properties The {@code Properties} class represents a set of + * properties. + * @param inputStream the input stream. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv) throws IOException { - this(input, cipher, bufferSize, key, iv, 0); + public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key, + final byte[] iv) throws IOException { + this(properties, inputStream, key, iv, 0); } /** @@ -167,7 +223,7 @@ protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key, final byte[] iv, final long streamOffset) throws IOException { this(inputStream, Utils.getCipherInstance( - "AES/CTR/NoPadding", properties), + AES.CTR_NO_PADDING, properties), CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); } @@ -176,59 +232,54 @@ public CtrCryptoInputStream(final Properties properties, final InputStream input * * @param properties The {@code Properties} class represents a set of * properties. - * @param in the ReadableByteChannel instance. + * @param channel the ReadableByteChannel instance. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ - @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream. - public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel in, - final byte[] key, final byte[] iv, final long streamOffset) throws IOException { - this(in, Utils.getCipherInstance( - "AES/CTR/NoPadding", properties), - CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); + public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel, + final byte[] key, final byte[] iv) throws IOException { + this(properties, channel, key, iv, 0); } /** * Constructs a {@link CtrCryptoInputStream}. * - * @param inputStream the InputStream instance. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. + * @param properties The {@code Properties} class represents a set of + * properties. + * @param in the ReadableByteChannel instance. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) - throws IOException { - this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv, - streamOffset); + @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream. + public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel in, + final byte[] key, final byte[] iv, final long streamOffset) throws IOException { + this(in, Utils.getCipherInstance( + AES.CTR_NO_PADDING, properties), + CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); } /** * Constructs a {@link CtrCryptoInputStream}. * * @param channel the ReadableByteChannel instance. - * @param cipher the CryptoCipher instance. + * @param cipher the cipher instance. * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) - throws IOException { - this(new ChannelInput(channel), cipher, bufferSize, key, iv, streamOffset); + final int bufferSize, final byte[] key, final byte[] iv) throws IOException { + this(channel, cipher, bufferSize, key, iv, 0); } /** * Constructs a {@link CtrCryptoInputStream}. * - * @param input the input data. + * @param channel the ReadableByteChannel instance. * @param cipher the CryptoCipher instance. * @param bufferSize the bufferSize. * @param key crypto key for the cipher. @@ -236,154 +287,123 @@ protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCi * @param streamOffset the start offset in the stream. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, + @SuppressWarnings("resource") // Closing the instance closes the ChannelInput + protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) throws IOException { - super(input, cipher, bufferSize, new SecretKeySpec(key, "AES"), - new IvParameterSpec(iv)); - - this.initIV = iv.clone(); - this.iv = iv.clone(); - - CryptoInputStream.checkStreamCipher(cipher); - - resetStreamOffset(streamOffset); + this(new ChannelInput(channel), cipher, bufferSize, key, iv, streamOffset); } /** - * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and - * discards {@code n} bytes of data from this input stream. + * Does the decryption using inBuffer as input and outBuffer as output. Upon + * return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(). * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ @Override - public long skip(long n) throws IOException { - Utils.checkArgument(n >= 0, "Negative skip length."); - checkStream(); + protected void decrypt() throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if (inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } - if (n == 0) { - return 0; - } else if (n <= outBuffer.remaining()) { - final int pos = outBuffer.position() + (int) n; - outBuffer.position(pos); - return n; - } else { + inBuffer.flip(); + outBuffer.clear(); + decryptBuffer(outBuffer); + inBuffer.clear(); + outBuffer.flip(); + + if (padding > 0) { /* - * Subtract outBuffer.remaining() to see how many bytes we need to - * skip in the underlying stream. Add outBuffer.remaining() to the - * actual number of skipped bytes in the underlying stream to get - * the number of skipped bytes from the user's point of view. + * The plain text and cipher text have a 1:1 mapping, they start at + * the same position. */ - n -= outBuffer.remaining(); - long skipped = input.skip(n); - if (skipped < 0) { - skipped = 0; - } - final long pos = streamOffset + skipped; - skipped += outBuffer.remaining(); - resetStreamOffset(pos); - return skipped; + outBuffer.position(padding); } } /** - * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a - * sequence of bytes from this channel into the given buffer. + * Decrypts all data in buf: total n bytes from given start position. Output + * is also buf and same start position. buf.position() and buf.limit() + * should be unchanged after decryption. * * @param buf The buffer into which bytes are to be transferred. - * @return The number of bytes read, possibly zero, or {@code -1} if the - * channel has reached end-of-stream. + * @param offset the start offset in the data. + * @param len the maximum number of decrypted data bytes to read. * @throws IOException if an I/O error occurs. */ - @Override - public int read(final ByteBuffer buf) throws IOException { - checkStream(); - int unread = outBuffer.remaining(); - if (unread <= 0) { // Fill the unread decrypted data buffer firstly - final int n = input.read(inBuffer); - if (n <= 0) { - return n; - } - - streamOffset += n; // Read n bytes - if (buf.isDirect() && buf.remaining() >= inBuffer.position() - && padding == 0) { - // Use buf as the output buffer directly - decryptInPlace(buf); - padding = postDecryption(streamOffset); - return n; + protected void decrypt(final ByteBuffer buf, final int offset, final int len) + throws IOException { + final int pos = buf.position(); + final int limit = buf.limit(); + int n = 0; + while (n < len) { + buf.position(offset + n); + buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); + inBuffer.put(buf); + // Do decryption + try { + decrypt(); + buf.position(offset + n); + buf.limit(limit); + n += outBuffer.remaining(); + buf.put(outBuffer); + } finally { + padding = postDecryption(streamOffset - (len - n)); } - // Use outBuffer as the output buffer - decrypt(); - padding = postDecryption(streamOffset); } - - // Copy decrypted data from outBuffer to buf - unread = outBuffer.remaining(); - final int toRead = buf.remaining(); - if (toRead <= unread) { - final int limit = outBuffer.limit(); - outBuffer.limit(outBuffer.position() + toRead); - buf.put(outBuffer); - outBuffer.limit(limit); - return toRead; - } - buf.put(outBuffer); - return unread; + buf.position(pos); } /** - * Seeks the stream to a specific position relative to start of the under - * layer stream. + * Does the decryption using out as output. * - * @param position the given position in the data. + * @param out the output ByteBuffer. * @throws IOException if an I/O error occurs. */ - public void seek(final long position) throws IOException { - Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); - checkStream(); - /* - * If data of target pos in the underlying stream has already been read - * and decrypted in outBuffer, we just need to re-position outBuffer. - */ - if (position >= getStreamPosition() && position <= getStreamOffset()) { - final int forward = (int) (position - getStreamPosition()); - if (forward > 0) { - outBuffer.position(outBuffer.position() + forward); - } - } else { - input.seek(position); - resetStreamOffset(position); + protected void decryptBuffer(final ByteBuffer out) throws IOException { + final int inputSize = inBuffer.remaining(); + try { + final int n = cipher.update(inBuffer, out); + if (n < inputSize) { + /** + * Typically code will not get here. CryptoCipher#update will + * consume all input data and put result in outBuffer. + * CryptoCipher#doFinal will reset the cipher context. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (final GeneralSecurityException e) { + throw new IOException(e); } } /** - * Gets the offset of the stream. - * - * @return the stream offset. - */ - protected long getStreamOffset() { - return streamOffset; - } - - /** - * Sets the offset of stream. + * Does the decryption using inBuffer as input and buf as output. Upon + * return, inBuffer is cleared; the buf's position will be equal to + * p {@code +} n where p is the position + * before decryption, n is the number of bytes decrypted. The buf's + * limit will not have changed. * - * @param streamOffset the stream offset. + * @param buf The buffer into which bytes are to be transferred. + * @throws IOException if an I/O error occurs. */ - protected void setStreamOffset(final long streamOffset) { - this.streamOffset = streamOffset; - } + protected void decryptInPlace(final ByteBuffer buf) throws IOException { + Utils.checkState(inBuffer.position() >= padding); + Utils.checkState(buf.isDirect()); + Utils.checkState(buf.remaining() >= inBuffer.position()); + Utils.checkState(padding == 0); - /** - * Gets the position of the stream. - * - * @return the position of the stream. - */ - protected long getStreamPosition() { - return streamOffset - outBuffer.remaining(); + if (inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inBuffer.flip(); + decryptBuffer(buf); + inBuffer.clear(); } /** @@ -407,98 +427,67 @@ protected int decryptMore() throws IOException { } /** - * Does the decryption using inBuffer as input and outBuffer as output. Upon - * return, inBuffer is cleared; the decrypted data starts at - * outBuffer.position() and ends at outBuffer.limit(). + * Gets the counter for input stream position. * - * @throws IOException if an I/O error occurs. + * @param position the given position in the data. + * @return the counter for input stream position. */ - @Override - protected void decrypt() throws IOException { - Utils.checkState(inBuffer.position() >= padding); - if (inBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - - inBuffer.flip(); - outBuffer.clear(); - decryptBuffer(outBuffer); - inBuffer.clear(); - outBuffer.flip(); + protected long getCounter(final long position) { + return position / cipher.getBlockSize(); + } - if (padding > 0) { - /* - * The plain text and cipher text have a 1:1 mapping, they start at - * the same position. - */ - outBuffer.position(padding); - } + /** + * Gets the initialization vector. + * + * @return the initIV. + */ + protected byte[] getInitIV() { + return initIV; } /** - * Does the decryption using inBuffer as input and buf as output. Upon - * return, inBuffer is cleared; the buf's position will be equal to - * p {@code +} n where p is the position - * before decryption, n is the number of bytes decrypted. The buf's - * limit will not have changed. + * Gets the padding for input stream position. * - * @param buf The buffer into which bytes are to be transferred. - * @throws IOException if an I/O error occurs. + * @param position the given position in the data. + * @return the padding for input stream position. */ - protected void decryptInPlace(final ByteBuffer buf) throws IOException { - Utils.checkState(inBuffer.position() >= padding); - Utils.checkState(buf.isDirect()); - Utils.checkState(buf.remaining() >= inBuffer.position()); - Utils.checkState(padding == 0); + protected byte getPadding(final long position) { + return (byte) (position % cipher.getBlockSize()); + } - if (inBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - inBuffer.flip(); - decryptBuffer(buf); - inBuffer.clear(); + /** + * Gets the offset of the stream. + * + * @return the stream offset. + */ + protected long getStreamOffset() { + return streamOffset; } /** - * Decrypts all data in buf: total n bytes from given start position. Output - * is also buf and same start position. buf.position() and buf.limit() - * should be unchanged after decryption. + * Gets the position of the stream. * - * @param buf The buffer into which bytes are to be transferred. - * @param offset the start offset in the data. - * @param len the maximum number of decrypted data bytes to read. - * @throws IOException if an I/O error occurs. + * @return the position of the stream. */ - protected void decrypt(final ByteBuffer buf, final int offset, final int len) - throws IOException { - final int pos = buf.position(); - final int limit = buf.limit(); - int n = 0; - while (n < len) { - buf.position(offset + n); - buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); - inBuffer.put(buf); - // Do decryption - try { - decrypt(); - buf.position(offset + n); - buf.limit(limit); - n += outBuffer.remaining(); - buf.put(outBuffer); - } finally { - padding = postDecryption(streamOffset - (len - n)); - } - } - buf.position(pos); + protected long getStreamPosition() { + return streamOffset - outBuffer.remaining(); + } + + /** + * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the + * cipher. + */ + @Override + protected void initCipher() { + // Do nothing for initCipher + // Will reset the cipher when reset the stream offset } /** * This method is executed immediately after decryption. Checks whether * cipher should be updated and recalculate padding if needed. * - * @param position the given position in the data.. + * @param position the given position in the data. * @return the byte. * @throws IOException if an I/O error occurs. */ @@ -519,42 +508,49 @@ protected byte postDecryption(final long position) throws IOException { } /** - * Gets the initialization vector. - * - * @return the initIV. - */ - protected byte[] getInitIV() { - return initIV; - } - - /** - * Gets the counter for input stream position. + * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a + * sequence of bytes from this channel into the given buffer. * - * @param position the given position in the data. - * @return the counter for input stream position. + * @param buf The buffer into which bytes are to be transferred. + * @return The number of bytes read, possibly zero, or {@code -1} if the + * channel has reached end-of-stream. + * @throws IOException if an I/O error occurs. */ - protected long getCounter(final long position) { - return position / cipher.getBlockSize(); - } + @Override + public int read(final ByteBuffer buf) throws IOException { + checkStream(); + int unread = outBuffer.remaining(); + if (unread <= 0) { // Fill the unread decrypted data buffer firstly + final int n = input.read(inBuffer); + if (n <= 0) { + return n; + } - /** - * Gets the padding for input stream position. - * - * @param position the given position in the data. - * @return the padding for input stream position. - */ - protected byte getPadding(final long position) { - return (byte) (position % cipher.getBlockSize()); - } + streamOffset += n; // Read n bytes + if (buf.isDirect() && buf.remaining() >= inBuffer.position() + && padding == 0) { + // Use buf as the output buffer directly + decryptInPlace(buf); + padding = postDecryption(streamOffset); + return n; + } + // Use outBuffer as the output buffer + decrypt(); + padding = postDecryption(streamOffset); + } - /** - * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the - * cipher. - */ - @Override - protected void initCipher() { - // Do nothing for initCipher - // Will reset the cipher when reset the stream offset + // Copy decrypted data from outBuffer to buf + unread = outBuffer.remaining(); + final int toRead = buf.remaining(); + if (toRead <= unread) { + final int limit = outBuffer.limit(); + outBuffer.limit(outBuffer.position() + toRead); + buf.put(outBuffer); + outBuffer.limit(limit); + return toRead; + } + buf.put(outBuffer); + return unread; } /** @@ -592,68 +588,74 @@ protected void resetStreamOffset(final long offset) throws IOException { } /** - * Does the decryption using out as output. + * Seeks the stream to a specific position relative to start of the under + * layer stream. * - * @param out the output ByteBuffer. + * @param position the given position in the data. * @throws IOException if an I/O error occurs. */ - protected void decryptBuffer(final ByteBuffer out) throws IOException { - final int inputSize = inBuffer.remaining(); - try { - final int n = cipher.update(inBuffer, out); - if (n < inputSize) { - /** - * Typically code will not get here. CryptoCipher#update will - * consume all input data and put result in outBuffer. - * CryptoCipher#doFinal will reset the cipher context. - */ - cipher.doFinal(inBuffer, out); - cipherReset = true; + public void seek(final long position) throws IOException { + Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); + checkStream(); + /* + * If data of target pos in the underlying stream has already been read + * and decrypted in outBuffer, we just need to re-position outBuffer. + */ + if (position >= getStreamPosition() && position <= getStreamOffset()) { + final int forward = (int) (position - getStreamPosition()); + if (forward > 0) { + outBuffer.position(outBuffer.position() + forward); } - } catch (final GeneralSecurityException e) { - throw new IOException(e); + } else { + input.seek(position); + resetStreamOffset(position); } } /** - *- * This method is only for Counter (CTR) mode. Generally the CryptoCipher - * calculates the IV and maintain encryption context internally.For example - * a Cipher will maintain its encryption context internally when we do - * encryption/decryption using the CryptoCipher#update interface. - *
- *- * Encryption/Decryption is not always on the entire file. For example, in - * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In - * these situations, the counter is derived from the file position. - *
- * The IV can be calculated by combining the initial IV and the counter with - * a lossless operation (concatenation, addition, or XOR). - * - * @see - * http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29 + * Sets the offset of stream. * - * @param initIV initial IV - * @param counter counter for input stream position - * @param IV the IV for input stream position + * @param streamOffset the stream offset. */ - static void calculateIV(final byte[] initIV, long counter, final byte[] IV) { - int i = IV.length; // IV length + protected void setStreamOffset(final long streamOffset) { + this.streamOffset = streamOffset; + } - Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE); - Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE); + /** + * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and + * discards {@code n} bytes of data from this input stream. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @throws IOException if an I/O error occurs. + */ + @Override + public long skip(long n) throws IOException { + Utils.checkArgument(n >= 0, "Negative skip length."); + checkStream(); - int j = 0; // counter bytes index - int sum = 0; - while (i-- > 0) { - // (sum >>> Byte.SIZE) is the carry for addition - sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD - if (j++ < 8) { // Big-endian, and long is 8 bytes length - sum += (byte) counter & 0xff; - counter >>>= 8; - } - IV[i] = (byte) sum; + if (n == 0) { + return 0; + } + if (n <= outBuffer.remaining()) { + final int pos = outBuffer.position() + (int) n; + outBuffer.position(pos); + return n; + } + /* + * Subtract outBuffer.remaining() to see how many bytes we need to + * skip in the underlying stream. Add outBuffer.remaining() to the + * actual number of skipped bytes in the underlying stream to get + * the number of skipped bytes from the user's point of view. + */ + n -= outBuffer.remaining(); + long skipped = input.skip(n); + if (skipped < 0) { + skipped = 0; } + final long pos = streamOffset + skipped; + skipped += outBuffer.remaining(); + resetStreamOffset(pos); + return skipped; } } diff --git a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java index c5a0ed8fb..b7a4b1f5c 100644 --- a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java +++ b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java @@ -26,12 +26,12 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.stream.output.ChannelOutput; import org.apache.commons.crypto.stream.output.Output; import org.apache.commons.crypto.stream.output.StreamOutput; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; /** @@ -84,31 +84,41 @@ public class CtrCryptoOutputStream extends CryptoOutputStream { /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param props The {@code Properties} class represents a set of - * properties. - * @param out the output stream. + * @param output the Output instance. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @throws IOException if an I/O error occurs. */ - public CtrCryptoOutputStream(final Properties props, final OutputStream out, - final byte[] key, final byte[] iv) throws IOException { - this(props, out, key, iv, 0); + protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv) throws IOException { + this(output, cipher, bufferSize, key, iv, 0); } /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param props The {@code Properties} class represents a set of - * properties. - * @param out the WritableByteChannel instance. + * @param output the output stream. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ - public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out, - final byte[] key, final byte[] iv) throws IOException { - this(props, out, key, iv, 0); + protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) + throws IOException { + super(output, cipher, bufferSize, AES.newSecretKeySpec(key), + new IvParameterSpec(iv)); + + CryptoInputStream.checkStreamCipher(cipher); + this.streamOffset = streamOffset; + this.initIV = iv.clone(); + this.iv = iv.clone(); + + resetCipher(); } /** @@ -129,32 +139,34 @@ protected CtrCryptoOutputStream(final OutputStream out, final CryptoCipher ciphe /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param channel the WritableByteChannel instance. + * @param outputStream the output stream. * @param cipher the CryptoCipher instance. * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoOutputStream(final WritableByteChannel channel, - final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv) + @SuppressWarnings("resource") // Closing the instance closes the StreamOutput + protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher, + final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) throws IOException { - this(channel, cipher, bufferSize, key, iv, 0); + this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset); } /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param output the Output instance. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. + * @param props The {@code Properties} class represents a set of + * properties. + * @param out the output stream. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv) throws IOException { - this(output, cipher, bufferSize, key, iv, 0); + public CtrCryptoOutputStream(final Properties props, final OutputStream out, + final byte[] key, final byte[] iv) throws IOException { + this(props, out, key, iv, 0); } /** @@ -172,45 +184,42 @@ protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher, public CtrCryptoOutputStream(final Properties properties, final OutputStream outputStream, final byte[] key, final byte[] iv, final long streamOffset) throws IOException { this(outputStream, Utils.getCipherInstance( - "AES/CTR/NoPadding", properties), + AES.CTR_NO_PADDING, properties), CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); } /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param properties The {@code Properties} class represents a set of + * @param props The {@code Properties} class represents a set of * properties. - * @param channel the WritableByteChannel instance. + * @param out the WritableByteChannel instance. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ - @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream. - public CtrCryptoOutputStream(final Properties properties, final WritableByteChannel channel, - final byte[] key, final byte[] iv, final long streamOffset) throws IOException { - this(channel, Utils.getCipherInstance( - "AES/CTR/NoPadding", properties), - CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); + public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out, + final byte[] key, final byte[] iv) throws IOException { + this(props, out, key, iv, 0); } /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param outputStream the output stream. - * @param cipher the CryptoCipher instance. - * @param bufferSize the bufferSize. + * @param properties The {@code Properties} class represents a set of + * properties. + * @param channel the WritableByteChannel instance. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) - throws IOException { - this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, - streamOffset); + @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream. + public CtrCryptoOutputStream(final Properties properties, final WritableByteChannel channel, + final byte[] key, final byte[] iv, final long streamOffset) throws IOException { + this(channel, Utils.getCipherInstance( + AES.CTR_NO_PADDING, properties), + CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); } /** @@ -221,20 +230,18 @@ protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCip * @param bufferSize the bufferSize. * @param key crypto key for the cipher. * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ protected CtrCryptoOutputStream(final WritableByteChannel channel, - final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv, - final long streamOffset) throws IOException { - this(new ChannelOutput(channel), cipher, bufferSize, key, iv, - streamOffset); + final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv) + throws IOException { + this(channel, cipher, bufferSize, key, iv, 0); } /** * Constructs a {@link CtrCryptoOutputStream}. * - * @param output the output stream. + * @param channel the WritableByteChannel instance. * @param cipher the CryptoCipher instance. * @param bufferSize the bufferSize. * @param key crypto key for the cipher. @@ -242,18 +249,11 @@ protected CtrCryptoOutputStream(final WritableByteChannel channel, * @param streamOffset the start offset in the data. * @throws IOException if an I/O error occurs. */ - protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher, - final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) - throws IOException { - super(output, cipher, bufferSize, new SecretKeySpec(key, "AES"), - new IvParameterSpec(iv)); - - CryptoInputStream.checkStreamCipher(cipher); - this.streamOffset = streamOffset; - this.initIV = iv.clone(); - this.iv = iv.clone(); - - resetCipher(); + @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput + protected CtrCryptoOutputStream(final WritableByteChannel channel, + final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv, + final long streamOffset) throws IOException { + this(new ChannelOutput(channel), cipher, bufferSize, key, iv, streamOffset); } /** @@ -298,6 +298,30 @@ protected void encrypt() throws IOException { } } + /** + * Does the encryption if the ByteBuffer data. + * + * @param out the output ByteBuffer. + * @throws IOException if an I/O error occurs. + */ + private void encryptBuffer(final ByteBuffer out) throws IOException { + final int inputSize = inBuffer.remaining(); + try { + final int n = cipher.update(inBuffer, out); + if (n < inputSize) { + /** + * Typically code will not get here. CryptoCipher#update will + * consume all input data and put result in outBuffer. + * CryptoCipher#doFinal will reset the cipher context. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (final GeneralSecurityException e) { + throw new IOException(e); + } + } + /** * Does final encryption of the last data. * @@ -309,6 +333,15 @@ protected void encryptFinal() throws IOException { encrypt(); } + /** + * Gets the underlying stream offset + * + * @return the underlying stream offset + */ + protected long getStreamOffset() { + return streamOffset; + } + /** * Overrides the {@link CryptoOutputStream#initCipher()}. Initializes the * cipher. @@ -340,40 +373,7 @@ private void resetCipher() throws IOException { } /** - * Does the encryption if the ByteBuffer data. - * - * @param out the output ByteBuffer. - * @throws IOException if an I/O error occurs. - */ - private void encryptBuffer(final ByteBuffer out) throws IOException { - final int inputSize = inBuffer.remaining(); - try { - final int n = cipher.update(inBuffer, out); - if (n < inputSize) { - /** - * Typically code will not get here. CryptoCipher#update will - * consume all input data and put result in outBuffer. - * CryptoCipher#doFinal will reset the cipher context. - */ - cipher.doFinal(inBuffer, out); - cipherReset = true; - } - } catch (final GeneralSecurityException e) { - throw new IOException(e); - } - } - - /** - * Get the underlying stream offset - * - * @return the underlying stream offset - */ - protected long getStreamOffset() { - return streamOffset; - } - - /** - * Set the underlying stream offset + * Sets the underlying stream offset * * @param streamOffset the underlying stream offset */ diff --git a/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java index 7387e90af..748f60f18 100644 --- a/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java +++ b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java @@ -28,8 +28,8 @@ import javax.crypto.spec.IvParameterSpec; import org.apache.commons.crypto.cipher.CryptoCipher; -import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.IoUtils; import org.apache.commons.crypto.utils.Utils; @@ -40,15 +40,58 @@ */ public class PositionedCryptoInputStream extends CtrCryptoInputStream { + private static final class CipherState { + + private final CryptoCipher cryptoCipher; + private boolean reset; + + /** + * Constructs a new instance. + * + * @param cryptoCipher the CryptoCipher instance. + */ + public CipherState(final CryptoCipher cryptoCipher) { + this.cryptoCipher = cryptoCipher; + this.reset = false; + } + + /** + * Gets the CryptoCipher instance. + * + * @return the cipher. + */ + public CryptoCipher getCryptoCipher() { + return cryptoCipher; + } + + /** + * Gets the reset. + * + * @return the value of reset. + */ + public boolean isReset() { + return reset; + } + + /** + * Sets the value of reset. + * + * @param reset the reset. + */ + public void reset(final boolean reset) { + this.reset = reset; + } + } + /** * DirectBuffer pool */ - private final QueuebufferPool = new ConcurrentLinkedQueue<>(); + private final Queue byteBufferPool = new ConcurrentLinkedQueue<>(); /** * CryptoCipher pool */ - private final Queue cipherPool = new ConcurrentLinkedQueue<>(); + private final Queue cipherStatePool = new ConcurrentLinkedQueue<>(); /** * properties for constructing a CryptoCipher @@ -69,7 +112,7 @@ public class PositionedCryptoInputStream extends CtrCryptoInputStream { @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by PositionedCryptoInputStream. public PositionedCryptoInputStream(final Properties properties, final Input in, final byte[] key, final byte[] iv, final long streamOffset) throws IOException { - this(properties, in, Utils.getCipherInstance("AES/CTR/NoPadding", properties), + this(properties, in, Utils.getCipherInstance(AES.CTR_NO_PADDING, properties), CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); } @@ -92,62 +135,73 @@ protected PositionedCryptoInputStream(final Properties properties, final Input i this.properties = properties; } - /** - * Reads up to the specified number of bytes from a given position within a - * stream and return the number of bytes read. This does not change the - * current offset of the stream, and is thread-safe. - * - * @param buffer the buffer into which the data is read. - * @param length the maximum number of bytes to read. - * @param offset the start offset in the data. - * @param position the offset from the start of the stream. - * @throws IOException if an I/O error occurs. - * @return int the total number of decrypted data bytes read into the - * buffer. - */ - public int read(final long position, final byte[] buffer, final int offset, final int length) - throws IOException { - checkStream(); - final int n = input.read(position, buffer, offset, length); - if (n > 0) { - // This operation does not change the current offset of the file - decrypt(position, buffer, offset, n); + /** Cleans direct buffer pool */ + private void cleanByteBufferPool() { + ByteBuffer buf; + while ((buf = byteBufferPool.poll()) != null) { + CryptoInputStream.freeDirectBuffer(buf); + } + } + + /** Cleans direct buffer pool */ + private void cleanCipherStatePool() { + CipherState cs; + while ((cs = cipherStatePool.poll()) != null) { + try { + cs.getCryptoCipher().close(); + } catch (IOException ignored) { + // ignore + } } - return n; } /** - * Reads the specified number of bytes from a given position within a - * stream. This does not change the current offset of the stream and is - * thread-safe. + * Overrides the {@link CryptoInputStream#close()}. Closes this input stream + * and releases any system resources associated with the stream. * - * @param buffer the buffer into which the data is read. - * @param length the maximum number of bytes to read. - * @param offset the start offset in the data. - * @param position the offset from the start of the stream. * @throws IOException if an I/O error occurs. */ - public void readFully(final long position, final byte[] buffer, final int offset, final int length) - throws IOException { - checkStream(); - IoUtils.readFully(input, position, buffer, offset, length); - if (length > 0) { - // This operation does not change the current offset of the file - decrypt(position, buffer, offset, length); + @Override + public void close() throws IOException { + if (!isOpen()) { + return; } + + cleanByteBufferPool(); + cleanCipherStatePool(); + super.close(); } /** - * Reads the specified number of bytes from a given position within a - * stream. This does not change the current offset of the stream and is - * thread-safe. + * Does the decryption using inBuffer as input and outBuffer as output. Upon + * return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(). * - * @param position the offset from the start of the stream. - * @param buffer the buffer into which the data is read. + * @param state the CipherState instance. + * @param inByteBuffer the input buffer. + * @param outByteBuffer the output buffer. + * @param padding the padding. * @throws IOException if an I/O error occurs. */ - public void readFully(final long position, final byte[] buffer) throws IOException { - readFully(position, buffer, 0, buffer.length); + private void decrypt(final CipherState state, final ByteBuffer inByteBuffer, + final ByteBuffer outByteBuffer, final byte padding) throws IOException { + Utils.checkState(inByteBuffer.position() >= padding); + if (inByteBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inByteBuffer.flip(); + outByteBuffer.clear(); + decryptBuffer(state, inByteBuffer, outByteBuffer); + inByteBuffer.clear(); + outByteBuffer.flip(); + if (padding > 0) { + /* + * The plain text and cipher text have a 1:1 mapping, they start at + * the same position. + */ + outByteBuffer.position(padding); + } } /** @@ -185,41 +239,9 @@ protected void decrypt(final long position, final byte[] buffer, final int offse padding = postDecryption(state, inByteBuffer, position + n, iv); } } finally { - returnBuffer(inByteBuffer); - returnBuffer(outByteBuffer); - returnCipherState(state); - } - } - - /** - * Does the decryption using inBuffer as input and outBuffer as output. Upon - * return, inBuffer is cleared; the decrypted data starts at - * outBuffer.position() and ends at outBuffer.limit(). - * - * @param state the CipherState instance. - * @param inByteBuffer the input buffer. - * @param outByteBuffer the output buffer. - * @param padding the padding. - * @throws IOException if an I/O error occurs. - */ - private void decrypt(final CipherState state, final ByteBuffer inByteBuffer, - final ByteBuffer outByteBuffer, final byte padding) throws IOException { - Utils.checkState(inByteBuffer.position() >= padding); - if (inByteBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - inByteBuffer.flip(); - outByteBuffer.clear(); - decryptBuffer(state, inByteBuffer, outByteBuffer); - inByteBuffer.clear(); - outByteBuffer.flip(); - if (padding > 0) { - /* - * The plain text and cipher text have a 1:1 mapping, they start at - * the same position. - */ - outByteBuffer.position(padding); + returnToPool(inByteBuffer); + returnToPool(outByteBuffer); + returnToPool(state); } } @@ -231,6 +253,7 @@ private void decrypt(final CipherState state, final ByteBuffer inByteBuffer, * @param outByteBuffer the output buffer. * @throws IOException if an I/O error occurs. */ + @SuppressWarnings("resource") // getCryptoCipher does not allocate private void decryptBuffer(final CipherState state, final ByteBuffer inByteBuffer, final ByteBuffer outByteBuffer) throws IOException { final int inputSize = inByteBuffer.remaining(); @@ -250,6 +273,29 @@ private void decryptBuffer(final CipherState state, final ByteBuffer inByteBuffe } } + /** + * Gets direct buffer from pool. Caller MUST also call {@link #returnToPool(ByteBuffer)}. + * + * @return the buffer. + * @see #returnToPool(ByteBuffer) + */ + private ByteBuffer getBuffer() { + final ByteBuffer buffer = byteBufferPool.poll(); + return buffer != null ? buffer : ByteBuffer.allocateDirect(getBufferSize()); + } + + /** + * Gets CryptoCipher from pool. Caller MUST also call {@link #returnToPool(CipherState)}. + * + * @return the CipherState instance. + * @throws IOException if an I/O error occurs. + */ + @SuppressWarnings("resource") // Caller calls #returnToPool(CipherState) + private CipherState getCipherState() throws IOException { + final CipherState state = cipherStatePool.poll(); + return state != null ? state : new CipherState(Utils.getCipherInstance(AES.CTR_NO_PADDING, properties)); + } + /** * This method is executed immediately after decryption. Check whether * cipher should be updated and recalculate padding if needed. @@ -259,10 +305,9 @@ private void decryptBuffer(final CipherState state, final ByteBuffer inByteBuffe * @param position the offset from the start of the stream. * @param iv the iv. * @return the padding. - * @throws IOException if an I/O error occurs. */ private byte postDecryption(final CipherState state, final ByteBuffer inByteBuffer, - final long position, final byte[] iv) throws IOException { + final long position, final byte[] iv) { byte padding = 0; if (state.isReset()) { /* @@ -279,69 +324,80 @@ private byte postDecryption(final CipherState state, final ByteBuffer inByteBuff } /** - * Calculates the counter and iv, reset the cipher. + * Reads up to the specified number of bytes from a given position within a + * stream and return the number of bytes read. This does not change the + * current offset of the stream, and is thread-safe. * - * @param state the CipherState instance. + * @param buffer the buffer into which the data is read. + * @param length the maximum number of bytes to read. + * @param offset the start offset in the data. * @param position the offset from the start of the stream. - * @param iv the iv. * @throws IOException if an I/O error occurs. + * @return int the total number of decrypted data bytes read into the + * buffer. */ - private void resetCipher(final CipherState state, final long position, final byte[] iv) + public int read(final long position, final byte[] buffer, final int offset, final int length) throws IOException { - final long counter = getCounter(position); - CtrCryptoInputStream.calculateIV(getInitIV(), counter, iv); - try { - state.getCryptoCipher().init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); - } catch (final GeneralSecurityException e) { - // Ignore + checkStream(); + final int n = input.read(position, buffer, offset, length); + if (n > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, n); } - state.reset(false); + return n; } /** - * Gets CryptoCipher from pool. + * Reads the specified number of bytes from a given position within a + * stream. This does not change the current offset of the stream and is + * thread-safe. * - * @return the CipherState instance. + * @param position the offset from the start of the stream. + * @param buffer the buffer into which the data is read. * @throws IOException if an I/O error occurs. */ - private CipherState getCipherState() throws IOException { - CipherState state = cipherPool.poll(); - if (state == null) { - final CryptoCipher cryptoCipher; - try { - cryptoCipher = CryptoCipherFactory.getCryptoCipher("AES/CTR/NoPadding", properties); - } catch (final GeneralSecurityException e) { - throw new IOException(e); - } - state = new CipherState(cryptoCipher); - } - - return state; + public void readFully(final long position, final byte[] buffer) throws IOException { + readFully(position, buffer, 0, buffer.length); } /** - * Returns CryptoCipher to pool. + * Reads the specified number of bytes from a given position within a + * stream. This does not change the current offset of the stream and is + * thread-safe. * - * @param state the CipherState instance. + * @param buffer the buffer into which the data is read. + * @param length the maximum number of bytes to read. + * @param offset the start offset in the data. + * @param position the offset from the start of the stream. + * @throws IOException if an I/O error occurs. */ - private void returnCipherState(final CipherState state) { - if (state != null) { - cipherPool.add(state); + public void readFully(final long position, final byte[] buffer, final int offset, final int length) + throws IOException { + checkStream(); + IoUtils.readFully(input, position, buffer, offset, length); + if (length > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, length); } } /** - * Gets direct buffer from pool. + * Calculates the counter and iv, reset the cipher. * - * @return the buffer. + * @param state the CipherState instance. + * @param position the offset from the start of the stream. + * @param iv the iv. */ - private ByteBuffer getBuffer() { - ByteBuffer buffer = bufferPool.poll(); - if (buffer == null) { - buffer = ByteBuffer.allocateDirect(getBufferSize()); + @SuppressWarnings("resource") // getCryptoCipher does not allocate + private void resetCipher(final CipherState state, final long position, final byte[] iv) { + final long counter = getCounter(position); + CtrCryptoInputStream.calculateIV(getInitIV(), counter, iv); + try { + state.getCryptoCipher().init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + } catch (final GeneralSecurityException e) { + // Ignore } - - return buffer; + state.reset(false); } /** @@ -349,77 +405,21 @@ private ByteBuffer getBuffer() { * * @param buf the buffer. */ - private void returnBuffer(final ByteBuffer buf) { + private void returnToPool(final ByteBuffer buf) { if (buf != null) { buf.clear(); - bufferPool.add(buf); + byteBufferPool.add(buf); } } /** - * Overrides the {@link CryptoInputStream#close()}. Closes this input stream - * and releases any system resources associated with the stream. + * Returns CryptoCipher to pool. * - * @throws IOException if an I/O error occurs. + * @param state the CipherState instance. */ - @Override - public void close() throws IOException { - if (!isOpen()) { - return; - } - - cleanBufferPool(); - super.close(); - } - - /** Clean direct buffer pool */ - private void cleanBufferPool() { - ByteBuffer buf; - while ((buf = bufferPool.poll()) != null) { - CryptoInputStream.freeDirectBuffer(buf); - } - } - - private static class CipherState { - - private final CryptoCipher cryptoCipher; - private boolean reset; - - /** - * The constructor of {@link CipherState}. - * - * @param cipher the CryptoCipher instance. - */ - public CipherState(final CryptoCipher cipher) { - this.cryptoCipher = cipher; - this.reset = false; - } - - /** - * Gets the CryptoCipher instance. - * - * @return the cipher. - */ - public CryptoCipher getCryptoCipher() { - return cryptoCipher; - } - - /** - * Gets the reset. - * - * @return the value of reset. - */ - public boolean isReset() { - return reset; - } - - /** - * Sets the value of reset. - * - * @param reset the reset. - */ - public void reset(final boolean reset) { - this.reset = reset; + private void returnToPool(final CipherState state) { + if (state != null) { + cipherStatePool.add(state); } } } diff --git a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java index b2f26c08d..23699c4ef 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java @@ -20,11 +20,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.util.Objects; + +import org.apache.commons.crypto.stream.CryptoInputStream; /** - * The ChannelInput class takes a {@code ReadableByteChannel} object and - * wraps it as {@code Input} object acceptable by - * {@code CryptoInputStream}. + * The ChannelInput class takes a {@link ReadableByteChannel} object and + * wraps it as {@link Input} object acceptable by + * {@link CryptoInputStream}. */ public class ChannelInput implements Input { private static final int SKIP_BUFFER_SIZE = 2048; @@ -37,76 +40,69 @@ public class ChannelInput implements Input { * {@link org.apache.commons.crypto.stream.input.ChannelInput}. * * @param channel the ReadableByteChannel object. + * @throws NullPointerException if channel is null. */ public ChannelInput(final ReadableByteChannel channel) { - this.channel = channel; + this.channel = Objects.requireNonNull(channel, "channel"); } /** - * Overrides the - * {@link org.apache.commons.crypto.stream.input.Input#read(ByteBuffer)}. - * Reads a sequence of bytes from input into the given buffer. + * Overrides the {@link Input#available()}. Returns an estimate of the + * number of bytes that can be read (or skipped over) from this input stream + * without blocking by the next invocation of a method for this input + * stream. The next invocation might be the same thread or another thread. A + * single read or skip of this many bytes will not block, but may read or + * skip fewer bytes. * - * @param dst The buffer into which bytes are to be transferred. - * @return the total number of bytes read into the buffer, or - * {@code -1} if there is no more data because the end of the - * stream has been reached. + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking or {@code 0} when + * it reaches the end of the input stream. * @throws IOException if an I/O error occurs. */ @Override - public int read(final ByteBuffer dst) throws IOException { - return channel.read(dst); + public int available() throws IOException { + return 0; } /** * Overrides the - * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips - * over and discards {@code n} bytes of data from this input stream. + * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes + * this input and releases any system resources associated with the under + * layer input. * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ @Override - public long skip(final long n) throws IOException { - long remaining = n; - int nr; - - if (n <= 0) { - return 0; - } + public void close() throws IOException { + channel.close(); + } - final int size = (int) Math.min(SKIP_BUFFER_SIZE, remaining); - final ByteBuffer skipBuffer = getSkipBuf(); - while (remaining > 0) { - skipBuffer.clear(); - skipBuffer.limit((int) Math.min(size, remaining)); - nr = read(skipBuffer); - if (nr < 0) { - break; - } - remaining -= nr; + /** + * Gets the skip buffer. + * + * @return the buffer. + */ + private ByteBuffer getSkipBuf() { + if (buf == null) { + buf = ByteBuffer.allocate(SKIP_BUFFER_SIZE); } - - return n - remaining; + return buf; } /** - * Overrides the {@link Input#available()}. Returns an estimate of the - * number of bytes that can be read (or skipped over) from this input stream - * without blocking by the next invocation of a method for this input - * stream. The next invocation might be the same thread or another thread. A - * single read or skip of this many bytes will not block, but may read or - * skip fewer bytes. + * Overrides the + * {@link org.apache.commons.crypto.stream.input.Input#read(ByteBuffer)}. + * Reads a sequence of bytes from input into the given buffer. * - * @return an estimate of the number of bytes that can be read (or skipped - * over) from this input stream without blocking or {@code 0} when - * it reaches the end of the input stream. + * @param dst The buffer into which bytes are to be transferred. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of the + * stream has been reached. * @throws IOException if an I/O error occurs. */ @Override - public int available() throws IOException { - return 0; + public int read(final ByteBuffer dst) throws IOException { + return channel.read(dst); } /** @@ -150,26 +146,34 @@ public void seek(final long position) throws IOException { /** * Overrides the - * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes - * this input and releases any system resources associated with the under - * layer input. + * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips + * over and discards {@code n} bytes of data from this input stream. * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ @Override - public void close() throws IOException { - channel.close(); - } + public long skip(final long n) throws IOException { + long remaining = n; + int nr; - /** - * Gets the skip buffer. - * - * @return the buffer. - */ - private ByteBuffer getSkipBuf() { - if (buf == null) { - buf = ByteBuffer.allocate(SKIP_BUFFER_SIZE); + if (n <= 0) { + return 0; } - return buf; + + final int size = (int) Math.min(SKIP_BUFFER_SIZE, remaining); + final ByteBuffer skipBuffer = getSkipBuf(); + while (remaining > 0) { + skipBuffer.clear(); + skipBuffer.limit((int) Math.min(size, remaining)); + nr = read(skipBuffer); + if (nr < 0) { + break; + } + remaining -= nr; + } + + return n - remaining; } } diff --git a/src/main/java/org/apache/commons/crypto/stream/input/Input.java b/src/main/java/org/apache/commons/crypto/stream/input/Input.java index b7d6f92c5..2192050a0 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/Input.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/Input.java @@ -19,16 +19,47 @@ import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +import org.apache.commons.crypto.stream.CryptoInputStream; /** * The Input interface abstract the input source of - * {@code CryptoInputStream} so that different implementation of input can - * be used. The implementation Input interface will usually wraps an input - * mechanism such as {@code InputStream} or - * {@code ReadableByteChannel}. + * {@link CryptoInputStream} so that different implementation of input can + * be used. The implementation Input interface will usually wrap an input + * mechanism such as {@link InputStream} or + * {@link ReadableByteChannel}. */ public interface Input extends Closeable { + /** + * Returns an estimate of the number of bytes that can be read (or skipped + * over) from this input without blocking by the next invocation of a method + * for this input stream. The next invocation might be the same thread or + * another thread. A single read or skip of this many bytes will not block, + * but may read or skip fewer bytes. + * + * + * It is never correct to use the return value of this method to allocate a + * buffer intended to hold all data in this stream. + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking or {@code 0} when + * it reaches the end of the input stream. + * @throws IOException if an I/O error occurs. + */ + int available() throws IOException; + + /** + * Closes this input and releases any system resources associated with the + * under layer input. + * + * @throws IOException if an I/O error occurs. + */ + @Override + void close() throws IOException; + /** * Reads a sequence of bytes from input into the given buffer. * @@ -55,47 +86,6 @@ public interface Input extends Closeable { */ int read(ByteBuffer dst) throws IOException; - /** - * Skips over and discards {@code n} bytes of data from this input The - * {@code skip} method may, for a variety of reasons, end up skipping - * over some smaller number of bytes, possibly {@code 0}. This may - * result from any of a number of conditions; reaching end of file before - * {@code n} bytes have been skipped is only one possibility. The - * actual number of bytes skipped is returned. If {@code n} is - * negative, no bytes are skipped. - * - *
- * The {@code skip} method of this class creates a byte array and then - * repeatedly reads into it until {@code n} bytes have been read or the - * end of the stream has been reached. Subclasses are encouraged to provide - * a more efficient implementation of this method. For instance, the - * implementation may depend on the ability to seek. - * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. - * @throws IOException if the stream does not support seek, or if some - * other I/O error occurs. - */ - long skip(long n) throws IOException; - - /** - * Returns an estimate of the number of bytes that can be read (or skipped - * over) from this input without blocking by the next invocation of a method - * for this input stream. The next invocation might be the same thread or - * another thread. A single read or skip of this many bytes will not block, - * but may read or skip fewer bytes. - * - *
- * It is never correct to use the return value of this method to allocate a - * buffer intended to hold all data in this stream. - * - * @return an estimate of the number of bytes that can be read (or skipped - * over) from this input stream without blocking or {@code 0} when - * it reaches the end of the input stream. - * @throws IOException if an I/O error occurs. - */ - int available() throws IOException; - /** * Reads up to the specified number of bytes from a given position within a * stream and return the number of bytes read. This does not change the @@ -129,11 +119,25 @@ int read(long position, byte[] buffer, int offset, int length) void seek(long position) throws IOException; /** - * Closes this input and releases any system resources associated with the - * under layer input. + * Skips over and discards {@code n} bytes of data from this input The + * {@code skip} method may, for a variety of reasons, end up skipping + * over some smaller number of bytes, possibly {@code 0}. This may + * result from any of a number of conditions; reaching end of file before + * {@code n} bytes have been skipped is only one possibility. The + * actual number of bytes skipped is returned. If {@code n} is + * negative, no bytes are skipped. * - * @throws IOException if an I/O error occurs. + *
+ * The {@code skip} method of this class creates a byte array and then + * repeatedly reads into it until {@code n} bytes have been read or the + * end of the stream has been reached. Subclasses are encouraged to provide + * a more efficient implementation of this method. For instance, the + * implementation may depend on the ability to seek. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @throws IOException if the stream does not support seek, or if some + * other I/O error occurs. */ - @Override - void close() throws IOException; + long skip(long n) throws IOException; } diff --git a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java index 76c163495..674df6d83 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java @@ -17,17 +17,21 @@ */ package org.apache.commons.crypto.stream.input; +import static org.apache.commons.crypto.stream.CryptoInputStream.EOS; + import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Objects; -import static org.apache.commons.crypto.stream.CryptoInputStream.EOS; +import org.apache.commons.crypto.stream.CryptoInputStream; /** - * The StreamInput class takes a {@code InputStream} object and wraps it as - * {@code Input} object acceptable by {@code CryptoInputStream}. + * The StreamInput class takes a {@link InputStream} object and wraps it as + * {@link Input} object acceptable by {@link CryptoInputStream}. */ public class StreamInput implements Input { + private final byte[] buf; private final int bufferSize; final InputStream in; @@ -35,13 +39,45 @@ public class StreamInput implements Input { /** * Constructs a {@link org.apache.commons.crypto.stream.input.StreamInput}. * - * @param inputStream the inputstream object. - * @param bufferSize the buffersize. + * @param inputStream the InputStream object. + * @param bufferSize the buffer size. + * @throws NullPointerException if inputStream is null. */ public StreamInput(final InputStream inputStream, final int bufferSize) { - this.in = inputStream; + this.in = Objects.requireNonNull(inputStream, "inputStream"); this.bufferSize = bufferSize; - buf = new byte[bufferSize]; + this.buf = new byte[bufferSize]; + } + + /** + * Overrides the {@link Input#available()}. Returns an estimate of the + * number of bytes that can be read (or skipped over) from this input stream + * without blocking by the next invocation of a method for this input + * stream. The next invocation might be the same thread or another thread. A + * single read or skip of this many bytes will not block, but may read or + * skip fewer bytes. + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking or {@code 0} when + * it reaches the end of the input stream. + * @throws IOException if an I/O error occurs. + */ + @Override + public int available() throws IOException { + return in.available(); + } + + /** + * Overrides the + * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes + * this input and releases any system resources associated with the under + * layer input. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + in.close(); } /** @@ -67,7 +103,8 @@ public int read(final ByteBuffer dst) throws IOException { read = EOS; } break; - } else if (n > 0) { + } + if (n > 0) { dst.put(buf, 0, n); read += n; remaining -= n; @@ -76,38 +113,6 @@ public int read(final ByteBuffer dst) throws IOException { return read; } - /** - * Overrides the - * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips - * over and discards {@code n} bytes of data from this input stream. - * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. - * @throws IOException if an I/O error occurs. - */ - @Override - public long skip(final long n) throws IOException { - return in.skip(n); - } - - /** - * Overrides the {@link Input#available()}. Returns an estimate of the - * number of bytes that can be read (or skipped over) from this input stream - * without blocking by the next invocation of a method for this input - * stream. The next invocation might be the same thread or another thread. A - * single read or skip of this many bytes will not block, but may read or - * skip fewer bytes. - * - * @return an estimate of the number of bytes that can be read (or skipped - * over) from this input stream without blocking or {@code 0} when - * it reaches the end of the input stream. - * @throws IOException if an I/O error occurs. - */ - @Override - public int available() throws IOException { - return in.available(); - } - /** * Overrides the * {@link org.apache.commons.crypto.stream.input.Input#read(long, byte[], int, int)} @@ -126,10 +131,8 @@ public int available() throws IOException { * @throws IOException if an I/O error occurs. */ @Override - public int read(final long position, final byte[] buffer, final int offset, final int length) - throws IOException { - throw new UnsupportedOperationException( - "Positioned read is not supported by this implementation"); + public int read(final long position, final byte[] buffer, final int offset, final int length) throws IOException { + throw new UnsupportedOperationException("Positioned read is not supported by this implementation"); } /** @@ -143,20 +146,20 @@ public int read(final long position, final byte[] buffer, final int offset, fina */ @Override public void seek(final long position) throws IOException { - throw new UnsupportedOperationException( - "Seek is not supported by this implementation"); + throw new UnsupportedOperationException("Seek is not supported by this implementation"); } /** * Overrides the - * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes - * this input and releases any system resources associated with the under - * layer input. + * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips + * over and discards {@code n} bytes of data from this input stream. * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. */ @Override - public void close() throws IOException { - in.close(); + public long skip(final long n) throws IOException { + return in.skip(n); } } diff --git a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java index 8f517c860..9fc617dda 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java @@ -20,11 +20,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; +import java.util.Objects; + +import org.apache.commons.crypto.stream.CryptoOutputStream; /** - * The ChannelOutput class takes a {@code WritableByteChannel} object and + * The ChannelOutput class takes a {@link WritableByteChannel} object and * wraps it as {@code Output} object acceptable by - * {@code CryptoOutputStream} as the output target. + * {@link CryptoOutputStream} as the output target. */ public class ChannelOutput implements Output { @@ -35,24 +38,21 @@ public class ChannelOutput implements Output { * {@link org.apache.commons.crypto.stream.output.ChannelOutput}. * * @param channel the WritableByteChannel object. + * @throws NullPointerException if channel is null. */ public ChannelOutput(final WritableByteChannel channel) { - this.channel = channel; + this.channel = Objects.requireNonNull(channel, "channel"); } /** - * Overrides the - * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}. - * Writes a sequence of bytes to this output from the given buffer. - * - * @param src The buffer from which bytes are to be retrieved. + * Overrides the {@link Output#close()}. Closes this output and releases any + * system resources associated with the under layer output. * - * @return The number of bytes written, possibly zero. * @throws IOException if an I/O error occurs. */ @Override - public int write(final ByteBuffer src) throws IOException { - return channel.write(src); + public void close() throws IOException { + channel.close(); } /** @@ -68,13 +68,17 @@ public void flush() throws IOException { } /** - * Overrides the {@link Output#close()}. Closes this output and releases any - * system resources associated with the under layer output. + * Overrides the + * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}. + * Writes a sequence of bytes to this output from the given buffer. + * + * @param src The buffer from which bytes are to be retrieved. * + * @return The number of bytes written, possibly zero. * @throws IOException if an I/O error occurs. */ @Override - public void close() throws IOException { - channel.close(); + public int write(final ByteBuffer src) throws IOException { + return channel.write(src); } } diff --git a/src/main/java/org/apache/commons/crypto/stream/output/Output.java b/src/main/java/org/apache/commons/crypto/stream/output/Output.java index 7df87fafa..30812ce0b 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/Output.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/Output.java @@ -19,17 +19,42 @@ import java.io.Closeable; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +import org.apache.commons.crypto.stream.CryptoOutputStream; /** * The Output interface abstract the output target of - * {@code CryptoOutputStream} so that different implementation of output - * can be used. The implementation Output interface will usually wraps an output - * mechanism such as {@code OutputStream} or - * {@code WritableByteChannel}. + * {@link CryptoOutputStream} so that different implementation of output + * can be used. The implementation Output interface will usually wrap an output + * mechanism such as {@link OutputStream} or + * {@link WritableByteChannel}. */ public interface Output extends Closeable { + /** + * Closes this output and releases any system resources associated with the + * under layer output. + * + * @throws IOException if an I/O error occurs. + */ + @Override + void close() throws IOException; + + /** + * Flushes this output and forces any buffered output bytes to be written + * out if the under layer output method support. The general contract of + * {@code flush} is that calling it is an indication that, if any bytes + * previously written have been buffered by the implementation of the output + * stream, such bytes should immediately be written to their intended + * destination. + * + * @throws IOException if an I/O error occurs. + */ + void flush() throws IOException; + /** * Writes a sequence of bytes to this output from the given buffer. * @@ -55,25 +80,4 @@ public interface Output extends Closeable { * @throws IOException If some other I/O error occurs. */ int write(ByteBuffer src) throws IOException; - - /** - * Flushes this output and forces any buffered output bytes to be written - * out if the under layer output method support. The general contract of - * {@code flush} is that calling it is an indication that, if any bytes - * previously written have been buffered by the implementation of the output - * stream, such bytes should immediately be written to their intended - * destination. - * - * @throws IOException if an I/O error occurs. - */ - void flush() throws IOException; - - /** - * Closes this output and releases any system resources associated with the - * under layer output. - * - * @throws IOException if an I/O error occurs. - */ - @Override - void close() throws IOException; } diff --git a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java index dd293b402..22fdce8a5 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java @@ -20,10 +20,13 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.Objects; + +import org.apache.commons.crypto.stream.CryptoOutputStream; /** - * The StreamOutput class takes a {@code OutputStream} object and wraps it - * as {@code Output} object acceptable by {@code CryptoOutputStream} + * The StreamOutput class takes a {@link OutputStream} object and wraps it + * as {@link Output} object acceptable by {@link CryptoOutputStream} * as the output target. */ public class StreamOutput implements Output { @@ -32,41 +35,27 @@ public class StreamOutput implements Output { private final OutputStream out; /** - * Constructs a {@link org.apache.commons.crypto.stream.output.StreamOutput} - * . + * Constructs a new instance. * * @param out the OutputStream object. - * @param bufferSize the buffersize. + * @param bufferSize the buffer size. + * @throws NullPointerException if channel is null. */ public StreamOutput(final OutputStream out, final int bufferSize) { - this.out = out; + this.out = Objects.requireNonNull(out, "out"); this.bufferSize = bufferSize; - buf = new byte[bufferSize]; + this.buf = new byte[bufferSize]; } /** - * Overrides the - * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}. - * Writes a sequence of bytes to this output from the given buffer. - * - * @param src The buffer from which bytes are to be retrieved. + * Overrides the {@link Output#close()}. Closes this output and releases any + * system resources associated with the under layer output. * - * @return The number of bytes written, possibly zero. * @throws IOException if an I/O error occurs. */ @Override - public int write(final ByteBuffer src) throws IOException { - final int len = src.remaining(); - - int remaining = len; - while (remaining > 0) { - final int n = Math.min(remaining, bufferSize); - src.get(buf, 0, n); - out.write(buf, 0, n); - remaining = src.remaining(); - } - - return len; + public void close() throws IOException { + out.close(); } /** @@ -82,22 +71,36 @@ public void flush() throws IOException { } /** - * Overrides the {@link Output#close()}. Closes this output and releases any - * system resources associated with the under layer output. + * Gets the output stream. * - * @throws IOException if an I/O error occurs. + * @return the output stream. */ - @Override - public void close() throws IOException { - out.close(); + protected OutputStream getOut() { + return out; } /** - * Gets the output stream. + * Overrides the + * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}. + * Writes a sequence of bytes to this output from the given buffer. * - * @return the output stream. + * @param src The buffer from which bytes are to be retrieved. + * + * @return The number of bytes written, possibly zero. + * @throws IOException if an I/O error occurs. */ - protected OutputStream getOut() { - return out; + @Override + public int write(final ByteBuffer src) throws IOException { + final int len = src.remaining(); + + int remaining = len; + while (remaining > 0) { + final int n = Math.min(remaining, bufferSize); + src.get(buf, 0, n); + out.write(buf, 0, n); + remaining = src.remaining(); + } + + return len; } } diff --git a/src/main/java/org/apache/commons/crypto/utils/AES.java b/src/main/java/org/apache/commons/crypto/utils/AES.java new file mode 100644 index 000000000..bb83bdec1 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/utils/AES.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +package org.apache.commons.crypto.utils; + +import javax.crypto.spec.SecretKeySpec; + +/** + * Creates AES objects + * + * @since 1.2.0 + */ +public class AES { + + /** The AES algorithm name. */ + public static final String ALGORITHM = "AES"; + + /** + * Defines {@value}. + */ + public static final String CBC_NO_PADDING = "AES/CBC/NoPadding"; + + /** + * Defines {@value}. + */ + public static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding"; + + /** + * Defines {@value}. + */ + public static final String CTR_NO_PADDING = "AES/CTR/NoPadding"; + + /** + * Creates a new SecretKeySpec for the given key and {@link #ALGORITHM}. + * + * @param key a key. + * @return a new SecretKeySpec. + */ + public static SecretKeySpec newSecretKeySpec(final byte[] key) { + return new SecretKeySpec(key, ALGORITHM); + } + + + /** + * Constructs a new instance. + * + * @deprecated Will be private in the next major release. + */ + @Deprecated + public AES() { + // empty + } +} diff --git a/src/main/java/org/apache/commons/crypto/utils/IoUtils.java b/src/main/java/org/apache/commons/crypto/utils/IoUtils.java index 86e1c91f6..a7ef2be5e 100644 --- a/src/main/java/org/apache/commons/crypto/utils/IoUtils.java +++ b/src/main/java/org/apache/commons/crypto/utils/IoUtils.java @@ -29,30 +29,31 @@ public final class IoUtils { /** - * The private constructor of {@link IoUtils}. + * Closes the Closeable objects and ignore any {@link IOException} or + * null pointers. Must only be used for cleanup in exception handlers. + * + * @param closeables the objects to close. */ - private IoUtils() { + public static void cleanup(final Closeable... closeables) { + if (closeables != null) { + for (final Closeable c : closeables) { + closeQuietly(c); + } + } } /** - * Does the readFully based on the Input read. + * Closes the given {@link Closeable} quietly by ignoring IOException. * - * @param in the input stream of bytes. - * @param buf the buffer to be read. - * @param off the start offset in array buffer. - * @param len the maximum number of bytes to read. - * @throws IOException if an I/O error occurs. + * @param closeable The resource to close. + * @since 1.1.0 */ - public static void readFully(final InputStream in, final byte[] buf, int off, final int len) - throws IOException { - int toRead = len; - while (toRead > 0) { - final int ret = in.read(buf, off, toRead); - if (ret < 0) { - throw new IOException("Premature EOF from inputStream"); + public static void closeQuietly(final Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (final IOException e) { // NOPMD } - toRead -= ret; - off += ret; } } @@ -82,31 +83,30 @@ public static void readFully(final Input in, final long position, final byte[] b } /** - * Closes the Closeable objects and ignore any {@link IOException} or - * null pointers. Must only be used for cleanup in exception handlers. + * Does the readFully based on the Input read. * - * @param closeables the objects to close. + * @param in the input stream of bytes. + * @param buf the buffer to be read. + * @param off the start offset in array buffer. + * @param len the maximum number of bytes to read. + * @throws IOException if an I/O error occurs. */ - public static void cleanup(final Closeable... closeables) { - if (closeables != null) { - for (final Closeable c : closeables) { - closeQuietly(c); + public static void readFully(final InputStream in, final byte[] buf, int off, final int len) + throws IOException { + int toRead = len; + while (toRead > 0) { + final int ret = in.read(buf, off, toRead); + if (ret < 0) { + throw new IOException("Premature EOF from inputStream"); } + toRead -= ret; + off += ret; } } /** - * Closes the given {@link Closeable} quietly by ignoring IOException. - * - * @param closeable The resource to close. - * @since 1.1.0 + * The private constructor of {@link IoUtils}. */ - public static void closeQuietly(final Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (final IOException e) { // NOPMD - } - } + private IoUtils() { } } diff --git a/src/main/java/org/apache/commons/crypto/utils/Padding.java b/src/main/java/org/apache/commons/crypto/utils/Padding.java new file mode 100644 index 000000000..59d82e4be --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/utils/Padding.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package org.apache.commons.crypto.utils; + +import javax.crypto.NoSuchPaddingException; + +/** + * Padding types. + */ +public enum Padding { + + /** Don't change the order of this enum value. */ + NoPadding, + + /** Don't change the order of this enum value. */ + PKCS5Padding; + + /** + * Gets a Padding. + * + * @param padding the padding name. + * @return a Padding instance. + * @throws NoSuchPaddingException if the algorithm is not supported. + */ + public static Padding get(final String padding) throws NoSuchPaddingException { + try { + return Padding.valueOf(padding); + } catch (final Exception e) { + throw new NoSuchPaddingException("Algorithm not supported: " + padding); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java index dbde489e6..4f60dd8cf 100644 --- a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java +++ b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java @@ -19,9 +19,14 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.commons.crypto.cipher.CryptoCipher; @@ -30,66 +35,28 @@ */ public final class ReflectionUtils { + /** + * A unique class which is used as a sentinel value in the caching for + * getClassByName. {@link #getClassByNameOrNull(String)}. + */ + private static abstract class AbstractNegativeCacheSentinel { + // noop + } + private static final Map
>>> CACHE_CLASSES = new WeakHashMap<>(); + private static final ConcurrentMap > INIT_ERROR_CLASSES = new ConcurrentHashMap<>(); - private static final ClassLoader CLASSLOADER; + private static final ClassLoader CLASS_LOADER; static { final ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); - CLASSLOADER = (threadClassLoader != null) ? threadClassLoader : CryptoCipher.class.getClassLoader(); + CLASS_LOADER = threadClassLoader != null ? threadClassLoader : CryptoCipher.class.getClassLoader(); } /** * Sentinel value to store negative cache results in {@link #CACHE_CLASSES}. */ - private static final Class> NEGATIVE_CACHE_SENTINEL = NegativeCacheSentinel.class; - - /** - * The private constructor of {@link ReflectionUtils}. - */ - private ReflectionUtils() { - } - - /** - * A unique class which is used as a sentinel value in the caching for - * getClassByName. {@link #getClassByNameOrNull(String)}. - */ - private static abstract class NegativeCacheSentinel { - // noop - } - - /** - * Uses the constructor represented by this {@code Constructor} object to create - * and initialize a new instance of the constructor's declaring class, with the - * specified initialization parameters. - * - * @param type for the new instance - * @param klass the Class object. - * @param args array of objects to be passed as arguments to the constructor - * call. - * @return a new object created by calling the constructor this object - * represents. - */ - public static T newInstance(final Class klass, final Object... args) { - try { - final Constructor ctor; - final int argsLength = args.length; - - if (argsLength == 0) { - ctor = klass.getDeclaredConstructor(); - } else { - final Class>[] argClses = new Class[argsLength]; - for (int i = 0; i < argsLength; i++) { - argClses[i] = args[i].getClass(); - } - ctor = klass.getDeclaredConstructor(argClses); - } - ctor.setAccessible(true); - return ctor.newInstance(args); - } catch (final Exception e) { - throw new IllegalArgumentException(e); - } - } + private static final Class> NEGATIVE_CACHE_SENTINEL = AbstractNegativeCacheSentinel.class; /** * Loads a class by name. @@ -101,23 +68,32 @@ public static T newInstance(final Class klass, final Object... args) { public static Class> getClassByName(final String name) throws ClassNotFoundException { final Class> ret = getClassByNameOrNull(name); if (ret == null) { + if (INIT_ERROR_CLASSES.get(CLASS_LOADER).contains(name)) { + throw new IllegalStateException("Class " + name + " initialization error"); + } throw new ClassNotFoundException("Class " + name + " not found"); } return ret; } /** - * Loads a class by name, returning null rather than throwing an exception if it + * Loads a class by name, returning {@code null} rather than throwing an exception if it * couldn't be loaded. This is to avoid the overhead of creating an exception. * * @param name the class name. - * @return the class object, or null if it could not be found. + * @return the class object, or {@code null} if it could not be found or initialization failed. */ private static Class> getClassByNameOrNull(final String name) { + final Set set = INIT_ERROR_CLASSES.computeIfAbsent(CLASS_LOADER, k -> Collections.synchronizedSet(new HashSet<>())); + + if (set.contains(name)) { + return null; + } + final Map >> map; synchronized (CACHE_CLASSES) { - map = CACHE_CLASSES.computeIfAbsent(CLASSLOADER, k -> Collections.synchronizedMap(new WeakHashMap<>())); + map = CACHE_CLASSES.computeIfAbsent(CLASS_LOADER, k -> Collections.synchronizedMap(new WeakHashMap<>())); } Class> clazz = null; @@ -128,20 +104,61 @@ private static Class> getClassByNameOrNull(final String name) { if (clazz == null) { try { - clazz = Class.forName(name, true, CLASSLOADER); + clazz = Class.forName(name, true, CLASS_LOADER); } catch (final ClassNotFoundException e) { // Leave a marker that the class isn't found map.put(name, new WeakReference<>(NEGATIVE_CACHE_SENTINEL)); return null; + } catch (final ExceptionInInitializerError error) { + // Leave a marker that the class initialization failed + set.add(name); + return null; } // two putters can race here, but they'll put the same class map.put(name, new WeakReference<>(clazz)); return clazz; - } else if (clazz == NEGATIVE_CACHE_SENTINEL) { + } + if (clazz == NEGATIVE_CACHE_SENTINEL) { return null; // not found - } else { - // cache hit - return clazz; } + // cache hit + return clazz; + } + + /** + * Uses the constructor represented by this {@code Constructor} object to create + * and initialize a new instance of the constructor's declaring class, with the + * specified initialization parameters. + * + * @param type for the new instance + * @param klass the Class object. + * @param args array of objects to be passed as arguments to the constructor + * call. + * @return a new object created by calling the constructor this object + * represents. + */ + public static T newInstance(final Class klass, final Object... args) { + try { + final Constructor ctor; + final int argsLength = args.length; + + if (argsLength == 0) { + ctor = klass.getDeclaredConstructor(); + } else { + final Class>[] argClses = new Class[argsLength]; + Arrays.setAll(argClses, i -> args[i].getClass()); + ctor = klass.getDeclaredConstructor(argClses); + } + ctor.setAccessible(true); + return ctor.newInstance(args); + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * The private constructor of {@link ReflectionUtils}. + */ + private ReflectionUtils() { } } diff --git a/src/main/java/org/apache/commons/crypto/utils/Transformation.java b/src/main/java/org/apache/commons/crypto/utils/Transformation.java new file mode 100644 index 000000000..e2ebfd827 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/utils/Transformation.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package org.apache.commons.crypto.utils; + +import java.security.NoSuchAlgorithmException; + +import javax.crypto.NoSuchPaddingException; + +/** + * Transformation algorithm, mode and padding, in the format "Algorithm/Mode/Padding", for example "AES/CBC/NoPadding". + * + * @since 1.2.0 + */ +public class Transformation { + + private static final int T_DELIM_PARTS = 3; + private static final String T_DELIM_REGEX = "/"; + + /** + * Parses a transformation. + * + * @param transformation current transformation + * @return the Transformation + * @throws NoSuchAlgorithmException if the algorithm is not supported + * @throws NoSuchPaddingException Thrown when the padding is unsupported. + */ + public static Transformation parse(final String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { + if (transformation == null) { + throw new NoSuchAlgorithmException("No transformation given."); + } + + // + // Array containing the components of a Cipher transformation: index 0: + // algorithm (e.g., AES) index 1: mode (e.g., CTR) index 2: padding (e.g., + // NoPadding) + // + final String[] parts = transformation.split(T_DELIM_REGEX, T_DELIM_PARTS + 1); + if (parts.length != T_DELIM_PARTS) { + throw new NoSuchAlgorithmException("Invalid transformation format: " + transformation); + } + return new Transformation(parts[0], parts[1], parts[2]); + } + + private final String algorithm; + private final String mode; + private final Padding padding; + + /** + * Constructs a new instance. + * + * @param algorithm the algorithm name + * @param mode the mode name + * @param padding the padding name + */ + private Transformation(final String algorithm, final String mode, final Padding padding) { + this.algorithm = algorithm; + this.mode = mode; + this.padding = padding; + } + + /** + * Constructs a new instance. + * + * @param algorithm the algorithm name + * @param mode the mode name + * @param padding the padding name + * @throws NoSuchPaddingException Thrown when the padding is unsupported. + */ + private Transformation(final String algorithm, final String mode, final String padding) throws NoSuchPaddingException { + this(algorithm, mode, Padding.get(padding)); + } + + /** + * Gets the algorithm. + * + * @return the algorithm. + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * Gets the mode. + * + * @return the mode. + */ + public String getMode() { + return mode; + } + + /** + * Gets the padding. + * + * @return the padding. + */ + public Padding getPadding() { + return padding; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/commons/crypto/utils/Utils.java b/src/main/java/org/apache/commons/crypto/utils/Utils.java index fbc1a3f84..3cada7619 100644 --- a/src/main/java/org/apache/commons/crypto/utils/Utils.java +++ b/src/main/java/org/apache/commons/crypto/utils/Utils.java @@ -17,6 +17,7 @@ */ package org.apache.commons.crypto.utils; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -36,106 +37,57 @@ */ public final class Utils { - private static class DefaultPropertiesHolder { - static final Properties DEFAULT_PROPERTIES = createDefaultProperties(); - } - - /** - * The file name of configuration file. - * TODO is there any need for it to have the CONF_PREFIX? - */ - private static final String SYSTEM_PROPERTIES_FILE = Crypto.CONF_PREFIX - + "properties"; - - /** - * The private constructor of {@link Utils}. - */ - private Utils() { - } - - /** - * Loads system properties when configuration file of the name - * {@link #SYSTEM_PROPERTIES_FILE} is found. - * - * @return the default properties - */ - private static Properties createDefaultProperties() { - // default to system - final Properties defaultedProps = new Properties(System.getProperties()); - final URL url = Thread.currentThread().getContextClassLoader().getResource(SYSTEM_PROPERTIES_FILE); - if (url == null) { - // Fail early when the resource is not found which makes SpotBugs happy on Java 17. - return defaultedProps; - } - try { - final Properties fileProps = new Properties(); - try (InputStream is = url.openStream()) { - fileProps.load(is); - } - final Enumeration> names = fileProps.propertyNames(); - while (names.hasMoreElements()) { - final String name = (String) names.nextElement(); - // ensure System properties override ones in the file so one can override the file on the command line - if (System.getProperty(name) == null) { - defaultedProps.setProperty(name, fileProps.getProperty(name)); - } - } - } catch (final Exception ex) { - System.err.println("Could not load '" + SYSTEM_PROPERTIES_FILE + "' from classpath: " + ex.toString()); - } - return defaultedProps; - } + public static final int BYTE_MASK = 0xFF; // mask to keep a byte from a longer number + public static final int OPENSSL_VERSION_MAX_INDEX = 20; // max seen so far is 9, but leave some spare - /** - * Gets a properties instance that defaults to the System Properties - * plus any other properties found in the file - * {@link #SYSTEM_PROPERTIES_FILE} - * @return a Properties instance with defaults - */ - public static Properties getDefaultProperties() { - return new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES); - } + private static final class DefaultPropertiesHolder { + static final Properties DEFAULT_PROPERTIES = createDefaultProperties(); - /** - * Gets the properties merged with default properties. - * @param newProp User-defined properties - * @return User-defined properties with the default properties - */ - public static Properties getProperties(final Properties newProp) { - final Properties properties = new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES); - properties.putAll(newProp); - return properties; - } + /** + * Loads system properties when configuration file of the name + * {@link #SYSTEM_PROPERTIES_FILE} is found. + * + * @return the default properties + */ + private static Properties createDefaultProperties() { + // default to system + final Properties defaultedProps = new Properties(System.getProperties()); + final URL url = Thread.currentThread().getContextClassLoader().getResource(SYSTEM_PROPERTIES_FILE); + if (url == null) { + // Fail early when the resource is not found which makes SpotBugs happy on Java 17. + return defaultedProps; + } + try { + final Properties fileProps = new Properties(); + try (InputStream is = url.openStream()) { + fileProps.load(is); + } + final Enumeration> names = fileProps.propertyNames(); + while (names.hasMoreElements()) { + final String name = (String) names.nextElement(); + // ensure System properties override ones in the file so one can override the file on the command line + if (System.getProperty(name) == null) { + defaultedProps.setProperty(name, fileProps.getProperty(name)); + } + } + } catch (final Exception ex) { + System.err.println("Could not load '" + SYSTEM_PROPERTIES_FILE + "' from classpath: " + ex); + } + return defaultedProps; + } + } /** - * Helper method to create a CryptoCipher instance and throws only - * IOException. - * - * @param properties The {@code Properties} class represents a set of - * properties. - * @param transformation the name of the transformation, e.g., - * AES/CBC/PKCS5Padding. - * See the Java Cryptography Architecture Standard Algorithm Name Documentation - * for information about standard transformation names. - * @return the CryptoCipher instance. - * @throws IOException if an I/O error occurs. + * The file name of configuration file. */ - public static CryptoCipher getCipherInstance( - final String transformation, final Properties properties) - throws IOException { - try { - return CryptoCipherFactory.getCryptoCipher(transformation, properties); - } catch (final GeneralSecurityException e) { - throw new IOException(e); - } - } + private static final String SYSTEM_PROPERTIES_FILE = Crypto.CONF_PREFIX + "properties"; /** * Ensures the truth of an expression involving one or more parameters to * the calling method. * * @param expression a boolean expression. - * @throws IllegalArgumentException if expression is false. + * @throws IllegalArgumentException if expression is {@code false}. */ public static void checkArgument(final boolean expression) { if (!expression) { @@ -148,9 +100,8 @@ public static void checkArgument(final boolean expression) { * * @param expression a boolean expression. * @param errorMessage the exception message to use if the check fails; will - * be converted to a string using String - * .valueOf(Object)
. - * @throws IllegalArgumentException if expression is false. + * be converted to a string usingString.valueOf(Object)
. + * @throws IllegalArgumentException if expression is {@code false}. */ public static void checkArgument(final boolean expression, final Object errorMessage) { if (!expression) { @@ -160,12 +111,12 @@ public static void checkArgument(final boolean expression, final Object errorMes /** * Ensures that an object reference passed as a parameter to the calling - * method is not null. + * method is not {@code null}. * * @paramthe type of the object reference to be checked. * @param reference an object reference. * @return the non-null reference that was validated. - * @throws NullPointerException if reference is null. + * @throws NullPointerException if reference is {@code null}. * @deprecated Use {@link Objects#requireNonNull(Object)}. */ @Deprecated @@ -178,7 +129,7 @@ public static T checkNotNull(final T reference) { * instance, but not involving any parameters to the calling method. * * @param expression a boolean expression. - * @throws IllegalStateException if expression is false. + * @throws IllegalStateException if expression is {@code false}. */ public static void checkState(final boolean expression) { checkState(expression, null); @@ -189,8 +140,8 @@ public static void checkState(final boolean expression) { * instance, but not involving any parameters to the calling method. * * @param expression a boolean expression. - * @param message Error message for the exception when the expression is false. - * @throws IllegalStateException if expression is false. + * @param message Error message for the exception when the expression is {@code false}. + * @throws IllegalStateException if expression is {@code false}. */ public static void checkState(final boolean expression, final String message) { if (!expression) { @@ -198,28 +149,105 @@ public static void checkState(final boolean expression, final String message) { } } + /** + * Helper method to create a CryptoCipher instance and throws only + * IOException. + * + * @param properties The {@link Properties} class represents a set of + * properties. + * @param transformation the name of the transformation, e.g., + * AES/CBC/PKCS5Padding. + * See the Java Cryptography Architecture Standard Algorithm Name Documentation + * for information about standard transformation names. + * @return the CryptoCipher instance. + * @throws IOException if an I/O error occurs. + */ + public static CryptoCipher getCipherInstance(final String transformation, final Properties properties) throws IOException { + try { + return CryptoCipherFactory.getCryptoCipher(transformation, properties); + } catch (final GeneralSecurityException e) { + throw new IOException(e); + } + } + + /** + * Gets a properties instance that defaults to the System Properties + * plus any other properties found in the file + * {@link #SYSTEM_PROPERTIES_FILE} + * @return a Properties instance with defaults + */ + public static Properties getDefaultProperties() { + return new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES); + } + + /** + * Gets the properties merged with default properties. + * @param newProp User-defined properties + * @return User-defined properties with the default properties + */ + public static Properties getProperties(final Properties newProp) { + final Properties properties = new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES); + properties.putAll(newProp); + return properties; + } + + /* + * Override the default DLL name if jni.library.path is a valid directory + * If jni.library.name is defined, this overrides the default name. + * + * @param name - the default name, passed from native code + * @return the updated library path + * This method is designed for use from the DynamicLoader native code. + * Although it could all be implemented in native code, this hook method + * makes maintenance easier. + * The code is intended for use with macOS where SIP makes it hard to override + * the environment variables needed to override the DLL search path. It also + * works for Linux. + * On both macOS and Linux, different versions of the library are stored in different directories. + * In each case, there is a link from the canonical name (libcrypto.xx) to the versioned name (libcrypto-1.2.3.xx) + * However on Windows, all the DLL versions seem to be stored in the same directory. + * This means that Windows code needs to be given the versioned name (e.g. libcrypto-1_1-x64) + * This is done by defining jni.library.name. + * + * Do not change the method name or its signature! + */ + static String libraryPath(final String name) { + final String overridename = System.getProperty("jni.library.name", name); + final String override = System.getProperty("jni.library.path"); + if (override != null && new File(override).isDirectory()) { + return new File(override, overridename).getPath(); + } + return overridename; + } + /** * Splits class names sequence into substrings, Trim each substring into an - * entry,and returns an list of the entries. + * entry, and returns a list of the entries. * - * @param clazzNames a string consist of a list of the entries joined by a - * delimiter, may be null or empty in which case an empty list is returned. + * @param classNames a string consist of a list of the entries joined by a + * delimiter, may be {@code null} or empty in which case an empty list is returned. * @param separator a delimiter for the input string. * @return a list of class entries. */ - public static List splitClassNames(final String clazzNames, final String separator) { + public static List splitClassNames(final String classNames, final String separator) { final List res = new ArrayList<>(); - if (clazzNames == null || clazzNames.isEmpty()) { + if (classNames == null || classNames.isEmpty()) { return res; } - for (String clazzName : clazzNames.split(separator)) { - clazzName = clazzName.trim(); - if (!clazzName.isEmpty()) { - res.add(clazzName); + for (String className : classNames.split(separator)) { + className = className.trim(); + if (!className.isEmpty()) { + res.add(className); } } return res; } + /** + * The private constructor of {@link Utils}. + */ + private Utils() { + } + } diff --git a/src/main/native/org/apache/commons/crypto/DynamicLoader.c b/src/main/native/org/apache/commons/crypto/DynamicLoader.c new file mode 100644 index 000000000..1f2656276 --- /dev/null +++ b/src/main/native/org/apache/commons/crypto/DynamicLoader.c @@ -0,0 +1,84 @@ +/* + * 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. + */ + +/* +Shared code to load and unload the library. +*/ + +#include "org_apache_commons_crypto.h" + +static HMODULE openssl; // the cached pointer +HMODULE open_library(JNIEnv *env) + +{ + if (!openssl) { + const char *libraryPath = COMMONS_CRYPTO_OPENSSL_LIBRARY; + jclass clazz = (*env)->FindClass(env, "org/apache/commons/crypto/utils/Utils"); + if (clazz) { + jmethodID libraryPathFunc = (*env)->GetStaticMethodID(env, clazz, "libraryPath", "(Ljava/lang/String;)Ljava/lang/String;"); + if (libraryPathFunc) { + jstring defaultLibrary = (*env)->NewStringUTF(env, COMMONS_CRYPTO_OPENSSL_LIBRARY); + jstring result = (jstring) (*env)->CallStaticObjectMethod(env, clazz, libraryPathFunc, defaultLibrary); + if (result) { + libraryPath = (*env)->GetStringUTFChars(env, result, NULL); + } + } + } +#ifdef UNIX + openssl = dlopen(libraryPath, RTLD_LAZY | RTLD_GLOBAL); +#endif + +#ifdef WINDOWS + openssl = LoadLibraryA(libraryPath); // use the non-generic method; assume libraryPath is suitable +#endif + + // Did we succeed? + if (!openssl) + { + char msg[1000]; +#ifdef UNIX + snprintf(msg, sizeof(msg), "Cannot load '%s' (%s)!", libraryPath, dlerror()); // returns char* +#endif +#ifdef WINDOWS + // Crude method to convert most likely errors to string + DWORD lastError = GetLastError(); + char *lastmsg; + if (lastError == 126) + { + lastmsg = "specified module cannot be found"; + } + else if (lastError == 193) + { + lastmsg = "module is not a valid Win32 application"; + } + else + { + lastmsg = "unknown error - check online Windows documentation"; + } + snprintf(msg, sizeof(msg), "Cannot load '%s' (%d: %s)!", libraryPath, lastError, lastmsg); +#endif + THROW(env, "java/lang/UnsatisfiedLinkError", msg); + return 0; + } + } + return openssl; +} + +void close_library() { + openssl = NULL; +} diff --git a/src/main/native/org/apache/commons/crypto/OpenSslInfoNative.c b/src/main/native/org/apache/commons/crypto/OpenSslInfoNative.c index 4b9f8e9de..38b68b6dd 100644 --- a/src/main/native/org/apache/commons/crypto/OpenSslInfoNative.c +++ b/src/main/native/org/apache/commons/crypto/OpenSslInfoNative.c @@ -16,6 +16,8 @@ * limitations under the License. */ +// identify caller to the include file (to avoid unnecessary define of _GNU_SOURCE) +#define ORG_APACHE_COMMONS_OPENSSLINFONATIVE_C #include "org_apache_commons_crypto.h" #include @@ -50,51 +52,31 @@ static __dlsym_OpenSSL_version_num dlsym_OpenSSL_version_num; static __dlsym_OpenSSL_version dlsym_OpenSSL_version; #endif -#ifdef UNIX -static void get_methods(JNIEnv *env, void *openssl) -#endif -#ifdef WINDOWS +static char dynamicLibraryPath[80]; // where was the crypto library found? + static void get_methods(JNIEnv *env, HMODULE openssl) -#endif { - LOAD_OPENSSL_VERSION_FUNCTION(dlsym_OpenSSL_version_num, env, openssl); + LOAD_DYNAMIC_SYMBOL_FALLBACK(__dlsym_OpenSSL_version_num, dlsym_OpenSSL_version_num, env, openssl, "OpenSSL_version_num", "SSLeay"); // SSLeay fallback needed by LibreSSL 2.x + LOAD_DYNAMIC_SYMBOL_FALLBACK(__dlsym_OpenSSL_version, dlsym_OpenSSL_version, env, openssl, "OpenSSL_version", "SSLeay_version"); // SSLeay fallback needed by LibreSSL 2.x #ifdef UNIX - if (dlsym_OpenSSL_version_num() > VERSION_1_1_X) { - LOAD_DYNAMIC_SYMBOL(dlsym_OpenSSL_version, env, openssl, "OpenSSL_version"); - } else { - LOAD_DYNAMIC_SYMBOL(dlsym_OpenSSL_version, env, openssl, "SSLeay_version"); - } + Dl_info info; + (void) dladdr(dlsym_OpenSSL_version_num, &info); // ignore the return code + strncpy(dynamicLibraryPath, info.dli_fname, sizeof(dynamicLibraryPath) - 1); // allow for null #endif #ifdef WINDOWS - if (dlsym_OpenSSL_version_num() > VERSION_1_1_X) { - LOAD_DYNAMIC_SYMBOL(__dlsym_OpenSSL_version, dlsym_OpenSSL_version, env, openssl, "OpenSSL_version"); - } else { - LOAD_DYNAMIC_SYMBOL(__dlsym_OpenSSL_version, dlsym_OpenSSL_version, env, openssl, "SSLeay_version"); - } -#endif + LPWSTR lpFilename; + WCHAR buffer[80]; + lpFilename = buffer; + (void) GetModuleFileName(openssl, lpFilename, sizeof(buffer)); // ignore return code + wcstombs(dynamicLibraryPath, buffer, sizeof(dynamicLibraryPath)); + #endif } static int load_library(JNIEnv *env) { - char msg[100]; -#ifdef UNIX - void *openssl = dlopen(COMMONS_CRYPTO_OPENSSL_LIBRARY, RTLD_LAZY | RTLD_GLOBAL); -#endif - -#ifdef WINDOWS - HMODULE openssl = LoadLibrary(TEXT(COMMONS_CRYPTO_OPENSSL_LIBRARY)); -#endif + HMODULE openssl = open_library(env); // calls THROW and returns 0 on error if (!openssl) { -#ifdef UNIX - snprintf(msg, sizeof(msg), "Cannot load %s (%s)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, \ - dlerror()); -#endif -#ifdef WINDOWS - snprintf(msg, sizeof(msg), "Cannot load %s (%d)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, \ - GetLastError()); -#endif - THROW(env, "java/lang/UnsatisfiedLinkError", msg); return 0; } get_methods(env, openssl); @@ -138,3 +120,20 @@ JNIEXPORT jstring JNICALL Java_org_apache_commons_crypto_OpenSslInfoNative_OpenS jstring answer = (*env)->NewStringUTF(env,dlsym_OpenSSL_version(type)); return answer; } + +JNIEXPORT jstring JNICALL Java_org_apache_commons_crypto_OpenSslInfoNative_DLLName + (JNIEnv *env, jclass clazz) +{ + jstring answer = (*env)->NewStringUTF(env, COMMONS_CRYPTO_OPENSSL_LIBRARY); + return answer; +} + +JNIEXPORT jstring JNICALL Java_org_apache_commons_crypto_OpenSslInfoNative_DLLPath + (JNIEnv *env, jclass clazz) +{ + if (!load_library(env)) { + return NULL; + } + jstring answer = (*env)->NewStringUTF(env, dynamicLibraryPath); + return answer; +} diff --git a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c index 3d12b1cea..9038607ad 100644 --- a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c +++ b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c @@ -30,6 +30,7 @@ #include "org_apache_commons_crypto_cipher_OpenSslNative.h" #ifdef UNIX +static unsigned long (*dlsym_OpenSSL_version_num) (void); static EVP_CIPHER_CTX * (*dlsym_EVP_CIPHER_CTX_new)(void); static void (*dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *); static int (*dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, int); @@ -57,6 +58,8 @@ static EVP_CIPHER * (*dlsym_EVP_sm4_cbc)(void); #endif #ifdef WINDOWS +typedef unsigned long (__cdecl *__dlsym_OpenSSL_version_num) (void); +static __dlsym_OpenSSL_version_num dlsym_OpenSSL_version_num; typedef EVP_CIPHER_CTX * (__cdecl *__dlsym_EVP_CIPHER_CTX_new)(void); typedef void (__cdecl *__dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *); typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, int); @@ -107,29 +110,8 @@ static __dlsym_EVP_sm4_ctr dlsym_EVP_sm4_ctr; static __dlsym_EVP_sm4_cbc dlsym_EVP_sm4_cbc; #endif -#ifdef UNIX -static void loadAes(JNIEnv *env, void *openssl) -#endif - -#ifdef WINDOWS static void loadAes(JNIEnv *env, HMODULE openssl) -#endif { -#ifdef UNIX - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_ctr, env, openssl, "EVP_aes_256_ctr"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_ctr, env, openssl, "EVP_aes_192_ctr"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_ctr, env, openssl, "EVP_aes_128_ctr"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_cbc, env, openssl, "EVP_aes_256_cbc"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_cbc, env, openssl, "EVP_aes_192_cbc"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_cbc, env, openssl, "EVP_aes_128_cbc"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_gcm, env, openssl, "EVP_aes_256_gcm"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_gcm, env, openssl, "EVP_aes_192_gcm"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_gcm, env, openssl, "EVP_aes_128_gcm"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_sm4_ctr, env, openssl, "EVP_sm4_ctr"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_sm4_cbc, env, openssl, "EVP_sm4_cbc"); -#endif - -#ifdef WINDOWS LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_256_ctr, dlsym_EVP_aes_256_ctr, \ env, openssl, "EVP_aes_256_ctr"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_192_ctr, dlsym_EVP_aes_192_ctr, \ @@ -152,50 +134,22 @@ static void loadAes(JNIEnv *env, HMODULE openssl) env, openssl, "EVP_sm4_ctr"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_sm4_cbc, dlsym_EVP_sm4_cbc, \ env, openssl, "EVP_sm4_cbc"); -#endif } JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initIDs (JNIEnv *env, jclass clazz) { - char msg[1000]; -#ifdef UNIX - void *openssl = dlopen(COMMONS_CRYPTO_OPENSSL_LIBRARY, RTLD_LAZY | RTLD_GLOBAL); -#endif - -#ifdef WINDOWS - HMODULE openssl = LoadLibrary(TEXT(COMMONS_CRYPTO_OPENSSL_LIBRARY)); -#endif + HMODULE openssl = open_library(env); // calls THROW and returns 0 on error if (!openssl) { -#ifdef UNIX - snprintf(msg, sizeof(msg), "Cannot load %s (%s)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, \ - dlerror()); -#endif -#ifdef WINDOWS - snprintf(msg, sizeof(msg), "Cannot load %s (%d)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, \ - GetLastError()); -#endif - THROW(env, "java/lang/UnsatisfiedLinkError", msg); return; } #ifdef UNIX dlerror(); // Clear any existing error - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_new, env, openssl, "EVP_CIPHER_CTX_new"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_free, env, openssl, "EVP_CIPHER_CTX_free"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_set_padding, env, openssl, "EVP_CIPHER_CTX_set_padding"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_ctrl, env, openssl, "EVP_CIPHER_CTX_ctrl"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_block_size, env, openssl, "EVP_CIPHER_CTX_block_size"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_cipher, env, openssl, "EVP_CIPHER_CTX_cipher"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_flags, env, openssl, "EVP_CIPHER_flags"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_test_flags, env, openssl, "EVP_CIPHER_CTX_test_flags"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherInit_ex, env, openssl, "EVP_CipherInit_ex"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherUpdate, env, openssl, "EVP_CipherUpdate"); - LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherFinal_ex, env, openssl, "EVP_CipherFinal_ex"); #endif - -#ifdef WINDOWS + LOAD_DYNAMIC_SYMBOL_FALLBACK(__dlsym_OpenSSL_version_num, dlsym_OpenSSL_version_num, \ + env, openssl, "OpenSSL_version_num", "SSLeay"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_new, dlsym_EVP_CIPHER_CTX_new, \ env, openssl, "EVP_CIPHER_CTX_new"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_free, dlsym_EVP_CIPHER_CTX_free, \ @@ -204,12 +158,21 @@ JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initI env, openssl, "EVP_CIPHER_CTX_set_padding"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_ctrl, dlsym_EVP_CIPHER_CTX_ctrl, \ env, openssl, "EVP_CIPHER_CTX_ctrl"); + char *block_size_name; + char *flags_name; + if (dlsym_OpenSSL_version_num() < VERSION_3_0_X) { + block_size_name = "EVP_CIPHER_CTX_block_size"; + flags_name = "EVP_CIPHER_flags"; + } else { + block_size_name = "EVP_CIPHER_CTX_get_block_size"; + flags_name = "EVP_CIPHER_get_flags"; + } LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_block_size, dlsym_EVP_CIPHER_CTX_block_size, \ - env, openssl, "EVP_CIPHER_CTX_block_size"); + env, openssl, block_size_name); + LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_flags, dlsym_EVP_CIPHER_flags, \ + env, openssl, flags_name); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_cipher, dlsym_EVP_CIPHER_CTX_cipher, \ env, openssl, "EVP_CIPHER_CTX_cipher"); - LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_flags, dlsym_EVP_CIPHER_flags, \ - env, openssl, "EVP_CIPHER_flags"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CIPHER_CTX_test_flags, dlsym_EVP_CIPHER_CTX_test_flags, \ env, openssl, "EVP_CIPHER_CTX_test_flags"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CipherInit_ex, dlsym_EVP_CipherInit_ex, \ @@ -218,14 +181,13 @@ JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initI env, openssl, "EVP_CipherUpdate"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_CipherFinal_ex, dlsym_EVP_CipherFinal_ex, \ env, openssl, "EVP_CipherFinal_ex"); -#endif loadAes(env, openssl); jthrowable jthr = (*env)->ExceptionOccurred(env); if (jthr) { (*env)->DeleteLocalRef(env, jthr); THROW(env, "java/lang/UnsatisfiedLinkError", \ - "Cannot find AES-CTR support, is your version of Openssl new enough?"); + "Cannot find AES-CTR support, is your version of OpenSSL new enough?"); return; } } diff --git a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h index 25b9a6f5a..d302e49b4 100644 --- a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h +++ b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h @@ -62,14 +62,25 @@ if ((*env)->ExceptionCheck(env)) return (ret); \ } +void close_library(); + /** * Unix definitions */ #ifdef UNIX -#include +#ifdef ORG_APACHE_COMMONS_OPENSSLINFONATIVE_C +#ifndef MAC_OS +// needed for access to dladdr +#define _GNU_SOURCE +#endif +#endif #include #include +typedef void * HMODULE; // to agree with Windows type name + +HMODULE open_library(JNIEnv *env); + /** * A helper function to dlsym a 'symbol' from a given library-handle. * @@ -81,7 +92,7 @@ */ static __attribute__ ((unused)) void *do_dlsym(JNIEnv *env, void *handle, const char *symbol) { - if (!env || !handle || !symbol) { + if (!handle || !symbol) { THROW(env, "java/lang/InternalError", NULL); return NULL; } @@ -94,30 +105,49 @@ void *do_dlsym(JNIEnv *env, void *handle, const char *symbol) { return func_ptr; } +/** + * A helper function to dlsym a 'symbol' from a given library-handle. + * Allows for fallback symbol name. + * + * @param env jni handle to report contingencies. + * @param handle handle to the dlopen'ed library. + * @param symbol symbol to load. + * @param fallback alternate symbol to load + * @return returns the address where the symbol is loaded in memory, + * NULL
on error. + */ static __attribute__ ((unused)) -void *do_version_dlsym(JNIEnv *env, void *handle) { - if (!env || !handle) { +void *do_dlsym_fallback(JNIEnv *env, void *handle, const char *symbol, const char *fallback) { + if (!handle) { THROW(env, "java/lang/InternalError", NULL); return NULL; } - void *func_ptr = dlsym(handle, "OpenSSL_version_num"); + char *error = NULL; + void *func_ptr = dlsym(handle, symbol); if (func_ptr == NULL) { - func_ptr = dlsym(handle, "SSLeay"); + func_ptr = dlsym(handle, fallback); + } + if ((error = dlerror()) != NULL) { + THROW(env, "java/lang/UnsatisfiedLinkError", symbol); + return NULL; } return func_ptr; } /* A helper macro to dlsym the requisite dynamic symbol and bail-out on error. */ -#define LOAD_DYNAMIC_SYMBOL(func_ptr, env, handle, symbol) \ +// func_type is currently ignored, so can use same macro invocation as for Windows +#define LOAD_DYNAMIC_SYMBOL(_func_type, func_ptr, env, handle, symbol) \ if ((func_ptr = do_dlsym(env, handle, symbol)) == NULL) { \ return; \ } -/* A macro to dlsym the appropriate OpenSSL version number function. */ -#define LOAD_OPENSSL_VERSION_FUNCTION(func_ptr, env, handle) \ - if ((func_ptr = do_version_dlsym(env, handle)) == NULL) { \ - THROW(env, "java/lang/Error", NULL); \ +/* A macro to dlsym the requisite dynamic symbol (with fallback) and bail-out on error. */ +// func_type is currently ignored, so can use same macro invocation as for Windows +#define LOAD_DYNAMIC_SYMBOL_FALLBACK(_func_type, func_ptr, env, handle, symbol, fallback) \ + if ((func_ptr = do_dlsym_fallback(env, handle, symbol, fallback)) == NULL) { \ + return; \ } + #endif // Unix part end @@ -156,6 +186,8 @@ void *do_version_dlsym(JNIEnv *env, void *handle) { #define snprintf(a, b ,c, d) _snprintf_s((a), (b), _TRUNCATE, (c), (d)) #endif +HMODULE open_library(JNIEnv *env); + /* A helper macro to dlsym the requisite dynamic symbol and bail-out on error. */ #define LOAD_DYNAMIC_SYMBOL(func_type, func_ptr, env, handle, symbol) \ if ((func_ptr = (func_type) do_dlsym(env, handle, symbol)) == NULL) { \ @@ -175,7 +207,7 @@ static FARPROC WINAPI do_dlsym(JNIEnv *env, HMODULE handle, LPCSTR symbol) { DWORD dwErrorCode = ERROR_SUCCESS; FARPROC func_ptr = NULL; - if (!env || !handle || !symbol) { + if (!handle || !symbol) { THROW(env, "java/lang/InternalError", NULL); return NULL; } @@ -188,24 +220,43 @@ static FARPROC WINAPI do_dlsym(JNIEnv *env, HMODULE handle, LPCSTR symbol) { return func_ptr; } -static FARPROC WINAPI do_version_dlsym(JNIEnv *env, HMODULE handle) { +/* A helper macro to dlsym the requisite dynamic symbol and bail-out on error. */ +#define LOAD_DYNAMIC_SYMBOL_FALLBACK(func_type, func_ptr, env, handle, symbol, fallback) \ + if ((func_ptr = (func_type) do_dlsym_fallback(env, handle, symbol, fallback)) == NULL) { \ + return; \ + } + +/** + * A helper function to dynamic load a 'symbol' from a given library-handle. + * + * @param env jni handle to report contingencies. + * @param handle handle to the dynamic library. + * @param symbol symbol to load. + * @param fallback alternate symbol to load. + * @return returns the address where the symbol is loaded in memory, + *NULL
on error. + */ +static FARPROC WINAPI do_dlsym_fallback(JNIEnv *env, HMODULE handle, LPCSTR symbol, LPCSTR fallback) { + DWORD dwErrorCode = ERROR_SUCCESS; FARPROC func_ptr = NULL; - if (!env || !handle) { + + if (!handle || !symbol) { THROW(env, "java/lang/InternalError", NULL); return NULL; } - func_ptr = GetProcAddress(handle, "OpenSSL_version_num"); - if (func_ptr == NULL) { - func_ptr = GetProcAddress(handle, "SSLeay"); + + func_ptr = GetProcAddress(handle, symbol); + if (func_ptr == NULL) + { + func_ptr = GetProcAddress(handle, fallback); + if (func_ptr == NULL) + { + THROW(env, "java/lang/UnsatisfiedLinkError", symbol); + } } return func_ptr; } -/* A macro to dlsym the appropriate OpenSSL version number function. */ -#define LOAD_OPENSSL_VERSION_FUNCTION(func_ptr, env, handle) \ - if ((func_ptr = (__dlsym_OpenSSL_version_num) do_version_dlsym(env, handle)) == NULL) { \ - THROW(env, "java/lang/Error", NULL); \ - } #endif // Windows part end @@ -213,14 +264,14 @@ static FARPROC WINAPI do_version_dlsym(JNIEnv *env, HMODULE handle) { #define LOCK_CLASS(env, clazz, classname) \ if ((*env)->MonitorEnter(env, clazz) != 0) { \ char exception_msg[128]; \ - snprintf(exception_msg, 128, "Failed to lock %s", classname); \ + snprintf(exception_msg, sizeof(exception_msg), "Failed to lock %s", classname); \ THROW(env, "java/lang/InternalError", exception_msg); \ } #define UNLOCK_CLASS(env, clazz, classname) \ if ((*env)->MonitorExit(env, clazz) != 0) { \ char exception_msg[128]; \ - snprintf(exception_msg, 128, "Failed to unlock %s", classname); \ + snprintf(exception_msg, sizeof(exception_msg), "Failed to unlock %s", classname); \ THROW(env, "java/lang/InternalError", exception_msg); \ } @@ -265,8 +316,8 @@ static FARPROC WINAPI do_version_dlsym(JNIEnv *env, HMODULE handle) { #define NOPADDING 0 #define PKCS5PADDING 1 -#define VERSION_1_0_X 0x10000000 #define VERSION_1_1_X 0x10100000 +#define VERSION_3_0_X 0x30000000 #endif diff --git a/src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c b/src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c index 2516e2cfc..efaca8f99 100644 --- a/src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c +++ b/src/main/native/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.c @@ -51,14 +51,6 @@ static int (*dlsym_ENGINE_free) (ENGINE *); static int (*dlsym_RAND_bytes) (unsigned char *, int); static unsigned long (*dlsym_ERR_get_error) (void); static unsigned long (*dlsym_OpenSSL_version_num)(void); -static int (*dlsym_CRYPTO_num_locks) (void); -static void (*dlsym_CRYPTO_set_id_callback) (unsigned long (*)()); -static void (*dlsym_CRYPTO_set_locking_callback) (void (*)()); -static void (*dlsym_ENGINE_load_rdrand) (void); -static void (*dlsym_ENGINE_cleanup) (void); -static void pthreads_locking_callback(int mode, int type, char *file, int line); -static unsigned long pthreads_thread_id(void); -static pthread_mutex_t *lock_cs; #endif #ifdef WINDOWS @@ -72,10 +64,6 @@ typedef int (__cdecl *__dlsym_ENGINE_free) (ENGINE *); typedef int (__cdecl *__dlsym_RAND_bytes) (unsigned char *, int); typedef unsigned long (__cdecl *__dlsym_ERR_get_error) (void); typedef unsigned long (__cdecl *__dlsym_OpenSSL_version_num) (void); -typedef int (__cdecl *__dlsym_CRYPTO_num_locks) (void); -typedef void (__cdecl *__dlsym_CRYPTO_set_locking_callback) (void (*)()); -typedef void (__cdecl *__dlsym_ENGINE_load_rdrand) (void); -typedef void (__cdecl *__dlsym_ENGINE_cleanup) (void); static __dlsym_CRYPTO_malloc dlsym_CRYPTO_malloc; static __dlsym_CRYPTO_free dlsym_CRYPTO_free; static __dlsym_ENGINE_by_id dlsym_ENGINE_by_id; @@ -86,11 +74,6 @@ static __dlsym_ENGINE_free dlsym_ENGINE_free; static __dlsym_RAND_bytes dlsym_RAND_bytes; static __dlsym_ERR_get_error dlsym_ERR_get_error; static __dlsym_OpenSSL_version_num dlsym_OpenSSL_version_num; -static __dlsym_CRYPTO_num_locks dlsym_CRYPTO_num_locks; -static __dlsym_CRYPTO_set_locking_callback dlsym_CRYPTO_set_locking_callback; -static __dlsym_ENGINE_load_rdrand dlsym_ENGINE_load_rdrand; -static __dlsym_ENGINE_cleanup dlsym_ENGINE_cleanup; -static void windows_locking_callback(int mode, int type, char *file, int line); static HANDLE *lock_cs; #endif @@ -100,47 +83,21 @@ static int openssl_rand_bytes(unsigned char *buf, int num); JNIEXPORT void JNICALL Java_org_apache_commons_crypto_random_OpenSslCryptoRandomNative_initSR (JNIEnv *env, jclass clazz) { - char msg[1000]; -#ifdef UNIX - void *openssl = dlopen(COMMONS_CRYPTO_OPENSSL_LIBRARY, RTLD_LAZY | RTLD_GLOBAL); -#endif - -#ifdef WINDOWS - HMODULE openssl = LoadLibrary(TEXT(COMMONS_CRYPTO_OPENSSL_LIBRARY)); -#endif + HMODULE openssl = open_library(env); // calls THROW and returns 0 on error if (!openssl) { -#ifdef UNIX - snprintf(msg, sizeof(msg), "Cannot load %s (%s)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, dlerror()); -#endif -#ifdef WINDOWS - snprintf(msg, sizeof(msg), "Cannot load %s (%d)!", COMMONS_CRYPTO_OPENSSL_LIBRARY, GetLastError()); -#endif - THROW(env, "java/lang/UnsatisfiedLinkError", msg); return; } - LOAD_OPENSSL_VERSION_FUNCTION(dlsym_OpenSSL_version_num, env, openssl); -#ifdef UNIX - dlerror(); // Clear any existing error - LOAD_DYNAMIC_SYMBOL(dlsym_CRYPTO_malloc, env, openssl, "CRYPTO_malloc"); - LOAD_DYNAMIC_SYMBOL(dlsym_CRYPTO_free, env, openssl, "CRYPTO_free"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_by_id, env, openssl, "ENGINE_by_id"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_init, env, openssl, "ENGINE_init"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_set_default, env, openssl, "ENGINE_set_default"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_finish, env, openssl, "ENGINE_finish"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_free, env, openssl, "ENGINE_free"); - LOAD_DYNAMIC_SYMBOL(dlsym_RAND_bytes, env, openssl, "RAND_bytes"); - LOAD_DYNAMIC_SYMBOL(dlsym_ERR_get_error, env, openssl, "ERR_get_error"); + + LOAD_DYNAMIC_SYMBOL_FALLBACK(__dlsym_OpenSSL_version_num, dlsym_OpenSSL_version_num, env, openssl, "OpenSSL_version_num", "SSLeay"); + // Reject attempt to use obsolete version if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - LOAD_DYNAMIC_SYMBOL(dlsym_CRYPTO_num_locks, env, openssl, "CRYPTO_num_locks"); - LOAD_DYNAMIC_SYMBOL(dlsym_CRYPTO_set_id_callback, env, openssl, "CRYPTO_set_id_callback"); - LOAD_DYNAMIC_SYMBOL(dlsym_CRYPTO_set_locking_callback, env, openssl, "CRYPTO_set_locking_callback"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_load_rdrand, env, openssl, "ENGINE_load_rdrand"); - LOAD_DYNAMIC_SYMBOL(dlsym_ENGINE_cleanup, env, openssl, "ENGINE_cleanup"); + THROW(env, "java/lang/UnsatisfiedLinkError", "Versions below 1.1 are not supported"); + return; } +#ifdef UNIX + dlerror(); // Clear any existing error #endif - -#ifdef WINDOWS LOAD_DYNAMIC_SYMBOL(__dlsym_CRYPTO_malloc, dlsym_CRYPTO_malloc, env, openssl, "CRYPTO_malloc"); LOAD_DYNAMIC_SYMBOL(__dlsym_CRYPTO_free, dlsym_CRYPTO_free, env, openssl, "CRYPTO_free"); LOAD_DYNAMIC_SYMBOL(__dlsym_ENGINE_by_id, dlsym_ENGINE_by_id, env, openssl, "ENGINE_by_id"); @@ -150,13 +107,6 @@ JNIEXPORT void JNICALL Java_org_apache_commons_crypto_random_OpenSslCryptoRandom LOAD_DYNAMIC_SYMBOL(__dlsym_ENGINE_free, dlsym_ENGINE_free, env, openssl, "ENGINE_free"); LOAD_DYNAMIC_SYMBOL(__dlsym_RAND_bytes, dlsym_RAND_bytes, env, openssl, "RAND_bytes"); LOAD_DYNAMIC_SYMBOL(__dlsym_ERR_get_error, dlsym_ERR_get_error, env, openssl, "ERR_get_error"); - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - LOAD_DYNAMIC_SYMBOL(__dlsym_CRYPTO_num_locks, dlsym_CRYPTO_num_locks, env, openssl, "CRYPTO_num_locks"); - LOAD_DYNAMIC_SYMBOL(__dlsym_CRYPTO_set_locking_callback, dlsym_CRYPTO_set_locking_callback, env, openssl, "CRYPTO_set_locking_callback"); - LOAD_DYNAMIC_SYMBOL(__dlsym_ENGINE_load_rdrand, dlsym_ENGINE_load_rdrand, env, openssl, "ENGINE_load_rdrand"); - LOAD_DYNAMIC_SYMBOL(__dlsym_ENGINE_cleanup, dlsym_ENGINE_cleanup, env, openssl, "ENGINE_cleanup"); - } -#endif openssl_rand_init(); } @@ -195,112 +145,12 @@ JNIEXPORT jboolean JNICALL Java_org_apache_commons_crypto_random_OpenSslCryptoRa return JNI_TRUE; } -/** - * To ensure thread safety for random number generators, we need to call - * CRYPTO_set_locking_callback. - * http://wiki.openssl.org/index.php/Random_Numbers - * Example: crypto/threads/mttest.c - */ -#ifdef UNIX -static void pthreads_locking_callback(int mode, int type, char *file, int line) -{ - UNUSED(file), UNUSED(line); - - if (mode & CRYPTO_LOCK) { - pthread_mutex_lock(&(lock_cs[type])); - } else { - pthread_mutex_unlock(&(lock_cs[type])); - } -} - -static unsigned long pthreads_thread_id(void) -{ - return (unsigned long)syscall(SYS_gettid); -} - -static void locks_setup(void) -{ - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - int i; - lock_cs = dlsym_CRYPTO_malloc(dlsym_CRYPTO_num_locks() * sizeof(pthread_mutex_t), __FILE__, __LINE__); - - for (i = 0; i < dlsym_CRYPTO_num_locks(); i++) { - pthread_mutex_init(&(lock_cs[i]), NULL); - } - - dlsym_CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id); - dlsym_CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback); - } -} - -static void locks_cleanup(void) -{ - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - int i; - dlsym_CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < dlsym_CRYPTO_num_locks(); i++) { - pthread_mutex_destroy(&(lock_cs[i])); - } - - dlsym_CRYPTO_free(lock_cs); - } -} -#endif /* UNIX */ - -#ifdef WINDOWS -static void locks_setup(void) -{ - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - int i; - lock_cs = dlsym_CRYPTO_malloc(dlsym_CRYPTO_num_locks() * sizeof(HANDLE), \ - __FILE__, __LINE__); - - for (i = 0; i < dlsym_CRYPTO_num_locks(); i++) { - lock_cs[i] = CreateMutex(NULL, FALSE, NULL); - } - dlsym_CRYPTO_set_locking_callback((void (*)(int, int, char *, int)) \ - windows_locking_callback); - /* id callback defined */ - } -} - -static void locks_cleanup(void) -{ - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - int i; - dlsym_CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < dlsym_CRYPTO_num_locks(); i++) { - CloseHandle(lock_cs[i]); - } - dlsym_CRYPTO_free(lock_cs); - } -} - -static void windows_locking_callback(int mode, int type, char *file, int line) -{ - UNUSED(file), UNUSED(line); - - if (mode & CRYPTO_LOCK) { - WaitForSingleObject(lock_cs[type], INFINITE); - } else { - ReleaseMutex(lock_cs[type]); - } -} -#endif /* WINDOWS */ - /** * If using an Intel chipset with RDRAND, the high-performance hardware * random number generator will be used. */ static ENGINE * openssl_rand_init(void) { - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - locks_setup(); - dlsym_ENGINE_load_rdrand(); - } - ENGINE *eng = dlsym_ENGINE_by_id("rdrand"); int ret = -1; @@ -334,13 +184,6 @@ static void openssl_rand_clean(ENGINE *eng, int clean_locks) if (NULL != eng) { dlsym_ENGINE_finish(eng); dlsym_ENGINE_free(eng); - - if (dlsym_OpenSSL_version_num() < VERSION_1_1_X) { - dlsym_ENGINE_cleanup(); - if (clean_locks) { - locks_cleanup(); - } - } } } diff --git a/src/main/native/org/apache/commons/crypto/random/org_apache_commons_crypto_random.h b/src/main/native/org/apache/commons/crypto/random/org_apache_commons_crypto_random.h index 7040f0a06..41e5cf371 100644 --- a/src/main/native/org/apache/commons/crypto/random/org_apache_commons_crypto_random.h +++ b/src/main/native/org/apache/commons/crypto/random/org_apache_commons_crypto_random.h @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #ifndef ORG_APACHE_COMMONS_CRYPTO_RANDOM_H #define ORG_APACHE_COMMONS_CRYPTO_RANDOM_H diff --git a/src/site/site.xml b/src/site/site.xml index 1a60f2cc6..a669ab074 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -24,12 +24,13 @@- The KEYS + The KEYS file contains the public PGP keys used by Apache Commons developers to sign releases.
-+
- commons-crypto-1.1.0-bin.tar.gz -sha512 -pgp +commons-crypto-1.2.0-bin.tar.gz +sha512 +pgp - commons-crypto-1.1.0-bin.zip -sha512 -pgp +commons-crypto-1.2.0-bin.zip +sha512 +pgp diff --git a/src/site/xdoc/faq.xml b/src/site/xdoc/faq.xml index 6f39ca94d..a8ac03fbf 100644 --- a/src/site/xdoc/faq.xml +++ b/src/site/xdoc/faq.xml @@ -33,7 +33,7 @@ limitations under the License.
- commons-crypto-1.1.0-src.tar.gz -sha512 -pgp +commons-crypto-1.2.0-src.tar.gz +sha512 +pgp - commons-crypto-1.1.0-src.zip -sha512 -pgp +commons-crypto-1.2.0-src.zip +sha512 +pgp When calling
- OPENSSL
- OpenSSL based JNI implementation shipped with Commons Crypto.
- JAVA
- The SecureRandom implementation from the JVM.
-- OS
- The OS random device implementation. May not be available on some operation systems.
+- OS
- The OS random device implementation. May not be available on some operating systems.
CryptoRandomFactory.getCryptoRandom()
, Commons Crypto tries to use the OpenSSL diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index e47144384..9fe1110af 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -37,7 +37,7 @@ limitations under the License.- Cipher API for low level cryptographic operations.
- Secure true random number generator.
- Java stream API for high level stream - encyrption/decryption. + encryption/decryption.
- High performance AES encryption/decryption optimized with Intel AES-NI.
@@ -75,7 +75,11 @@ limitations under the License.
- - Crypto 1.1.0 (mirrors) + Crypto 1.2.0 (mirrors) + requires Java 1.8 and OpenSSL 1.1.x (should also work with 1.0.x) +
+- + Crypto 1.1.0 (archives) requires Java 1.8, built and tested with:
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml index b71cbbbf0..2b2811f7a 100644 --- a/src/site/xdoc/issue-tracking.xml +++ b/src/site/xdoc/issue-tracking.xml @@ -85,7 +85,7 @@ limitations under the License.
- For more information on subversion and creating patches see the + For more information on creating patches see the Apache Contributors Guide.
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml index e7a88d575..1fbae86e7 100644 --- a/src/site/xdoc/mail-lists.xml +++ b/src/site/xdoc/mail-lists.xml @@ -105,7 +105,9 @@ limitations under the License.Subscribe Unsubscribe Post -mail-archives.apache.org +mail-archives.apache.org
+ lists.apache.org +markmail.org
www.mail-archive.com
news.gmane.org @@ -123,7 +125,9 @@ limitations under the License.Subscribe Unsubscribe Post -mail-archives.apache.org +mail-archives.apache.org
+ lists.apache.org +markmail.org
www.mail-archive.com
news.gmane.org @@ -141,7 +145,9 @@ limitations under the License.Subscribe Unsubscribe read only -mail-archives.apache.org +mail-archives.apache.org
+ lists.apache.org +markmail.org @@ -152,13 +158,15 @@ limitations under the License.
www.mail-archive.comCommons Commits List
- Only for e-mails automatically generated by the source control sytem. + Only for e-mails automatically generated by the source control system.
Subscribe Unsubscribe read only -mail-archives.apache.org +mail-archives.apache.org
+ lists.apache.org +markmail.org @@ -191,7 +199,9 @@ limitations under the License.
www.mail-archive.comSubscribe Unsubscribe read only -mail-archives.apache.org +mail-archives.apache.org
+ lists.apache.org +markmail.org
old.nabble.com
www.mail-archive.com
diff --git a/src/site/xdoc/proposal.xml b/src/site/xdoc/proposal.xml index 2a8f69f11..bc73e85bb 100644 --- a/src/site/xdoc/proposal.xml +++ b/src/site/xdoc/proposal.xml @@ -22,16 +22,16 @@ limitations under the License. - +
March 24, 2016
- - + +Providing Java based optimized and high performance cryptographic IO streams for - the applications who wants to implement the data encryption. It also provides cipher + the applications that want to implement the data encryption. It also provides cipher level API to use. It does provide the openssl API integration and provide the fallback mechanism to use JCE when openssl library unavailable.
@@ -40,8 +40,8 @@ limitations under the License.This proposal is to create a package of cryptographic IO classes with the integration - of Openssl library. - It focuses on AES-NI optimizations mainly and it can be extended to other algorithms + of OpenSSL library. + It focuses on AES-NI optimizations mainly, and it can be extended to other algorithms based on demand from the users later.
diff --git a/src/site/xdoc/security.xml b/src/site/xdoc/security.xml new file mode 100644 index 000000000..e2bffb090 --- /dev/null +++ b/src/site/xdoc/security.xml @@ -0,0 +1,45 @@ + + ++ diff --git a/src/site/xdoc/userguide.xml b/src/site/xdoc/userguide.xml index 27550042b..fb6514c48 100644 --- a/src/site/xdoc/userguide.xml +++ b/src/site/xdoc/userguide.xml @@ -20,8 +20,12 @@ (Advanced Encryption Standard New Instructions). It provides Java API for both cipher level and Java stream level. Developers can use it to implement high performance AES encryption/decryption with - the minimum code and effort. Please note that Apache Commons Crypto doesn't implement the cryptographic - algorithm such as AES directly. It wraps to Openssl or JCE which implement the algorithms. + the minimum code and effort. + ++ + +Apache Commons Crypto Security Reports +Commons Team ++ + ++ For information about reporting or asking questions about + security, please see the + security page + of the Apache Commons project. +
++ This page lists all security vulnerabilities fixed in released versions of this component. +
+ ++ Please note that binary patches are never provided. If you need to apply a source code patch, use the + building instructions for the component version that you are using. +
+ ++ If you need help on building this component or other help on following the instructions to + mitigate the known vulnerabilities listed here, please send your questions to the public + user mailing list. +
+ ++ If you have encountered an unlisted security vulnerability or other unexpected behavior that has security + impact, or if the descriptions here are incomplete, please report them privately to the Apache Security + Team. Thank you. +
+ ++ Please note that Apache Commons Crypto doesn't implement the cryptographic + algorithm such as AES directly. It wraps OpenSSL or JCE which implement the algorithms. + OpenSSL 1.1.1 is required for building and running.
Interfaces and classes used by the various implementation in the sub-packages.
@@ -50,7 +54,7 @@- The interface wraps the underlying stream and it automatically encrypts + The interface wraps the underlying stream, and it automatically encrypts the stream when data is written and decrypts the stream when data is read. @@ -61,8 +65,12 @@Prerequisites
- Commons Crypto relies on standard JDK 1.7 (or above) and OpenSSL 1.0.1c (or above) for production + Commons Crypto relies on standard JDK 1.8 (or above) and OpenSSL 1.1.1 for production deployment. + If it is installed, the command
+openssl version
can be used to show the version. +OpenSSL may already be installed on your system; if not, please visit + OpenSSL.org for information on installation.
Using Commons Crypto in your Apache Maven build
@@ -78,7 +86,7 @@
Usage of Random API
CryptoRandom provides a cryptographically strong random number generators. - The default implementation will use Intel® Digital Random Number Generator (DRNG) + The default implementation will use Intel® Digital Random Number Generator (DRNG) for accelerating the random generation.
@@ -86,10 +94,10 @@Usage of Cipher API
- Cipher provides an cryptographic interface for encryption and decryption. - We provide two kind of implementations: JCE Cipher and Openssl Cipher. The - JCE implementation uses JCE provider and the Openssl implementation uses - Intel® AES New Instructions (Intel® AES NI). + Cipher provides a cryptographic interface for encryption and decryption. + We provide two kind of implementations: JCE Cipher and OpenSSL Cipher. The + JCE implementation uses JCE provider and the OpenSSL implementation uses + Intel® AES New Instructions (Intel® AES NI).
Usage of Byte Array Encryption/Decryption
diff --git a/src/test/java/org/apache/commons/crypto/AbstractBenchmark.java b/src/test/java/org/apache/commons/crypto/AbstractBenchmark.java index 1f551ae91..43b55895e 100644 --- a/src/test/java/org/apache/commons/crypto/AbstractBenchmark.java +++ b/src/test/java/org/apache/commons/crypto/AbstractBenchmark.java @@ -17,6 +17,8 @@ package org.apache.commons.crypto; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.nio.ByteBuffer; import java.util.Properties; @@ -28,8 +30,7 @@ import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.random.CryptoRandom; import org.apache.commons.crypto.random.CryptoRandomFactory; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.apache.commons.crypto.utils.AES; public abstract class AbstractBenchmark { @@ -37,7 +38,7 @@ public abstract class AbstractBenchmark { 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; private static final byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - private static final SecretKeySpec keySpec = new SecretKeySpec(KEY, "AES"); + private static final SecretKeySpec keySpec = AES.newSecretKeySpec(KEY); private static final IvParameterSpec ivSpec = new IvParameterSpec(IV); private static final byte[] BUFFER = new byte[1000]; @@ -45,13 +46,6 @@ public AbstractBenchmark() { super(); } - protected void random(final String cipherClass) throws Exception { - final CryptoRandom random = getRandom(cipherClass); - random.nextBytes(new byte[1000]); - random.nextBytes(new byte[1000]); - random.close(); - } - protected void encipher(final String cipherClass) throws Exception { final CryptoCipher enCipher = getCipher(cipherClass); enCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); @@ -64,6 +58,14 @@ protected void encipher(final String cipherClass) throws Exception { enCipher.close(); } + protected CryptoCipher getCipher(final String className) throws Exception { + final Properties properties = new Properties(); + properties.setProperty(CryptoCipherFactory.CLASSES_KEY, className); + final CryptoCipher cipher = CryptoCipherFactory.getCryptoCipher(AES.CTR_NO_PADDING, properties); + assertEquals(className, cipher.getClass().getCanonicalName()); + return cipher; + } + protected CryptoRandom getRandom(final String className) throws Exception { final Properties props = new Properties(); props.setProperty(CryptoRandomFactory.CLASSES_KEY, className); @@ -72,12 +74,11 @@ protected CryptoRandom getRandom(final String className) throws Exception { return cryptoRandom; } - protected CryptoCipher getCipher(final String className) throws Exception { - final Properties properties = new Properties(); - properties.setProperty(CryptoCipherFactory.CLASSES_KEY, className); - final CryptoCipher cipher = CryptoCipherFactory.getCryptoCipher("AES/CBC/PKCS5Padding", properties); - assertEquals(className, cipher.getClass().getCanonicalName()); - return cipher; + protected void random(final String cipherClass) throws Exception { + final CryptoRandom random = getRandom(cipherClass); + random.nextBytes(new byte[1000]); + random.nextBytes(new byte[1000]); + random.close(); } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/crypto/CryptoBenchmark.java b/src/test/java/org/apache/commons/crypto/CryptoBenchmark.java index 3ec4e9db4..70b9423d1 100644 --- a/src/test/java/org/apache/commons/crypto/CryptoBenchmark.java +++ b/src/test/java/org/apache/commons/crypto/CryptoBenchmark.java @@ -25,10 +25,10 @@ import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Mode; /** * Basic Benchmark to compare creation and runtimes for the different implementations. @@ -50,54 +50,54 @@ public class CryptoBenchmark extends AbstractBenchmark { private static final String CIPHER_JCE = CryptoCipherFactory.CipherProvider.JCE.getClassName(); @Benchmark - public void RandomCreateOS() throws Exception { - getRandom(RANDOM_OS); + public void CipherCreateJce() throws Exception { + getCipher(CIPHER_JCE); } @Benchmark - public void RandomCreateJava() throws Exception { - getRandom(RANDOM_JAVA); + public void CipherCreateOpenssl() throws Exception { + getCipher(CIPHER_OPENSSL); } @Benchmark - public void RandomCreateOpenssl() throws Exception { - getRandom(RANDOM_OPENSSL); + public void CipherTestJce() throws Exception { + encipher(CIPHER_JCE); } @Benchmark - public void RandomTestOS() throws Exception { - random(RANDOM_OS); + public void CipherTestOpenssl() throws Exception { + encipher(CIPHER_OPENSSL); } @Benchmark - public void RandomTestJava() throws Exception { - random(RANDOM_JAVA); + public void RandomCreateJava() throws Exception { + getRandom(RANDOM_JAVA); } @Benchmark - public void RandomTestOpenssl() throws Exception { - random(RANDOM_OPENSSL); + public void RandomCreateOpenssl() throws Exception { + getRandom(RANDOM_OPENSSL); } - + @Benchmark - public void CipherCreateJce() throws Exception { - getCipher(CIPHER_JCE); + public void RandomCreateOS() throws Exception { + getRandom(RANDOM_OS); } @Benchmark - public void CipherTestJce() throws Exception { - encipher(CIPHER_JCE); + public void RandomTestJava() throws Exception { + random(RANDOM_JAVA); } @Benchmark - public void CipherCreateOpenssl() throws Exception { - getCipher(CIPHER_OPENSSL); + public void RandomTestOpenssl() throws Exception { + random(RANDOM_OPENSSL); } @Benchmark - public void CipherTestOpenssl() throws Exception { - encipher(CIPHER_OPENSSL); + public void RandomTestOS() throws Exception { + random(RANDOM_OS); } } diff --git a/src/test/java/org/apache/commons/crypto/CryptoTest.java b/src/test/java/org/apache/commons/crypto/CryptoTest.java index 8cddcd534..9c1a532e6 100644 --- a/src/test/java/org/apache/commons/crypto/CryptoTest.java +++ b/src/test/java/org/apache/commons/crypto/CryptoTest.java @@ -13,59 +13,51 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto; - import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class CryptoTest { - /** - * This test may fail unless the code was built by Maven, as it relies on the - * VERSION file being set up correctly - */ - @Test - public void testGetComponentName() { - final String version = Crypto.getComponentName(); - assertNotNull("Should not be null", version); - assertTrue(version.matches("^Apache Commons Crypto.*"), version); - } - - /** - * This test may fail unless the code was built by Maven, as it relies on the - * VERSION file being set up correctly. - */ - @Test - public void testGetComponentVersion() { - final String version = Crypto.getComponentVersion(); - assertNotNull("Should not be null", version); - assertTrue(version.matches("^\\d+\\.\\d+.*"), version); - } - - @Test - @Disabled("Mac64 failure with OpenSSL 1.1.1g") - public void testMain() throws Throwable { - try { - Crypto.main(new String[0]); - } catch (final Throwable e) { - final Throwable loadingError = Crypto.getLoadingError(); - System.err.println("Special case; LoadingError = " + loadingError); - throw loadingError != null ? loadingError : e; - } - } - - @Test - public void testLoadingError() throws Throwable { - final Throwable loadingError = Crypto.getLoadingError(); - if (loadingError != null) { - throw loadingError; - } - } + /** + * This test may fail unless the code was built by Maven, as it relies on the VERSION file being set up correctly + */ + @Test + public void testGetComponentName() { + final String version = Crypto.getComponentName(); + assertNotNull("Should not be null", version); + assertTrue(version.matches("^Apache Commons Crypto.*"), version); + } + + /** + * This test may fail unless the code was built by Maven, as it relies on the VERSION file being set up correctly. + */ + @Test + public void testGetComponentVersion() { + final String version = Crypto.getComponentVersion(); + assertNotNull("Should not be null", version); + assertTrue(version.matches("^\\d+\\.\\d+.*"), version); + } + + @Test + public void testLoadingError() throws Throwable { + final Throwable loadingError = Crypto.getLoadingError(); + if (loadingError != null) { + throw loadingError; + } + assertTrue(true, "Completed OK"); + } + + @Test + public void testMain() throws Throwable { + // Check that Crypto.main will actually run tests + assertTrue(Crypto.isNativeCodeLoaded(), "Native code loaded OK"); + Crypto.main(new String[] { }); // show the JNI library details + assertTrue(Crypto.isNativeCodeLoaded(), "Completed OK"); + } } diff --git a/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java b/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java index c86196882..eeb6dbd82 100644 --- a/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java +++ b/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java @@ -23,7 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -33,19 +34,20 @@ public class NativeCodeLoaderTest { @Test public void test() { - if (NativeCodeLoader.isNativeCodeLoaded()) { - // TODO display versions once available - System.out.println("** INFO: Native (JNI) code loaded successfully"); - } else { - System.out.println("** WARN: Native (JNI) code was not loaded: " - + NativeCodeLoader.getLoadingError()); - } + assertTrue(NativeCodeLoader.isNativeCodeLoaded(), "Native (JNI) code loaded successfully"); } @Test - public void testNativePresent() { + @Disabled("Causes crash on Ubuntu when compiled with Java 17") + // The following error is reported: + // "Corrupted channel by directly writing to native stream in forked JVM 1" + // Note that this appears during a subsequent test, and does not + // happen every time. + // At this point it is not known where the native stream is written. + public void testCanLoadIfPresent() { assumeTrue(NativeCodeLoader.isNativeCodeLoaded()); - assertNull(NativeCodeLoader.getLoadingError()); + // This will try to reload the library, so should work + assertNull(NativeCodeLoader.loadLibrary()); } @Test @@ -55,27 +57,29 @@ public void testNativeNotPresent() { } @Test - public void testCanLoadIfPresent() { + public void testNativePresent() { assumeTrue(NativeCodeLoader.isNativeCodeLoaded()); - // This will try to reload the library, so should work - assertNull(NativeCodeLoader.loadLibrary()); + assertNull(NativeCodeLoader.getLoadingError()); } @Test @Disabled("Seems to cause issues with other tests on Linux; disable for now") + // It causes problems because the system properties are temporarily changed. + // However, properties are only fetched once, thus the test either corrupts the settings + // or does not work, depending on the order of tests. public void testUnSuccessfulLoad() throws Exception { final String nameKey = System.getProperty(Crypto.LIB_NAME_KEY); final String pathKey = System.getProperty(Crypto.LIB_PATH_KEY); // An empty file should cause UnsatisfiedLinkError - final File empty = File.createTempFile("NativeCodeLoaderTest", "tmp"); + final Path empty = Files.createTempFile("NativeCodeLoaderTest", "tmp"); try { - System.setProperty(Crypto.LIB_PATH_KEY, empty.getParent()); - System.setProperty(Crypto.LIB_NAME_KEY, empty.getName()); + System.setProperty(Crypto.LIB_PATH_KEY, empty.getParent().toString()); + System.setProperty(Crypto.LIB_NAME_KEY, empty.getFileName().toString()); final Throwable result = NativeCodeLoader.loadLibrary(); assertNotNull(result); assertTrue(result instanceof UnsatisfiedLinkError); } finally { - empty.delete(); + Files.delete(empty); if (nameKey != null) { System.setProperty(Crypto.LIB_NAME_KEY, nameKey); } diff --git a/src/test/java/org/apache/commons/crypto/OsInfoTest.java b/src/test/java/org/apache/commons/crypto/OsInfoTest.java new file mode 100644 index 000000000..a1c9e74af --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/OsInfoTest.java @@ -0,0 +1,30 @@ +/* + * 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. + */ +package org.apache.commons.crypto; + +import org.junit.jupiter.api.Test; + +public class OsInfoTest { + + @Test + public void testMain() { + OsInfo.main(new String[0]); + OsInfo.main(new String[] { "--os" }); + OsInfo.main(new String[] { "--arch" }); + } +} diff --git a/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java index 15b839886..111d0efb9 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java @@ -37,6 +37,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.ReflectionUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,18 +51,148 @@ public abstract class AbstractCipherTest { // data public static final int BYTEBUFFER_SIZE = 1000; - public String[] cipherTests; - private Properties props; - protected String cipherClass; - protected String[] transformations; - // cipher static final byte[] KEY = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24 }; static final byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + public String[] cipherTests; + private Properties props; + + protected String cipherClass; + protected String[] transformations; private CryptoCipher enc, dec; + /** test byte array whose data is randomly generated */ + private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv) throws Exception { + final int blockSize = enc.getBlockSize(); + + // AES_CBC_NOPADDING only accepts data whose size is the multiple of + // block size + final int[] dataLenList = transformation.equals(AES.CBC_NO_PADDING) ? new int[] { 10 * 1024 } + : new int[] { 10 * 1024, 10 * 1024 - 3 }; + for (final int dataLen : dataLenList) { + final byte[] plainText = new byte[dataLen]; + final Random random = new SecureRandom(); + random.nextBytes(plainText); + final byte[] cipherText = new byte[dataLen + blockSize]; + + // check update method with inputs whose sizes are the multiple of + // block size or not + final int[] bufferLenList = { 2 * 1024 - 128, 2 * 1024 - 125 }; + for (final int bufferLen : bufferLenList) { + resetCipher(transformation, key, iv); + + int offset = 0; + // encrypt (update + doFinal) the data + int cipherPos = 0; + for (int i = 0; i < dataLen / bufferLen; i++) { + cipherPos += enc.update(plainText, offset, bufferLen, cipherText, cipherPos); + offset += bufferLen; + } + cipherPos += enc.doFinal(plainText, offset, dataLen % bufferLen, cipherText, cipherPos); + + offset = 0; + // decrypt (update + doFinal) the data + final byte[] realPlainText = new byte[cipherPos + blockSize]; + int plainPos = 0; + for (int i = 0; i < cipherPos / bufferLen; i++) { + plainPos += dec.update(cipherText, offset, bufferLen, realPlainText, plainPos); + offset += bufferLen; + } + plainPos += dec.doFinal(cipherText, offset, cipherPos % bufferLen, realPlainText, plainPos); + + // verify + assertEquals(dataLen, plainPos, "random byte array length changes after transformation"); + + final byte[] shrinkPlainText = new byte[plainPos]; + System.arraycopy(realPlainText, 0, shrinkPlainText, 0, plainPos); + assertArrayEquals(plainText, shrinkPlainText, "random byte array contents changes after transformation"); + } + } + } + + /** test byte array whose data is planned in {@link TestData} */ + private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv, final byte[] input, + final byte[] output) throws Exception { + resetCipher(transformation, key, iv); + final int blockSize = enc.getBlockSize(); + + byte[] temp = new byte[input.length + blockSize]; + final int n = enc.doFinal(input, 0, input.length, temp, 0); + final byte[] cipherText = new byte[n]; + System.arraycopy(temp, 0, cipherText, 0, n); + assertArrayEquals(output, cipherText, "byte array encryption error."); + + temp = new byte[cipherText.length + blockSize]; + final int m = dec.doFinal(cipherText, 0, cipherText.length, temp, 0); + final byte[] plainText = new byte[m]; + System.arraycopy(temp, 0, plainText, 0, m); + assertArrayEquals(input, plainText, "byte array decryption error."); + } + + private void byteBufferTest(final String transformation, final byte[] key, final byte[] iv, final ByteBuffer input, + final ByteBuffer output) throws Exception { + final ByteBuffer decResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE); + final ByteBuffer encResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE); + + try (final CryptoCipher enc = getCipher(transformation); final CryptoCipher dec = getCipher(transformation)) { + + enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); + dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); + + // + // encryption pass + // + enc.doFinal(input, encResult); + input.flip(); + encResult.flip(); + if (!output.equals(encResult)) { + final byte[] b = new byte[output.remaining()]; + output.get(b); + final byte[] c = new byte[encResult.remaining()]; + encResult.get(c); + fail("AES failed encryption - expected " + DatatypeConverter.printHexBinary(b) + + " got " + DatatypeConverter.printHexBinary(c)); + } + + // + // decryption pass + // + dec.doFinal(encResult, decResult); + decResult.flip(); + + if (!input.equals(decResult)) { + final byte[] inArray = new byte[input.remaining()]; + final byte[] decResultArray = new byte[decResult.remaining()]; + input.get(inArray); + decResult.get(decResultArray); + fail(); + } + } + } + + private CryptoCipher getCipher(final String transformation) throws ClassNotFoundException { + return (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(cipherClass), props, + transformation); + } + + protected abstract void init(); + + SecretKeySpec newSecretKeySpec() { + return AES.newSecretKeySpec(KEY); + } + + private void resetCipher(final String transformation, final byte[] key, final byte[] iv) + throws ClassNotFoundException, InvalidKeyException, InvalidAlgorithmParameterException { + enc = getCipher(transformation); + dec = getCipher(transformation); + + enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); + + dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); + } + @BeforeEach public void setup() { init(); @@ -71,50 +202,26 @@ public void setup() { props.setProperty(CryptoCipherFactory.CLASSES_KEY, cipherClass); } - protected abstract void init(); - - @Test - public void closeTestNoInit() throws Exception { - // This test deliberately does not use try with resources in order to control - // the sequence of operations exactly - try (final CryptoCipher enc = getCipher(transformations[0])) { - // empty - } - } - @Test - public void closeTestAfterInit() throws Exception { + public void testCloseTestAfterInit() throws Exception { // This test deliberately does not use try with resources in order to control // the sequence of operations exactly try (final CryptoCipher enc = getCipher(transformations[0])) { - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); + enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); } } @Test - public void reInitTest() throws Exception { + public void testCloseTestNoInit() throws Exception { // This test deliberately does not use try with resources in order to control // the sequence of operations exactly - try (final CryptoCipher enc = getCipher(transformations[0])) { - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); - enc.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); - } + try (final CryptoCipher enc = getCipher(transformations[0])) { + // empty + } } @Test - public void reInitAfterClose() throws Exception { - // This test deliberately does not use try with resources in order to control - // the sequence of operations exactly - try (final CryptoCipher enc = getCipher(transformations[0])) { - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); - enc.close(); - enc.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(IV)); - } - } - - @Test - public void closeTestRepeat() throws Exception { + public void testCloseTestRepeat() throws Exception { // This test deliberately does not use try with resources in order to control // the sequence of operations exactly try (final CryptoCipher enc = getCipher(transformations[0])) { @@ -124,7 +231,7 @@ public void closeTestRepeat() throws Exception { } @Test - public void cryptoTest() throws Exception { + public void testCryptoTest() throws Exception { for (final String tran : transformations) { /** uses the small data set in {@link TestData} */ cipherTests = TestData.getTestData(tran); @@ -152,31 +259,6 @@ public void cryptoTest() throws Exception { } } - @Test - public void testNullTransform() { - assertThrows(IllegalArgumentException.class, - () -> getCipher(null).close()); - } - - @Test - public void testInvalidTransform() { - assertThrows(IllegalArgumentException.class, - () -> getCipher("AES/CBR/NoPadding/garbage/garbage").close()); - } - - @Test - public void testInvalidKey() throws Exception { - for (final String transform : transformations) { - try (final CryptoCipher cipher = getCipher(transform)) { - assertNotNull(cipher); - - final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; - assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(invalidKey, "AES"), new IvParameterSpec(IV))); - } - } - } - @Test public void testInvalidIV() throws Exception { for (final String transform : transformations) { @@ -184,7 +266,7 @@ public void testInvalidIV() throws Exception { assertNotNull(cipher); final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; - assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new IvParameterSpec(invalidIV))); + assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(invalidIV))); } } } @@ -194,132 +276,55 @@ public void testInvalidIVClass() throws Exception { for (final String transform : transformations) { try (final CryptoCipher cipher = getCipher(transform)) { assertNotNull(cipher); - assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), new GCMParameterSpec(IV.length, IV))); + assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, newSecretKeySpec(), new GCMParameterSpec(IV.length, IV))); } } } - private void byteBufferTest(final String transformation, final byte[] key, final byte[] iv, final ByteBuffer input, - final ByteBuffer output) throws Exception { - final ByteBuffer decResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE); - final ByteBuffer encResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE); - - try (final CryptoCipher enc = getCipher(transformation); final CryptoCipher dec = getCipher(transformation)) { - - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - - // - // encryption pass - // - enc.doFinal(input, encResult); - input.flip(); - encResult.flip(); - if (!output.equals(encResult)) { - final byte[] b = new byte[output.remaining()]; - output.get(b); - final byte[] c = new byte[encResult.remaining()]; - encResult.get(c); - fail("AES failed encryption - expected " + DatatypeConverter.printHexBinary(b) - + " got " + DatatypeConverter.printHexBinary(c)); - } - - // - // decryption pass - // - dec.doFinal(encResult, decResult); - decResult.flip(); + @Test + public void testInvalidKey() throws Exception { + for (final String transform : transformations) { + try (final CryptoCipher cipher = getCipher(transform)) { + assertNotNull(cipher); - if (!input.equals(decResult)) { - final byte[] inArray = new byte[input.remaining()]; - final byte[] decResultArray = new byte[decResult.remaining()]; - input.get(inArray); - decResult.get(decResultArray); - fail(); + final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; + assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(invalidKey), new IvParameterSpec(IV))); } } } - /** test byte array whose data is planned in {@link TestData} */ - private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv, final byte[] input, - final byte[] output) throws Exception { - resetCipher(transformation, key, iv); - final int blockSize = enc.getBlockSize(); - - byte[] temp = new byte[input.length + blockSize]; - final int n = enc.doFinal(input, 0, input.length, temp, 0); - final byte[] cipherText = new byte[n]; - System.arraycopy(temp, 0, cipherText, 0, n); - assertArrayEquals(output, cipherText, "byte array encryption error."); - - temp = new byte[cipherText.length + blockSize]; - final int m = dec.doFinal(cipherText, 0, cipherText.length, temp, 0); - final byte[] plainText = new byte[m]; - System.arraycopy(temp, 0, plainText, 0, m); - assertArrayEquals(input, plainText, "byte array decryption error."); + @Test + public void testInvalidTransform() { + assertThrows(IllegalArgumentException.class, + () -> getCipher("AES/CBR/NoPadding/garbage/garbage").close()); } - /** test byte array whose data is randomly generated */ - private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv) throws Exception { - final int blockSize = enc.getBlockSize(); - - // AES_CBC_NOPADDING only accepts data whose size is the multiple of - // block size - final int[] dataLenList = transformation.equals("AES/CBC/NoPadding") ? new int[] { 10 * 1024 } - : new int[] { 10 * 1024, 10 * 1024 - 3 }; - for (final int dataLen : dataLenList) { - final byte[] plainText = new byte[dataLen]; - final Random random = new SecureRandom(); - random.nextBytes(plainText); - final byte[] cipherText = new byte[dataLen + blockSize]; - - // check update method with inputs whose sizes are the multiple of - // block size or not - final int[] bufferLenList = new int[] { 2 * 1024 - 128, 2 * 1024 - 125 }; - for (final int bufferLen : bufferLenList) { - resetCipher(transformation, key, iv); - - int offset = 0; - // encrypt (update + doFinal) the data - int cipherPos = 0; - for (int i = 0; i < dataLen / bufferLen; i++) { - cipherPos += enc.update(plainText, offset, bufferLen, cipherText, cipherPos); - offset += bufferLen; - } - cipherPos += enc.doFinal(plainText, offset, dataLen % bufferLen, cipherText, cipherPos); - - offset = 0; - // decrypt (update + doFinal) the data - final byte[] realPlainText = new byte[cipherPos + blockSize]; - int plainPos = 0; - for (int i = 0; i < cipherPos / bufferLen; i++) { - plainPos += dec.update(cipherText, offset, bufferLen, realPlainText, plainPos); - offset += bufferLen; - } - plainPos += dec.doFinal(cipherText, offset, cipherPos % bufferLen, realPlainText, plainPos); - - // verify - assertEquals(dataLen, plainPos, "random byte array length changes after transformation"); - - final byte[] shrinkPlainText = new byte[plainPos]; - System.arraycopy(realPlainText, 0, shrinkPlainText, 0, plainPos); - assertArrayEquals(plainText, shrinkPlainText, "random byte array contents changes after transformation"); - } - } + @Test + public void testNullTransform() { + assertThrows(IllegalArgumentException.class, + () -> getCipher(null).close()); } - private void resetCipher(final String transformation, final byte[] key, final byte[] iv) - throws ClassNotFoundException, InvalidKeyException, InvalidAlgorithmParameterException { - enc = getCipher(transformation); - dec = getCipher(transformation); - - enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - - dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - } + @Test + public void testReInitAfterClose() throws Exception { + // This test deliberately does not use try with resources in order to control + // the sequence of operations exactly + try (final CryptoCipher enc = getCipher(transformations[0])) { + enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); + enc.close(); + enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); + } + } - private CryptoCipher getCipher(final String transformation) throws ClassNotFoundException { - return (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(cipherClass), props, - transformation); + @Test + public void testReInitTest() throws Exception { + // This test deliberately does not use try with resources in order to control + // the sequence of operations exactly + try (final CryptoCipher enc = getCipher(transformations[0])) { + enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); + enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); + enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV)); + } } } diff --git a/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherFactoryTest.java b/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherFactoryTest.java index 1ce6148ab..31cf76fc4 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherFactoryTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherFactoryTest.java @@ -1,73 +1,72 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.cipher; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Properties; +import org.apache.commons.crypto.utils.AES; import org.junit.jupiter.api.Test; - public class CryptoCipherFactoryTest { + @Test - public void testDefaultCipher() throws GeneralSecurityException { - final CryptoCipher defaultCipher = CryptoCipherFactory - .getCryptoCipher("AES/CBC/NoPadding"); - final String name = defaultCipher.getClass().getName(); - if (OpenSsl.getLoadingFailureReason() == null) { - assertEquals(OpenSslCipher.class.getName(), name); - } else { - assertEquals(JceCipher.class.getName(), name); + public void testDefaultCipher() throws GeneralSecurityException, IOException { + try (CryptoCipher defaultCipher = CryptoCipherFactory.getCryptoCipher(AES.CTR_NO_PADDING)) { + final String name = defaultCipher.getClass().getName(); + if (OpenSsl.getLoadingFailureReason() == null) { + assertEquals(OpenSslCipher.class.getName(), name); + } else { + assertEquals(JceCipher.class.getName(), name); + } } } @Test - public void testEmptyCipher() throws GeneralSecurityException { + public void testEmptyCipher() throws GeneralSecurityException, IOException { final Properties properties = new Properties(); properties.setProperty(CryptoCipherFactory.CLASSES_KEY, ""); // TODO should this really mean use the default? - final CryptoCipher defaultCipher = CryptoCipherFactory.getCryptoCipher( - "AES/CBC/NoPadding", properties); - final String name = defaultCipher.getClass().getName(); - if (OpenSsl.getLoadingFailureReason() == null) { - assertEquals(OpenSslCipher.class.getName(), name); - } else { - assertEquals(JceCipher.class.getName(), name); + try (CryptoCipher defaultCipher = CryptoCipherFactory.getCryptoCipher(AES.CBC_NO_PADDING, properties)) { + final String name = defaultCipher.getClass().getName(); + if (OpenSsl.getLoadingFailureReason() == null) { + assertEquals(OpenSslCipher.class.getName(), name); + } else { + assertEquals(JceCipher.class.getName(), name); + } } } @Test public void testInvalidCipher() { final Properties properties = new Properties(); - properties.setProperty(CryptoCipherFactory.CLASSES_KEY, - "InvalidCipherName"); - assertThrows(GeneralSecurityException.class, - () -> CryptoCipherFactory.getCryptoCipher("AES/CBC/NoPadding", properties)); + properties.setProperty(CryptoCipherFactory.CLASSES_KEY, "InvalidCipherName"); + assertThrows(GeneralSecurityException.class, () -> CryptoCipherFactory.getCryptoCipher(AES.CBC_NO_PADDING, properties)); } @Test public void testInvalidTransformation() { - final Properties properties = new Properties(); - assertThrows(GeneralSecurityException.class, - () -> CryptoCipherFactory.getCryptoCipher("AES/Invalid/NoPadding", properties)); + final Properties properties = new Properties(); + assertThrows(GeneralSecurityException.class, () -> CryptoCipherFactory.getCryptoCipher("AES/Invalid/NoPadding", properties)); } @@ -77,8 +76,7 @@ public void testNoCipher() { // An empty string currently means use the default // However the splitter drops empty fields properties.setProperty(CryptoCipherFactory.CLASSES_KEY, ","); - assertThrows(IllegalArgumentException.class, - () -> CryptoCipherFactory.getCryptoCipher("AES/CBC/NoPadding", properties)); + assertThrows(IllegalArgumentException.class, () -> CryptoCipherFactory.getCryptoCipher(AES.CBC_NO_PADDING, properties)); } diff --git a/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherTest.java new file mode 100644 index 000000000..20b641ba4 --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/cipher/CryptoCipherTest.java @@ -0,0 +1,41 @@ +/* + * 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. + */ + +package org.apache.commons.crypto.cipher; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.ByteBuffer; + +import org.junit.jupiter.api.Test; + +/** + * Tests default methods. + */ +public class CryptoCipherTest { + + @Test + public void testUpdateAADByteArray() { + assertThrows(UnsupportedOperationException.class, () -> new DefaultCryptoCipher().updateAAD((byte[]) null)); + } + + @Test + public void testUpdateAADByteBuffer() { + assertThrows(UnsupportedOperationException.class, () -> new DefaultCryptoCipher().updateAAD((ByteBuffer) null)); + } +} diff --git a/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java b/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java new file mode 100644 index 000000000..09dc2496d --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +package org.apache.commons.crypto.cipher; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +/** + * Tests default methods. + */ +public class DefaultCryptoCipher implements CryptoCipher { + + @Override + public void close() throws IOException { + // Simplest + + } + + @Override + public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + // Simplest + return 0; + } + + @Override + public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + // Simplest + return 0; + } + + @Override + public String getAlgorithm() { + // Simplest + return null; + } + + @Override + public int getBlockSize() { + // Simplest + return 0; + } + + @Override + public void init(final int mode, final Key key, final AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { + // Simplest + + } + + @Override + public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException { + // Simplest + return 0; + } + + @Override + public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException { + // Simplest + return 0; + } + +} diff --git a/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java index ceb60fffd..fb9e189ca 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java @@ -30,9 +30,9 @@ import javax.crypto.AEADBadTagException; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,9 +40,10 @@ public class GcmCipherTest { + private static final String GCM_NO_PADDING = "AES/GCM/NoPadding"; Properties props; String cipherClass; - String transformation = "AES/GCM/NoPadding"; + String transformation = GCM_NO_PADDING; private String[] kHex; private String[] pHex; @@ -51,6 +52,92 @@ public class GcmCipherTest { private String[] cHex; private String[] tHex; + private void initTestData() { + + final int casesNumber = 4; + + kHex = new String[casesNumber]; + pHex = new String[casesNumber]; + ivHex = new String[casesNumber]; + aadHex = new String[casesNumber]; + cHex = new String[casesNumber]; + tHex = new String[casesNumber]; + + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf + // NIST Case2 ----------------------------- + // key length: 16 bytes + // plain text length: 16 bytes + // iv length: 12 bytes + // aad length: 0 bytes + kHex[0] = "00000000000000000000000000000000"; + pHex[0] = "00000000000000000000000000000000"; + ivHex[0] = "000000000000000000000000"; + aadHex[0] = ""; + cHex[0] = "0388dace60b6a392f328c2b971b2fe78"; + tHex[0] = "ab6e47d42cec13bdf53a67b21257bddf"; + + // NIST Case4 --------------------------------- + // key length: 16 bytes + // plain text length: 60 bytes + // iv length: 12 bytes + // aad length: 20 bytes + kHex[1] = "feffe9928665731c6d6a8f9467308308"; + pHex[1] = "d9313225f88406e5a55909c5aff5269a" + + "86a7a9531534f7da2e4c303d8a318a72" + + "1c3c0c95956809532fcf0e2449a6b525" + + "b16aedf5aa0de657ba637b39"; + ivHex[1] = "cafebabefacedbaddecaf888"; + aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef" + + "abaddad2"; + cHex[1] = "42831ec2217774244b7221b784d0d49c" + + "e3aa212f2c02a4e035c17e2329aca12e" + + "21d514b25466931c7d8f6a5aac84aa05" + + "1ba30b396a0aac973d58e091"; + tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47"; + + // NIST Case5 --------------------------------- + // key length: 16 bytes + // plain text length: 60 bytes + // iv length: 8 bytes + // aad length: 20 bytes + kHex[2] = "feffe9928665731c6d6a8f9467308308"; + pHex[2] = "d9313225f88406e5a55909c5aff5269a" + + "86a7a9531534f7da2e4c303d8a318a72" + + "1c3c0c95956809532fcf0e2449a6b525" + + "b16aedf5aa0de657ba637b39"; + ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits + aadHex[2]="feedfacedeadbeeffeedfacedeadbeef" + + "abaddad2"; + cHex[2] = "61353b4c2806934a777ff51fa22a4755" + + "699b2a714fcdc6f83766e5f97b6c7423" + + "73806900e49f24b22b097544d4896b42" + + "4989b5e1ebac0f07c23f4598"; + tHex[2] = "3612d2e79e3b0785561be14aaca2fccb"; + + // NIST Case6 --------------------------------- + // key length: 16 bytes + // plain text length: 60 bytes + // iv length: 60 bytes + // aad length: 20 bytes + kHex[3] = "feffe9928665731c6d6a8f9467308308"; + pHex[3] = "d9313225f88406e5a55909c5aff5269a" + + "86a7a9531534f7da2e4c303d8a318a72" + + "1c3c0c95956809532fcf0e2449a6b525" + + "b16aedf5aa0de657ba637b39"; + ivHex[3] = "9313225df88406e555909c5aff5269aa" + + "6a7a9538534f7da1e4c303d2a318a728" + + "c3c0c95156809539fcf0e2429a6b5254" + + "16aedbf5a0de6a57a637b39b"; // > 96bits + aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef" + + "abaddad2"; + cHex[3] = "8ce24998625615b603a033aca13fb894" + + "be9112a5c3a211a8ba262a3cca7e2ca7" + + "01e4a9a4fba43c90ccdcb281d48c7c6f" + + "d62875d2aca417034c34aee5"; + tHex[3] = "619cc5aefffe0bfa462af43c1699d050"; + } + @BeforeEach public void setup() { //init @@ -62,6 +149,171 @@ public void setup() { initTestData(); } + private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex, + final String cHex, final String tHex) throws Exception { + + final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); + final byte[] input = DatatypeConverter.parseHexBinary(pHex); + final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); + final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); + final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); + + final byte[] encOutput = new byte[expectedOutput.length]; + final byte[] decOutput = new byte[input.length]; + + final Random r = new Random(); + + int partLen; + int len; + try (final CryptoCipher enc = Utils.getCipherInstance(transformation, props)) { + final Key key = AES.newSecretKeySpec(keyBytes); + final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); + enc.init(Cipher.ENCRYPT_MODE, key, iv); + if (aad.length > 0) { + final int len1 = r.nextInt(aad.length); + final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); + final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); + enc.updateAAD(aad1); + enc.updateAAD(aad2); + } + + partLen = r.nextInt(input.length); + len = enc.update(input, 0, partLen, encOutput, 0); + assertEquals(partLen, len); + len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen); + assertEquals((input.length + (iv.getTLen() >> 3) - partLen), len); + + assertArrayEquals(expectedOutput, encOutput); + } + + // Decryption + try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { + dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes)); + if (aad.length > 0) { + final int len1 = r.nextInt(aad.length); + final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); + final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); + dec.updateAAD(aad1); + dec.updateAAD(aad2); + } + final byte[] decInput = encOutput; + partLen = r.nextInt(input.length); + len = dec.update(decInput, 0, partLen, decOutput, 0); + assertEquals(len, 0); + len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0); + assertEquals(input.length, len); + + assertArrayEquals(input, decOutput); + } + } + + private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex, + final String cHex, final String tHex) throws Exception { + + final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); + final byte[] plainText = DatatypeConverter.parseHexBinary(pHex); + final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); + final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); + final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex); + + final byte[] encOutput = new byte[cipherText.length]; + final byte[] decOutput = new byte[plainText.length]; + + final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length); + bfAAD.put(aad); + + final ByteBuffer bfPlainText; + final ByteBuffer bfCipherText; + bfPlainText = ByteBuffer.allocateDirect(plainText.length); + bfCipherText = ByteBuffer.allocateDirect(encOutput.length); + + // Encryption ------------------- + try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { + final Key key = AES.newSecretKeySpec(keyBytes); + final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); + c.init(Cipher.ENCRYPT_MODE, key, iv); + + bfAAD.flip(); + c.updateAAD(bfAAD); + + bfPlainText.put(plainText); + bfPlainText.flip(); + bfCipherText.position(0); + + c.doFinal(bfPlainText, bfCipherText); + + bfCipherText.flip(); + bfCipherText.get(encOutput); + assertArrayEquals(cipherText, encOutput); + } + + // Decryption ------------------- + try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { + dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes)); + bfAAD.flip(); + dec.updateAAD(bfAAD); + bfCipherText.clear(); + bfPlainText.clear(); + bfCipherText.put(cipherText); + bfCipherText.flip(); + dec.doFinal(bfCipherText, bfPlainText); + bfPlainText.flip(); + bfPlainText.get(decOutput); + assertArrayEquals(plainText, decOutput); + } + } + + private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex, + final String cHex, final String tHex) throws Exception { + + final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); + final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); + final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); + + final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); + final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); + + final byte[] input = cipherBytes; + final byte[] output = new byte[plainBytes.length]; + + try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { + + final Key key = AES.newSecretKeySpec(keyBytes); + + final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); + c.init(Cipher.DECRYPT_MODE, key, iv); + c.updateAAD(aad); + c.doFinal(input, 0, input.length, output, 0); + + assertArrayEquals(plainBytes, output); + } + } + + private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex, + final String cHex, final String tHex) throws Exception { + + final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); + final byte[] input = DatatypeConverter.parseHexBinary(pHex); + final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); + final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); + final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); + + final byte[] output = new byte[expectedOutput.length]; + + try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { + + final Key key = AES.newSecretKeySpec(keyBytes); + + final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); + c.init(Cipher.ENCRYPT_MODE, key, iv); + c.updateAAD(aad); + + c.doFinal(input, 0, input.length, output, 0); + + assertArrayEquals(expectedOutput, output); + } + } + /** * NIST AES Test Vectors * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf @@ -198,15 +450,46 @@ public void testGcmNistCases() throws Exception { } } - @Test - public void testGcmTamperedData() throws Exception { - - final Random r = new Random(); - final int textLength = r.nextInt(1024*1024); - final int ivLength = r.nextInt(59) + 1; - final int keyLength = 16; - final int tagLength = 128; // bits - final int aadLength = r.nextInt(128); + private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex, + final String cHex, final String tHex) throws Exception { + + final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); + final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); + final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); + + final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); + final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); + + final byte[] input = cipherBytes; + final byte[] output = new byte[plainBytes.length]; + + try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { + + final Key key = AES.newSecretKeySpec(keyBytes); + + final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); + c.init(Cipher.DECRYPT_MODE, key, iv); + c.updateAAD(aad); + + // only return recovered data after tag is successfully verified + int len = c.update(input, 0, input.length, output, 0); + assertEquals(len, 0); + len += c.doFinal(input, input.length, 0, output, 0); + assertEquals(plainBytes.length, len); + + assertArrayEquals(plainBytes, output); + } + } + + @Test + public void testGcmTamperedData() throws Exception { + + final Random r = new Random(); + final int textLength = r.nextInt(1024*1024); + final int ivLength = r.nextInt(59) + 1; + final int keyLength = 16; + final int tagLength = 128; // bits + final int aadLength = r.nextInt(128); final byte[] keyBytes = new byte[keyLength]; final byte[] plainBytes = new byte[textLength]; @@ -222,7 +505,7 @@ public void testGcmTamperedData() throws Exception { final byte[] decOutput = new byte[plainBytes.length]; try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes); c.init(Cipher.ENCRYPT_MODE, key, iv); @@ -234,7 +517,7 @@ public void testGcmTamperedData() throws Exception { encOutput[0] = (byte)(encOutput[0] + 1); try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes); c.init(Cipher.DECRYPT_MODE, key, iv); @@ -252,7 +535,7 @@ public void testGMac() throws Exception { final Random r = new Random(); final byte[] keyBytes = new byte[32]; - final byte[] input = new byte[0]; // no input for GMAC + final byte[] input = {}; // no input for GMAC final byte[] ivBytes = new byte[16]; final byte[] tag_orig = new byte[16]; // JDK's tag @@ -268,7 +551,7 @@ public void testGMac() throws Exception { { final Cipher c = Cipher.getInstance(transformation); - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); c.init(Cipher.ENCRYPT_MODE, key, iv); c.updateAAD(aad); @@ -276,7 +559,7 @@ public void testGMac() throws Exception { } try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); c.init(Cipher.ENCRYPT_MODE, key, iv); c.updateAAD(aad); @@ -289,7 +572,7 @@ public void testGMac() throws Exception { // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode // let's verify the add & tag now try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); c.init(Cipher.DECRYPT_MODE, key, iv); c.updateAAD(aad); @@ -302,7 +585,7 @@ public void testGMac() throws Exception { public void testGMacTamperedData() throws Exception { final Random r = new Random(); final byte[] keyBytes = new byte[32]; - final byte[] input = new byte[0]; + final byte[] input = {}; final byte[] ivBytes = new byte[16]; final byte[] tag = new byte[16]; @@ -315,7 +598,7 @@ public void testGMacTamperedData() throws Exception { r.nextBytes(aad); try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); c.init(Cipher.ENCRYPT_MODE, key, iv); c.updateAAD(aad); @@ -324,7 +607,7 @@ public void testGMacTamperedData() throws Exception { // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); + final Key key = AES.newSecretKeySpec(keyBytes); final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); c.init(Cipher.DECRYPT_MODE, key, iv); @@ -337,286 +620,4 @@ public void testGMacTamperedData() throws Exception { } } - - private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex, - final String cHex, final String tHex) throws Exception { - - final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); - final byte[] input = DatatypeConverter.parseHexBinary(pHex); - final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); - final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); - final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); - - final byte[] output = new byte[expectedOutput.length]; - - try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - - final Key key = new SecretKeySpec(keyBytes, "AES"); - - final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); - c.init(Cipher.ENCRYPT_MODE, key, iv); - c.updateAAD(aad); - - c.doFinal(input, 0, input.length, output, 0); - - assertArrayEquals(expectedOutput, output); - } - } - - private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex, - final String cHex, final String tHex) throws Exception { - - final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); - final byte[] input = DatatypeConverter.parseHexBinary(pHex); - final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); - final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); - final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); - - final byte[] encOutput = new byte[expectedOutput.length]; - final byte[] decOutput = new byte[input.length]; - - final Random r = new Random(); - - int partLen; - int len; - try (final CryptoCipher enc = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); - final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); - enc.init(Cipher.ENCRYPT_MODE, key, iv); - if (aad.length > 0) { - final int len1 = r.nextInt(aad.length); - final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); - final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); - enc.updateAAD(aad1); - enc.updateAAD(aad2); - } - - partLen = r.nextInt(input.length); - len = enc.update(input, 0, partLen, encOutput, 0); - assertEquals(partLen, len); - len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen); - assertEquals((input.length + (iv.getTLen() >> 3) - partLen), len); - - assertArrayEquals(expectedOutput, encOutput); - } - - // Decryption - try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { - dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new GCMParameterSpec(128, ivBytes)); - if (aad.length > 0) { - final int len1 = r.nextInt(aad.length); - final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); - final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); - dec.updateAAD(aad1); - dec.updateAAD(aad2); - } - final byte[] decInput = encOutput; - partLen = r.nextInt(input.length); - len = dec.update(decInput, 0, partLen, decOutput, 0); - assertEquals(len, 0); - len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0); - assertEquals(input.length, len); - - assertArrayEquals(input, decOutput); - } - } - - private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex, - final String cHex, final String tHex) throws Exception { - - final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); - final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); - final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); - - final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); - final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); - - final byte[] input = cipherBytes; - final byte[] output = new byte[plainBytes.length]; - - try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - - final Key key = new SecretKeySpec(keyBytes, "AES"); - - final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); - c.init(Cipher.DECRYPT_MODE, key, iv); - c.updateAAD(aad); - c.doFinal(input, 0, input.length, output, 0); - - assertArrayEquals(plainBytes, output); - } - } - - private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex, - final String cHex, final String tHex) throws Exception { - - final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); - final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); - final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); - - final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); - final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); - - final byte[] input = cipherBytes; - final byte[] output = new byte[plainBytes.length]; - - try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - - final Key key = new SecretKeySpec(keyBytes, "AES"); - - final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); - c.init(Cipher.DECRYPT_MODE, key, iv); - c.updateAAD(aad); - - // only return recovered data after tag is successfully verified - int len = c.update(input, 0, input.length, output, 0); - assertEquals(len, 0); - len += c.doFinal(input, input.length, 0, output, 0); - assertEquals(plainBytes.length, len); - - assertArrayEquals(plainBytes, output); - } - } - - private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex, - final String cHex, final String tHex) throws Exception { - - final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); - final byte[] plainText = DatatypeConverter.parseHexBinary(pHex); - final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); - final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); - final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex); - - final byte[] encOutput = new byte[cipherText.length]; - final byte[] decOutput = new byte[plainText.length]; - - final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length); - bfAAD.put(aad); - - final ByteBuffer bfPlainText; - final ByteBuffer bfCipherText; - bfPlainText = ByteBuffer.allocateDirect(plainText.length); - bfCipherText = ByteBuffer.allocateDirect(encOutput.length); - - // Encryption ------------------- - try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { - final Key key = new SecretKeySpec(keyBytes, "AES"); - final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); - c.init(Cipher.ENCRYPT_MODE, key, iv); - - bfAAD.flip(); - c.updateAAD(bfAAD); - - bfPlainText.put(plainText); - bfPlainText.flip(); - bfCipherText.position(0); - - c.doFinal(bfPlainText, bfCipherText); - - bfCipherText.flip(); - bfCipherText.get(encOutput); - assertArrayEquals(cipherText, encOutput); - } - - // Decryption ------------------- - try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { - dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new GCMParameterSpec(128, ivBytes)); - bfAAD.flip(); - dec.updateAAD(bfAAD); - bfCipherText.clear(); - bfPlainText.clear(); - bfCipherText.put(cipherText); - bfCipherText.flip(); - dec.doFinal(bfCipherText, bfPlainText); - bfPlainText.flip(); - bfPlainText.get(decOutput); - assertArrayEquals(plainText, decOutput); - } - } - - private void initTestData() { - - final int casesNumber = 4; - - kHex = new String[casesNumber]; - pHex = new String[casesNumber]; - ivHex = new String[casesNumber]; - aadHex = new String[casesNumber]; - cHex = new String[casesNumber]; - tHex = new String[casesNumber]; - - // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf - // NIST Case2 ----------------------------- - // key length: 16 bytes - // plain text length: 16 bytes - // iv length: 12 bytes - // aad length: 0 bytes - kHex[0] = "00000000000000000000000000000000"; - pHex[0] = "00000000000000000000000000000000"; - ivHex[0] = "000000000000000000000000"; - aadHex[0] = ""; - cHex[0] = "0388dace60b6a392f328c2b971b2fe78"; - tHex[0] = "ab6e47d42cec13bdf53a67b21257bddf"; - - // NIST Case4 --------------------------------- - // key length: 16 bytes - // plain text length: 60 bytes - // iv length: 12 bytes - // aad length: 20 bytes - kHex[1] = "feffe9928665731c6d6a8f9467308308"; - pHex[1] = "d9313225f88406e5a55909c5aff5269a" - + "86a7a9531534f7da2e4c303d8a318a72" - + "1c3c0c95956809532fcf0e2449a6b525" - + "b16aedf5aa0de657ba637b39"; - ivHex[1] = "cafebabefacedbaddecaf888"; - aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef" - + "abaddad2"; - cHex[1] = "42831ec2217774244b7221b784d0d49c" - + "e3aa212f2c02a4e035c17e2329aca12e" - + "21d514b25466931c7d8f6a5aac84aa05" - + "1ba30b396a0aac973d58e091"; - tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47"; - - // NIST Case5 --------------------------------- - // key length: 16 bytes - // plain text length: 60 bytes - // iv length: 8 bytes - // aad length: 20 bytes - kHex[2] = "feffe9928665731c6d6a8f9467308308"; - pHex[2] = "d9313225f88406e5a55909c5aff5269a" - + "86a7a9531534f7da2e4c303d8a318a72" - + "1c3c0c95956809532fcf0e2449a6b525" - + "b16aedf5aa0de657ba637b39"; - ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits - aadHex[2]="feedfacedeadbeeffeedfacedeadbeef" - + "abaddad2"; - cHex[2] = "61353b4c2806934a777ff51fa22a4755" - + "699b2a714fcdc6f83766e5f97b6c7423" - + "73806900e49f24b22b097544d4896b42" - + "4989b5e1ebac0f07c23f4598"; - tHex[2] = "3612d2e79e3b0785561be14aaca2fccb"; - - // NIST Case6 --------------------------------- - // key length: 16 bytes - // plain text length: 60 bytes - // iv length: 60 bytes - // aad length: 20 bytes - kHex[3] = "feffe9928665731c6d6a8f9467308308"; - pHex[3] = "d9313225f88406e5a55909c5aff5269a" - + "86a7a9531534f7da2e4c303d8a318a72" - + "1c3c0c95956809532fcf0e2449a6b525" - + "b16aedf5aa0de657ba637b39"; - ivHex[3] = "9313225df88406e555909c5aff5269aa" - + "6a7a9538534f7da1e4c303d2a318a728" - + "c3c0c95156809539fcf0e2429a6b5254" - + "16aedbf5a0de6a57a637b39b"; // > 96bits - aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef" - + "abaddad2"; - cHex[3] = "8ce24998625615b603a033aca13fb894" - + "be9112a5c3a211a8ba262a3cca7e2ca7" - + "01e4a9a4fba43c90ccdcb281d48c7c6f" - + "d62875d2aca417034c34aee5"; - tHex[3] = "619cc5aefffe0bfa462af43c1699d050"; - } } diff --git a/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java index 3fd959b58..edd300e30 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java @@ -24,6 +24,7 @@ import javax.crypto.Cipher; +import org.apache.commons.crypto.utils.AES; import org.junit.jupiter.api.BeforeAll; @@ -31,18 +32,9 @@ public class JceCipherTest extends AbstractCipherTest { private static final int MAX_KEY_LEN_LOWER_BOUND = 256; - @Override - public void init() { - transformations = new String[] { - "AES/CBC/NoPadding", - "AES/CBC/PKCS5Padding", - "AES/CTR/NoPadding"}; - cipherClass = JCE_CIPHER_CLASSNAME; - } - @BeforeAll public static void checkJceUnlimitedStrength() throws NoSuchAlgorithmException { - final int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES"); + final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES.ALGORITHM); assertTrue(maxKeyLen >= MAX_KEY_LEN_LOWER_BOUND, String.format( "Testing requires support for an AES key length of %d, but " + @@ -51,4 +43,13 @@ public static void checkJceUnlimitedStrength() throws NoSuchAlgorithmException { "Strength Jurisdiction Policy Files.", MAX_KEY_LEN_LOWER_BOUND, maxKeyLen)); } + + @Override + public void init() { + transformations = new String[] { + AES.CBC_NO_PADDING, + AES.CBC_PKCS5_PADDING, + AES.CTR_NO_PADDING}; + cipherClass = JCE_CIPHER_CLASSNAME; + } } diff --git a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java index 69a7f3705..e326ad712 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java @@ -34,67 +34,49 @@ import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.crypto.utils.AES; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; public class OpenSslCipherTest extends AbstractCipherTest { + private ByteBuffer dummyBuffer() { + return ByteBuffer.allocateDirect(8); + } + @Override public void init() { assumeTrue(OpenSsl.getLoadingFailureReason() == null); transformations = new String[] { - "AES/CBC/NoPadding", - "AES/CBC/PKCS5Padding", - "AES/CTR/NoPadding"}; + AES.CBC_NO_PADDING, + AES.CBC_PKCS5_PADDING, + AES.CTR_NO_PADDING}; cipherClass = OPENSSL_CIPHER_CLASSNAME; } @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testInvalidPadding() { - assumeTrue(OpenSsl.getLoadingFailureReason() == null); - assertThrows(NoSuchPaddingException.class, - () -> OpenSsl.getInstance("AES/CTR/NoPadding2")); - } - - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testInvalidMode() { - assumeTrue(OpenSsl.getLoadingFailureReason() == null); - assertThrows(NoSuchAlgorithmException.class, - () -> OpenSsl.getInstance("AES/CTR2/NoPadding")); - } - - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testUpdateArguments() throws Exception { - assumeTrue(OpenSsl.getLoadingFailureReason() == null); - final OpenSsl cipher = OpenSsl - .getInstance("AES/CTR/NoPadding"); - assertNotNull(cipher); - - cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV)); - - // Require direct buffers - ByteBuffer input = ByteBuffer.allocate(1024); - ByteBuffer output = ByteBuffer.allocate(1024); + public void testCipherLifecycle() throws Exception { + try (OpenSslCipher cipher = new OpenSslCipher(new Properties(), AES.CTR_NO_PADDING)) { - final ByteBuffer finalInput = input; - final ByteBuffer finalOutput = output; - Exception ex = assertThrows(IllegalArgumentException.class, () -> cipher.update(finalInput, finalOutput)); - assertTrue(ex.getMessage().contains("Direct buffers are required")); + assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer())); + cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY), + new IvParameterSpec(IV)); + cipher.update(dummyBuffer(), dummyBuffer()); - // Output buffer length should be sufficient to store output data - input = ByteBuffer.allocateDirect(1024); - output = ByteBuffer.allocateDirect(1000); - final ByteBuffer finalInput1 = input; - final ByteBuffer finalOutput1 = output; - ex = assertThrows(ShortBufferException.class, () -> cipher.update(finalInput1, finalOutput1)); - assertTrue(ex.getMessage().contains("Output buffer is not sufficient")); + assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(new byte[1]), + new IvParameterSpec(IV))); + // Should keep working with previous init parameters. + cipher.update(dummyBuffer(), dummyBuffer()); + cipher.doFinal(dummyBuffer(), dummyBuffer()); + cipher.close(); + assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer())); + cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY), + new IvParameterSpec(IV)); + cipher.update(dummyBuffer(), dummyBuffer()); + } } @Test @@ -102,7 +84,7 @@ public void testUpdateArguments() throws Exception { public void testDoFinalArguments() throws Exception { assumeTrue(OpenSsl.getLoadingFailureReason() == null); final OpenSsl cipher = OpenSsl - .getInstance("AES/CTR/NoPadding"); + .getInstance(AES.CTR_NO_PADDING); assertNotNull(cipher); cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV)); @@ -118,72 +100,90 @@ public void testDoFinalArguments() throws Exception { @Override @Test @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testInvalidKey() throws Exception { + public void testInvalidIV() throws Exception { assumeTrue(OpenSsl.getLoadingFailureReason() == null); final OpenSsl cipher = OpenSsl - .getInstance("AES/CTR/NoPadding"); + .getInstance(AES.CTR_NO_PADDING); assertNotNull(cipher); - final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; - assertThrows(InvalidKeyException.class, - () -> cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new IvParameterSpec(IV))); + assertThrows(InvalidAlgorithmParameterException.class, + () -> cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(invalidIV))); } @Override @Test @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testInvalidIV() throws Exception { - assumeTrue(OpenSsl.getLoadingFailureReason() == null); - final OpenSsl cipher = OpenSsl - .getInstance("AES/CTR/NoPadding"); + public void testInvalidIVClass() throws Exception { + final OpenSsl cipher = OpenSsl.getInstance(AES.CTR_NO_PADDING); assertNotNull(cipher); - final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; assertThrows(InvalidAlgorithmParameterException.class, - () -> cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(invalidIV))); + () -> cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new GCMParameterSpec(IV.length, IV))); } @Override @Test @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testInvalidIVClass() throws Exception { - final OpenSsl cipher = OpenSsl.getInstance("AES/CTR/NoPadding"); + public void testInvalidKey() throws Exception { + assumeTrue(OpenSsl.getLoadingFailureReason() == null); + final OpenSsl cipher = OpenSsl + .getInstance(AES.CTR_NO_PADDING); assertNotNull(cipher); + final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; - assertThrows(InvalidAlgorithmParameterException.class, - () -> cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new GCMParameterSpec(IV.length, IV))); + assertThrows(InvalidKeyException.class, + () -> cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new IvParameterSpec(IV))); } @Test - public void testCipherLifecycle() throws Exception { - try (OpenSslCipher cipher = new OpenSslCipher(new Properties(), "AES/CTR/NoPadding")) { + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testInvalidMode() { + assumeTrue(OpenSsl.getLoadingFailureReason() == null); + assertThrows(NoSuchAlgorithmException.class, + () -> OpenSsl.getInstance("AES/CTR2/NoPadding")); + } - assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer())); - cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), - new IvParameterSpec(IV)); - cipher.update(dummyBuffer(), dummyBuffer()); + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testInvalidPadding() { + assumeTrue(OpenSsl.getLoadingFailureReason() == null); + assertThrows(NoSuchPaddingException.class, + () -> OpenSsl.getInstance("AES/CTR/NoPadding2")); + } - assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(new byte[1], "AES"), - new IvParameterSpec(IV))); - // Should keep working with previous init parameters. - cipher.update(dummyBuffer(), dummyBuffer()); - cipher.doFinal(dummyBuffer(), dummyBuffer()); - cipher.close(); + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testUpdateArguments() throws Exception { + assumeTrue(OpenSsl.getLoadingFailureReason() == null); + final OpenSsl cipher = OpenSsl + .getInstance(AES.CTR_NO_PADDING); + assertNotNull(cipher); - assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer())); - cipher.init(OpenSsl.ENCRYPT_MODE, new SecretKeySpec(KEY, "AES"), - new IvParameterSpec(IV)); - cipher.update(dummyBuffer(), dummyBuffer()); - } - } + cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV)); + + // Require direct buffers + ByteBuffer input = ByteBuffer.allocate(1024); + ByteBuffer output = ByteBuffer.allocate(1024); + + final ByteBuffer finalInput = input; + final ByteBuffer finalOutput = output; + Exception ex = assertThrows(IllegalArgumentException.class, () -> cipher.update(finalInput, finalOutput)); + assertTrue(ex.getMessage().contains("Direct buffers are required")); + + // Output buffer length should be sufficient to store output data + input = ByteBuffer.allocateDirect(1024); + output = ByteBuffer.allocateDirect(1000); + final ByteBuffer finalInput1 = input; + final ByteBuffer finalOutput1 = output; + ex = assertThrows(ShortBufferException.class, () -> cipher.update(finalInput1, finalOutput1)); + assertTrue(ex.getMessage().contains("Output buffer is not sufficient")); - private ByteBuffer dummyBuffer() { - return ByteBuffer.allocateDirect(8); } } diff --git a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCommonModeTest.java b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCommonModeTest.java new file mode 100644 index 000000000..585ce5e96 --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCommonModeTest.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package org.apache.commons.crypto.cipher; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class OpenSslCommonModeTest { + + @Test + public void testUpdateAAD() { + assertThrows(UnsupportedOperationException.class, () -> new OpenSslCommonMode(0, 0, 0).updateAAD(null)); + } +} diff --git a/src/test/java/org/apache/commons/crypto/cipher/TestData.java b/src/test/java/org/apache/commons/crypto/cipher/TestData.java index f0f55d4d0..17893fcbf 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/TestData.java +++ b/src/test/java/org/apache/commons/crypto/cipher/TestData.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.Map; +import org.apache.commons.crypto.utils.AES; + public class TestData { private static final String[] CBCNoPaddingTests = { @@ -139,9 +141,9 @@ public class TestData { private static final MaptestData = new HashMap<>(); static { - testData.put("AES/CBC/NoPadding", CBCNoPaddingTests); - testData.put("AES/CBC/PKCS5Padding", CBCPKCS5PaddingTests); - testData.put("AES/CTR/NoPadding", cipherCTRTests); + testData.put(AES.CBC_NO_PADDING, CBCNoPaddingTests); + testData.put(AES.CBC_PKCS5_PADDING, CBCPKCS5PaddingTests); + testData.put(AES.CTR_NO_PADDING, cipherCTRTests); } public static String[] getTestData(final String transformation) { diff --git a/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java b/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java index 15afb8dbe..8519e2d71 100644 --- a/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java +++ b/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java @@ -28,6 +28,7 @@ import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.cipher.CryptoCipherFactory.CipherProvider; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; /** @@ -35,15 +36,25 @@ */ public class CipherByteArrayExample { + /** + * Converts String to UTF8 bytes + * + * @param input the input string + * @return UTF8 bytes + */ + private static byte[] getUTF8Bytes(final String input) { + return input.getBytes(StandardCharsets.UTF_8); + } + public static void main(final String[] args) throws Exception { - final SecretKeySpec key = new SecretKeySpec(getUTF8Bytes("1234567890123456"), "AES"); + final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456")); final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456")); final Properties properties = new Properties(); properties.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.OPENSSL.getClassName()); // Creates a CryptoCipher instance with the transformation and properties. - final String transform = "AES/CBC/PKCS5Padding"; + final String transform = AES.CBC_PKCS5_PADDING; byte[] output; int updateBytes; int finalBytes; @@ -84,14 +95,4 @@ public static void main(final String[] args) throws Exception { } } - /** - * Converts String to UTF8 bytes - * - * @param input the input string - * @return UTF8 bytes - */ - private static byte[] getUTF8Bytes(final String input) { - return input.getBytes(StandardCharsets.UTF_8); - } - } diff --git a/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java b/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java index ebb4ed9b4..4f028b9ab 100644 --- a/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java +++ b/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java @@ -27,6 +27,7 @@ import javax.crypto.spec.SecretKeySpec; import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.Utils; /** @@ -34,12 +35,35 @@ */ public class CipherByteBufferExample { + /** + * Converts ByteBuffer to String + * + * @param buffer input byte buffer + * @return the converted string + */ + private static String asString(final ByteBuffer buffer) { + final ByteBuffer copy = buffer.duplicate(); + final byte[] bytes = new byte[copy.remaining()]; + copy.get(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } + + /** + * Converts String to UTF8 bytes + * + * @param input the input string + * @return UTF8 bytes + */ + private static byte[] getUTF8Bytes(final String input) { + return input.getBytes(StandardCharsets.UTF_8); + } + public static void main(final String[] args) throws Exception { - final SecretKeySpec key = new SecretKeySpec(getUTF8Bytes("1234567890123456"), "AES"); + final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456")); final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456")); final Properties properties = new Properties(); //Creates a CryptoCipher instance with the transformation and properties. - final String transform = "AES/CBC/PKCS5Padding"; + final String transform = AES.CBC_PKCS5_PADDING; final ByteBuffer outBuffer; final int bufferSize = 1024; final int updateBytes; @@ -54,7 +78,7 @@ public static void main(final String[] args) throws Exception { // Show the data is there System.out.println("inBuffer=" + asString(inBuffer)); - // Initializes the cipher with ENCRYPT_MODE,key and iv. + // Initializes the cipher with ENCRYPT_MODE, key and iv. encipher.init(Cipher.ENCRYPT_MODE, key, iv); // Continues a multiple-part encryption/decryption operation for byte buffer. updateBytes = encipher.update(inBuffer, outBuffer); @@ -81,27 +105,4 @@ public static void main(final String[] args) throws Exception { } } - /** - * Converts String to UTF8 bytes - * - * @param input the input string - * @return UTF8 bytes - */ - private static byte[] getUTF8Bytes(final String input) { - return input.getBytes(StandardCharsets.UTF_8); - } - - /** - * Converts ByteBuffer to String - * - * @param buffer input byte buffer - * @return the converted string - */ - private static String asString(final ByteBuffer buffer) { - final ByteBuffer copy = buffer.duplicate(); - final byte[] bytes = new byte[copy.remaining()]; - copy.get(bytes); - return new String(bytes, StandardCharsets.UTF_8); - } - } diff --git a/src/test/java/org/apache/commons/crypto/examples/StreamExample.java b/src/test/java/org/apache/commons/crypto/examples/StreamExample.java index aeef592d1..75df626fa 100644 --- a/src/test/java/org/apache/commons/crypto/examples/StreamExample.java +++ b/src/test/java/org/apache/commons/crypto/examples/StreamExample.java @@ -30,17 +30,28 @@ import org.apache.commons.crypto.stream.CryptoInputStream; import org.apache.commons.crypto.stream.CryptoOutputStream; +import org.apache.commons.crypto.utils.AES; /** * Example showing how to use stream encryption and decryption. */ public class StreamExample { + /** + * Converts String to UTF8 bytes + * + * @param input the input string + * @return UTF8 bytes + */ + private static byte[] getUTF8Bytes(final String input) { + return input.getBytes(StandardCharsets.UTF_8); + } + public static void main(final String []args) throws IOException { - final SecretKeySpec key = new SecretKeySpec(getUTF8Bytes("1234567890123456"),"AES"); + final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456")); final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456")); final Properties properties = new Properties(); - final String transform = "AES/CBC/PKCS5Padding"; + final String transform = AES.CBC_PKCS5_PADDING; final String input = "hello world!"; //Encryption with CryptoOutputStream. @@ -69,14 +80,4 @@ public static void main(final String []args) throws IOException { } } - /** - * Converts String to UTF8 bytes - * - * @param input the input string - * @return UTF8 bytes - */ - private static byte[] getUTF8Bytes(final String input) { - return input.getBytes(StandardCharsets.UTF_8); - } - } diff --git a/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java index 0922fc342..99e8d5fb9 100644 --- a/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java @@ -24,6 +24,7 @@ import org.apache.commons.crypto.cipher.AbstractCipherTest; import org.apache.commons.crypto.stream.AbstractCipherStreamTest; +import org.apache.commons.crypto.utils.Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -37,16 +38,6 @@ public void init() { assumeTrue(OpenSslJna.isEnabled()); } - /** Test skip. */ - @Override - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testSkip() throws Exception { - doSkipTest(CIPHER_OPENSSL_JNA, false); - - doSkipTest(CIPHER_OPENSSL_JNA, true); - } - /** Test byte buffer read with different buffer size. */ @Override @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) @@ -75,10 +66,42 @@ public void testReadWrite() throws Exception { doReadWriteTest(count, CIPHER_OPENSSL_JNA, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv); // Overflow test, IV: xx xx xx xx xx xx xx xx ff ff ff ff ff ff ff ff for (int i = 0; i < 8; i++) { - iv[8 + i] = (byte) 0xff; + iv[8 + i] = (byte) Utils.BYTE_MASK; } doReadWriteTest(count, CIPHER_OPENSSL_JNA, CIPHER_OPENSSL_JNA, iv); doReadWriteTest(count, AbstractCipherTest.JCE_CIPHER_CLASSNAME, CIPHER_OPENSSL_JNA, iv); doReadWriteTest(count, CIPHER_OPENSSL_JNA, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv); } + + /** Test skip. */ + @Override + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testSkip() throws Exception { + doSkipTest(CIPHER_OPENSSL_JNA, false); + + doSkipTest(CIPHER_OPENSSL_JNA, true); + } + + @Override + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testExceptions() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doExceptionTest(CIPHER_OPENSSL_JNA, baos, false); + + doExceptionTest(CIPHER_OPENSSL_JNA, baos, true); + } + + @Override + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testFieldGetters() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doFieldGetterTest(CIPHER_OPENSSL_JNA, baos, false); + + doFieldGetterTest(CIPHER_OPENSSL_JNA, baos, true); + } + + } diff --git a/src/test/java/org/apache/commons/crypto/jna/CbcNoPaddingCipherJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/CbcNoPaddingCipherJnaStreamTest.java index deba4b8d4..69439e37f 100644 --- a/src/test/java/org/apache/commons/crypto/jna/CbcNoPaddingCipherJnaStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/CbcNoPaddingCipherJnaStreamTest.java @@ -17,11 +17,12 @@ */ package org.apache.commons.crypto.jna; +import org.apache.commons.crypto.utils.AES; public class CbcNoPaddingCipherJnaStreamTest extends AbstractCipherJnaStreamTest { @Override public void setUp() { - transformation = "AES/CBC/NoPadding"; + transformation = AES.CBC_NO_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/jna/CbcPkcs5PaddingCipherJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/CbcPkcs5PaddingCipherJnaStreamTest.java index 144dcb521..2d78e2a88 100644 --- a/src/test/java/org/apache/commons/crypto/jna/CbcPkcs5PaddingCipherJnaStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/CbcPkcs5PaddingCipherJnaStreamTest.java @@ -17,12 +17,13 @@ */ package org.apache.commons.crypto.jna; +import org.apache.commons.crypto.utils.AES; public class CbcPkcs5PaddingCipherJnaStreamTest extends AbstractCipherJnaStreamTest { @Override public void setUp() { - transformation = "AES/CBC/PKCS5Padding"; + transformation = AES.CBC_PKCS5_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/jna/CryptoJnaBenchmark.java b/src/test/java/org/apache/commons/crypto/jna/CryptoJnaBenchmark.java index a79227aeb..9c73d938f 100644 --- a/src/test/java/org/apache/commons/crypto/jna/CryptoJnaBenchmark.java +++ b/src/test/java/org/apache/commons/crypto/jna/CryptoJnaBenchmark.java @@ -24,10 +24,10 @@ import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Mode; /** * Basic Benchmark to compare creation and runtimes for the different implementations. @@ -47,23 +47,23 @@ public class CryptoJnaBenchmark extends AbstractBenchmark { @Benchmark - public void RandomTestOpensslJNA() throws Exception { - random(RANDOM_OPENSSL_JNA); + public void CipherCreateOpensslJna() throws Exception { + getCipher(CIPHER_OPENSSL_JNA); } @Benchmark - public void RandomCreateOpensslJNA() throws Exception { - getRandom(RANDOM_OPENSSL_JNA); + public void CipherTestOpensslJna() throws Exception { + encipher(CIPHER_OPENSSL_JNA); } @Benchmark - public void CipherCreateOpensslJna() throws Exception { - getCipher(CIPHER_OPENSSL_JNA); + public void RandomCreateOpensslJNA() throws Exception { + getRandom(RANDOM_OPENSSL_JNA); } @Benchmark - public void CipherTestOpensslJna() throws Exception { - encipher(CIPHER_OPENSSL_JNA); + public void RandomTestOpensslJNA() throws Exception { + random(RANDOM_OPENSSL_JNA); } } diff --git a/src/test/java/org/apache/commons/crypto/jna/CtrCryptoJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/CtrCryptoJnaStreamTest.java index 137afb427..e82b1eefe 100644 --- a/src/test/java/org/apache/commons/crypto/jna/CtrCryptoJnaStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/CtrCryptoJnaStreamTest.java @@ -17,12 +17,13 @@ */ package org.apache.commons.crypto.jna; +import org.apache.commons.crypto.utils.AES; public class CtrCryptoJnaStreamTest extends AbstractCipherJnaStreamTest { @Override public void setUp() { - transformation = "AES/CTR/NoPadding"; + transformation = AES.CTR_NO_PADDING; } diff --git a/src/test/java/org/apache/commons/crypto/jna/CtrNoPaddingCipherJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/CtrNoPaddingCipherJnaStreamTest.java index 863fc1b61..123cd7c1f 100644 --- a/src/test/java/org/apache/commons/crypto/jna/CtrNoPaddingCipherJnaStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/CtrNoPaddingCipherJnaStreamTest.java @@ -17,12 +17,13 @@ */ package org.apache.commons.crypto.jna; +import org.apache.commons.crypto.utils.AES; public class CtrNoPaddingCipherJnaStreamTest extends AbstractCipherJnaStreamTest { @Override public void setUp() { - transformation = "AES/CTR/NoPadding"; + transformation = AES.CTR_NO_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCipherTest.java b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCipherTest.java index 37aab98e8..6bdf9ef90 100644 --- a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCipherTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.apache.commons.crypto.cipher.AbstractCipherTest; +import org.apache.commons.crypto.utils.AES; public class OpenSslJnaCipherTest extends AbstractCipherTest { @@ -28,9 +29,9 @@ public class OpenSslJnaCipherTest extends AbstractCipherTest { public void init() { assumeTrue(OpenSslJna.isEnabled()); transformations = new String[] { - "AES/CBC/NoPadding", - "AES/CBC/PKCS5Padding", - "AES/CTR/NoPadding" + AES.CBC_NO_PADDING, + AES.CBC_PKCS5_PADDING, + AES.CTR_NO_PADDING }; cipherClass = OpenSslJnaCipher.class.getName(); } diff --git a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java index 48234fad6..9d0f30807 100644 --- a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java @@ -1,23 +1,22 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.jna; - import static org.junit.jupiter.api.Assertions.assertTrue; import java.security.GeneralSecurityException; @@ -31,22 +30,18 @@ public class OpenSslJnaCryptoRandomTest extends AbstractRandomTest { - @BeforeEach - public void init() { - Assumptions.assumeTrue(OpenSslJna.isEnabled()); - } - @Override public CryptoRandom getCryptoRandom() throws GeneralSecurityException { final Properties props = new Properties(); - props.setProperty( - CryptoRandomFactory.CLASSES_KEY, - OpenSslJnaCryptoRandom.class.getName()); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, OpenSslJnaCryptoRandom.class.getName()); final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - assertTrue( - random instanceof OpenSslJnaCryptoRandom, - "The CryptoRandom should be: " + OpenSslJnaCryptoRandom.class.getName()); + assertTrue(random instanceof OpenSslJnaCryptoRandom, "The CryptoRandom should be: " + OpenSslJnaCryptoRandom.class.getName()); return random; } + @BeforeEach + public void init() { + Assumptions.assumeTrue(OpenSslJna.isEnabled()); + } + } diff --git a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaTest.java b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaTest.java new file mode 100644 index 000000000..96911e584 --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaTest.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +package org.apache.commons.crypto.jna; + +import org.junit.jupiter.api.Test; + +public class OpenSslJnaTest { + + @Test + public void testMain() throws Throwable { + OpenSslJna.main(new String[0]); + } +} diff --git a/src/test/java/org/apache/commons/crypto/jna/OpenSslNativeJnaTest.java b/src/test/java/org/apache/commons/crypto/jna/OpenSslNativeJnaTest.java index 8ae09496e..60bcea155 100644 --- a/src/test/java/org/apache/commons/crypto/jna/OpenSslNativeJnaTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/OpenSslNativeJnaTest.java @@ -18,18 +18,20 @@ package org.apache.commons.crypto.jna; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; public class OpenSslNativeJnaTest { @Test - public void test() { + public void test(final TestReporter reporter) { if (OpenSslJna.isEnabled()) { - System.out.println("** INFO: JNA is using: " + OpenSslJna.OpenSSLVersion(0)); + reporter.publishEntry(String.format("JNA loaded OK for lib version 0x%x: ", OpenSslNativeJna.VERSION)); } else { - System.out.printf("** WARN: Could not enable JNA; detected lib VERSION 0x%x: %s%n", OpenSslNativeJna.VERSION, - OpenSslJna.initialisationError().getMessage()); - // OpenSslJna.initialisationError().printStackTrace(); + reporter.publishEntry(String.format("** ERROR: JNA NOT loaded OK for lib version 0x%x: ", OpenSslNativeJna.VERSION)); } + assertTrue(true, "Test OK"); // dummy for now } } diff --git a/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java b/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java index 27bc25911..ab47dd432 100644 --- a/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java +++ b/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java @@ -13,7 +13,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto.jna; @@ -24,7 +23,6 @@ import org.junit.jupiter.api.Test; /** - * */ public class PositionedCryptoInputStreamJnaTest extends PositionedCryptoInputStreamTest { @@ -34,7 +32,12 @@ public void init() { } @Test - public void doTest() throws Exception { + @Override // Don't load JNI! + public void testJNI() throws Exception { + } + + @Test + public void testCipher() throws Exception { testCipher(OpenSslJnaCipher.class.getName()); } diff --git a/src/test/java/org/apache/commons/crypto/random/AbstractRandom.java b/src/test/java/org/apache/commons/crypto/random/AbstractRandom.java index da1ac0492..6207bcf3e 100644 --- a/src/test/java/org/apache/commons/crypto/random/AbstractRandom.java +++ b/src/test/java/org/apache/commons/crypto/random/AbstractRandom.java @@ -13,19 +13,17 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto.random; import java.util.Properties; /** - * For testing class creation + * Only provides a constructor. */ abstract class AbstractRandom implements CryptoRandom { - // Should fail to instantiate, as it is an abstract class - AbstractRandom(final Properties props) { - + AbstractRandom(final Properties properties) { + // empty } } diff --git a/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java b/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java index ee3e6d397..dca853a60 100644 --- a/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java +++ b/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java @@ -1,20 +1,20 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.random; import java.lang.Thread.State; @@ -29,8 +29,21 @@ public abstract class AbstractRandomTest { - public abstract CryptoRandom getCryptoRandom() - throws GeneralSecurityException; + /** + * Test will timeout if secure random implementation always returns a constant value. + */ + private void checkRandomBytes(final CryptoRandom random, final int len) { + final byte[] bytes = new byte[len]; + final byte[] bytes1 = new byte[len]; + random.nextBytes(bytes); + random.nextBytes(bytes1); + + while (Arrays.equals(bytes1, new byte[len]) || Arrays.equals(bytes, bytes1)) { + random.nextBytes(bytes1); + } + } + + public abstract CryptoRandom getCryptoRandom() throws GeneralSecurityException; @Test @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) @@ -72,19 +85,4 @@ public void testRandomBytesMultiThreaded() throws Exception { } } - - /** - * Test will timeout if secure random implementation always returns a - * constant value. - */ - private void checkRandomBytes(final CryptoRandom random, final int len) { - final byte[] bytes = new byte[len]; - final byte[] bytes1 = new byte[len]; - random.nextBytes(bytes); - random.nextBytes(bytes1); - - while (Arrays.equals(bytes1, new byte[len]) || Arrays.equals(bytes, bytes1)) { - random.nextBytes(bytes1); - } - } } diff --git a/src/test/java/org/apache/commons/crypto/random/CryptoRandomFactoryTest.java b/src/test/java/org/apache/commons/crypto/random/CryptoRandomFactoryTest.java index c27341c13..111948312 100644 --- a/src/test/java/org/apache/commons/crypto/random/CryptoRandomFactoryTest.java +++ b/src/test/java/org/apache/commons/crypto/random/CryptoRandomFactoryTest.java @@ -1,20 +1,20 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.random; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -22,133 +22,136 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.security.GeneralSecurityException; import java.util.Properties; import org.junit.jupiter.api.Test; - public class CryptoRandomFactoryTest { @Test - public void testNull() { - assertThrows(NullPointerException.class, - () -> CryptoRandomFactory.getCryptoRandom(null)); + public void testAbstractRandom() { + final Properties properties = new Properties(); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, AbstractRandom.class.getName()); + final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(properties)); + final String message = ex.getMessage(); + assertTrue(message.contains("InstantiationException"), message); } @Test - public void testEmpty() throws Exception { - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, ""); - CryptoRandomFactory.getCryptoRandom(props); + public void testDefaultRandom() throws GeneralSecurityException, IOException { + final Properties properties = new Properties(); + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(properties)) { + final String name = random.getClass().getName(); + if (OpenSslCryptoRandom.isNativeCodeEnabled()) { + assertEquals(OpenSslCryptoRandom.class.getName(), name); + } else { + assertEquals(JavaCryptoRandom.class.getName(), name); + } + } } - @Test - public void testDefaultRandom() throws GeneralSecurityException { - final Properties props = new Properties(); - final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - final String name = random.getClass().getName(); - if (OpenSslCryptoRandom.isNativeCodeEnabled()) { - assertEquals(OpenSslCryptoRandom.class.getName(), name); - } else { - assertEquals(JavaCryptoRandom.class.getName(), name); + public void testDefaultRandomClass() throws GeneralSecurityException, IOException { + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom()) { + assertEquals(OpenSslCryptoRandom.class.getName(), random.getClass().getName()); } } @Test - public void testGetOSRandom() throws GeneralSecurityException { - // Windows does not have a /dev/random device - assumeTrue(!System.getProperty("os.name").contains("Windows")); - final Properties props = new Properties(); - props.setProperty( - CryptoRandomFactory.CLASSES_KEY, - CryptoRandomFactory.RandomProvider.OS.getClassName()); - final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - - assertEquals(OsCryptoRandom.class.getName(), random.getClass() - .getName()); + public void testDummmyRandom() { + final Properties properties = new Properties(); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, NoopRandom.class.getName()); + final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(properties)); + final String message = ex.getMessage(); + assertTrue(message.contains("NoSuchMethodException"), message); } @Test - public void testFullClassName() throws GeneralSecurityException { - final Properties props = new Properties(); - props.setProperty( - CryptoRandomFactory.CLASSES_KEY, - JavaCryptoRandom.class.getName()); - final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); + public void testEmpty() throws Exception { + final Properties properties = new Properties(); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, ""); + CryptoRandomFactory.getCryptoRandom(properties).close(); + } - assertEquals(JavaCryptoRandom.class.getName(), random.getClass() - .getName()); + @Test + public void testExceptionInInitializerErrorRandom() throws GeneralSecurityException, IOException { + final Properties properties = new Properties(); + String classes = ExceptionInInitializerErrorRandom.class.getName().concat(",") + .concat(CryptoRandomFactory.RandomProvider.JAVA.getClassName()); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, classes); + // Invoke 3 times to test the reentrancy of the method in the scenario of class initialization failure. + for (int i = 0; i < 3; i++) { + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(properties)) { + assertEquals(JavaCryptoRandom.class.getName(), random.getClass().getName()); + } + } } @Test - public void testInvalidRandom() { + public void testFailingRandom() { final Properties properties = new Properties(); - properties.setProperty( - CryptoRandomFactory.CLASSES_KEY, - "InvalidCipherName"); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, FailingRandom.class.getName()); + final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(properties)); - assertThrows(GeneralSecurityException.class, - () -> CryptoRandomFactory.getCryptoRandom(properties)); + Throwable cause = ex.getCause(); + assertEquals(IllegalArgumentException.class, cause.getClass()); + cause = cause.getCause(); + assertEquals(InvocationTargetException.class, cause.getClass()); + cause = cause.getCause(); + assertEquals(UnsatisfiedLinkError.class, cause.getClass()); } @Test - public void testInvalidRandomClass() throws GeneralSecurityException { - final Properties properties = new Properties(); - properties.setProperty( - "org.apache.commons.crypto.cipher", - "OpenSsl"); - final CryptoRandom rand = CryptoRandomFactory.getCryptoRandom(properties); - assertEquals(OpenSslCryptoRandom.class.getName(), rand.getClass().getName()); + public void testFullClassName() throws GeneralSecurityException, IOException { + final Properties props = new Properties(); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, JavaCryptoRandom.class.getName()); + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props)) { + assertEquals(JavaCryptoRandom.class.getName(), random.getClass().getName()); + } } @Test - public void testDefaultRandomClass() throws GeneralSecurityException { - final CryptoRandom rand = CryptoRandomFactory.getCryptoRandom(); - assertEquals(OpenSslCryptoRandom.class.getName(), rand.getClass().getName()); + public void testGetOSRandom() throws GeneralSecurityException, IOException { + // Windows does not have a /dev/random device + assumeTrue(!System.getProperty("os.name").contains("Windows")); + final Properties properties = new Properties(); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, CryptoRandomFactory.RandomProvider.OS.getClassName()); + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(properties)) { + assertEquals(OsCryptoRandom.class.getName(), random.getClass().getName()); + } } @Test - public void testAbstractRandom() { - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, AbstractRandom.class.getName()); - final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); - final String message = ex.getMessage(); - assertTrue(message.contains("InstantiationException"), message); + public void testInvalidRandom() { + final Properties properties = new Properties(); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, "InvalidCipherName"); + + assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(properties)); } @Test - public void testDummmyRandom() { - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, DummyRandom.class.getName()); - final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); - final String message = ex.getMessage(); - assertTrue(message.contains("NoSuchMethodException"), message); + public void testInvalidRandomClass() throws GeneralSecurityException, IOException { + final Properties properties = new Properties(); + properties.setProperty("org.apache.commons.crypto.cipher", "OpenSsl"); + try (final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(properties)) { + assertEquals(OpenSslCryptoRandom.class.getName(), random.getClass().getName()); + } } @Test public void testNoClasses() { - final Properties props = new Properties(); + final Properties properties = new Properties(); // An empty string currently means use the default // However the splitter drops empty fields - props.setProperty(CryptoRandomFactory.CLASSES_KEY, ","); - assertThrows(IllegalArgumentException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); + properties.setProperty(CryptoRandomFactory.CLASSES_KEY, ","); + assertThrows(IllegalArgumentException.class, () -> CryptoRandomFactory.getCryptoRandom(properties)); } @Test - public void testFailingRandom() { - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, FailingRandom.class.getName()); - final Exception ex = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); - - Throwable cause = ex.getCause(); - assertEquals(IllegalArgumentException.class, cause.getClass()); - cause = cause.getCause(); - assertEquals(InvocationTargetException.class, cause.getClass()); - cause = cause.getCause(); - assertEquals(UnsatisfiedLinkError.class, cause.getClass()); + public void testNull() { + assertThrows(NullPointerException.class, () -> CryptoRandomFactory.getCryptoRandom(null)); } - } diff --git a/src/test/java/org/apache/commons/crypto/random/ExceptionInInitializerErrorRandom.java b/src/test/java/org/apache/commons/crypto/random/ExceptionInInitializerErrorRandom.java new file mode 100644 index 000000000..08bf01282 --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/random/ExceptionInInitializerErrorRandom.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +package org.apache.commons.crypto.random; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * Simulates scenarios where {@link OpenSslCryptoRandom} fails in the static code block {@code checkNative()} or + * {@code !OpenSslCryptoRandomNative.nextRandBytes(new byte[1])} is false. + */ +public class ExceptionInInitializerErrorRandom implements CryptoRandom { + + static { + try { + check(); + } catch (final GeneralSecurityException e) { + throw new IllegalStateException(e); + } + } + + private static void check() throws GeneralSecurityException { + throw new GeneralSecurityException("ExceptionInInitializerErrorRandom init failed"); + } + + @Override + public void close() throws IOException { + // empty + } + + @Override + public void nextBytes(byte[] bytes) { + // empty + } +} diff --git a/src/test/java/org/apache/commons/crypto/random/FailingRandom.java b/src/test/java/org/apache/commons/crypto/random/FailingRandom.java index 6600bc7c9..6782f4954 100644 --- a/src/test/java/org/apache/commons/crypto/random/FailingRandom.java +++ b/src/test/java/org/apache/commons/crypto/random/FailingRandom.java @@ -13,26 +13,27 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto.random; import java.util.Properties; -class FailingRandom implements CryptoRandom { +final class FailingRandom implements CryptoRandom { + + public static native void NoSuchMethod(); - // Should fail with NoSuchMethodException + /** Should fail with NoSuchMethodException. */ FailingRandom(final Properties props) { NoSuchMethod(); } @Override public void close() { + // empty } @Override public void nextBytes(final byte[] bytes) { + // empty } - - public static native void NoSuchMethod(); } diff --git a/src/test/java/org/apache/commons/crypto/random/JavaCryptoRandomTest.java b/src/test/java/org/apache/commons/crypto/random/JavaCryptoRandomTest.java index 0f942c2e7..6ad26d4aa 100644 --- a/src/test/java/org/apache/commons/crypto/random/JavaCryptoRandomTest.java +++ b/src/test/java/org/apache/commons/crypto/random/JavaCryptoRandomTest.java @@ -1,20 +1,20 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.random; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,13 +27,9 @@ public class JavaCryptoRandomTest extends AbstractRandomTest { @Override public CryptoRandom getCryptoRandom() throws GeneralSecurityException { final Properties props = new Properties(); - props.setProperty( - CryptoRandomFactory.CLASSES_KEY, - JavaCryptoRandom.class.getName()); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, JavaCryptoRandom.class.getName()); final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - assertTrue( - random instanceof JavaCryptoRandom, - "The CryptoRandom should be: " + JavaCryptoRandom.class.getName()); + assertTrue(random instanceof JavaCryptoRandom, "The CryptoRandom should be: " + JavaCryptoRandom.class.getName()); return random; } diff --git a/src/test/java/org/apache/commons/crypto/random/DummyRandom.java b/src/test/java/org/apache/commons/crypto/random/NoopRandom.java similarity index 85% rename from src/test/java/org/apache/commons/crypto/random/DummyRandom.java rename to src/test/java/org/apache/commons/crypto/random/NoopRandom.java index 4d1b4aca4..b2ccfa554 100644 --- a/src/test/java/org/apache/commons/crypto/random/DummyRandom.java +++ b/src/test/java/org/apache/commons/crypto/random/NoopRandom.java @@ -13,24 +13,25 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.apache.commons.crypto.random; -class DummyRandom implements CryptoRandom { - - // Should fail with NoSuchMethodException - DummyRandom() { +final class NoopRandom implements CryptoRandom { + /** Should fail with NoSuchMethodException. */ + NoopRandom() { + // empty } @Override public void close() { + // empty } @Override public void nextBytes(final byte[] bytes) { + // empty } } diff --git a/src/test/java/org/apache/commons/crypto/random/OpenSslCryptoRandomTest.java b/src/test/java/org/apache/commons/crypto/random/OpenSslCryptoRandomTest.java index a99ffdfd9..a9f86e248 100644 --- a/src/test/java/org/apache/commons/crypto/random/OpenSslCryptoRandomTest.java +++ b/src/test/java/org/apache/commons/crypto/random/OpenSslCryptoRandomTest.java @@ -1,23 +1,22 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.random; - import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -32,13 +31,9 @@ public class OpenSslCryptoRandomTest extends AbstractRandomTest { public CryptoRandom getCryptoRandom() throws GeneralSecurityException { assumeTrue(Crypto.isNativeCodeLoaded()); final Properties props = new Properties(); - props.setProperty( - CryptoRandomFactory.CLASSES_KEY, - OpenSslCryptoRandom.class.getName()); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, OpenSslCryptoRandom.class.getName()); final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - assertTrue( - random instanceof OpenSslCryptoRandom, - "The CryptoRandom should be: " + OpenSslCryptoRandom.class.getName()); + assertTrue(random instanceof OpenSslCryptoRandom, "The CryptoRandom should be: " + OpenSslCryptoRandom.class.getName()); return random; } diff --git a/src/test/java/org/apache/commons/crypto/random/OsCryptoRandomTest.java b/src/test/java/org/apache/commons/crypto/random/OsCryptoRandomTest.java index b3b404482..4cb742f2c 100644 --- a/src/test/java/org/apache/commons/crypto/random/OsCryptoRandomTest.java +++ b/src/test/java/org/apache/commons/crypto/random/OsCryptoRandomTest.java @@ -32,33 +32,33 @@ public class OsCryptoRandomTest extends AbstractRandomTest { - @Override - public CryptoRandom getCryptoRandom() throws GeneralSecurityException { - // Windows does not have a /dev/random device - assumeTrue(!System.getProperty("os.name").contains("Windows")); - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, OsCryptoRandom.class.getName()); - final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); - assertTrue(random instanceof OsCryptoRandom, "The CryptoRandom should be: " + OsCryptoRandom.class.getName()); - return random; - } + @Override + public CryptoRandom getCryptoRandom() throws GeneralSecurityException { + // Windows does not have a /dev/random device + assumeTrue(!System.getProperty("os.name").contains("Windows")); + final Properties props = new Properties(); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, OsCryptoRandom.class.getName()); + final CryptoRandom random = CryptoRandomFactory.getCryptoRandom(props); + assertTrue(random instanceof OsCryptoRandom, "The CryptoRandom should be: " + OsCryptoRandom.class.getName()); + return random; + } - @Test - public void testInvalidRandom() { - final Properties props = new Properties(); - props.setProperty(CryptoRandomFactory.CLASSES_KEY, OsCryptoRandom.class.getName()); - // Invalid device - props.setProperty(CryptoRandomFactory.DEVICE_FILE_PATH_KEY, ""); - final Exception e = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); - Throwable cause; - cause = e.getCause(); - assertEquals(IllegalArgumentException.class, cause.getClass()); - cause = cause.getCause(); - assertEquals(InvocationTargetException.class, cause.getClass()); - cause = cause.getCause(); - assertEquals(IllegalArgumentException.class, cause.getClass()); - cause = cause.getCause(); - assertEquals(FileNotFoundException.class, cause.getClass()); + @Test + public void testInvalidRandom() { + final Properties props = new Properties(); + props.setProperty(CryptoRandomFactory.CLASSES_KEY, OsCryptoRandom.class.getName()); + // Invalid device + props.setProperty(CryptoRandomFactory.DEVICE_FILE_PATH_KEY, ""); + final Exception e = assertThrows(GeneralSecurityException.class, () -> CryptoRandomFactory.getCryptoRandom(props)); + Throwable cause; + cause = e.getCause(); + assertEquals(IllegalArgumentException.class, cause.getClass()); + cause = cause.getCause(); + assertEquals(InvocationTargetException.class, cause.getClass()); + cause = cause.getCause(); + assertEquals(IllegalArgumentException.class, cause.getClass()); + cause = cause.getCause(); + assertEquals(FileNotFoundException.class, cause.getClass()); - } + } } diff --git a/src/test/java/org/apache/commons/crypto/stream/AbstractCipherStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/AbstractCipherStreamTest.java index 600dfae15..3263c62d2 100644 --- a/src/test/java/org/apache/commons/crypto/stream/AbstractCipherStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/AbstractCipherStreamTest.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -44,18 +45,21 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import org.apache.commons.crypto.Crypto; import org.apache.commons.crypto.cipher.AbstractCipherTest; import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.ReflectionUtils; +import org.apache.commons.crypto.utils.Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; public abstract class AbstractCipherStreamTest { + protected static int defaultBufferSize = 8192; + protected static int smallBufferSize = 1024; protected final int dataLen = 20000; protected final byte[] data = new byte[dataLen]; protected byte[] encData; @@ -63,12 +67,12 @@ public abstract class AbstractCipherStreamTest { protected byte[] key = new byte[16]; protected byte[] iv = new byte[16]; protected int count = 10000; - protected static int defaultBufferSize = 8192; - protected static int smallBufferSize = 1024; protected String transformation; - public abstract void setUp() throws IOException; + public void assumeJniPresence(final String cipherClass) { + assumeFalse(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()); + } @BeforeEach public void before() throws Exception { @@ -80,294 +84,249 @@ public void before() throws Exception { prepareData(); } - /** Test skip. */ - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testSkip() throws Exception { - doSkipTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, false); - doSkipTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, false); - - doSkipTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true); - doSkipTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true); - } - - /** Test byte buffer read with different buffer size. */ - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testByteBufferRead() throws Exception { - doByteBufferRead(AbstractCipherTest.JCE_CIPHER_CLASSNAME, false); - doByteBufferRead(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, false); - - doByteBufferRead(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true); - doByteBufferRead(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true); - } - - /** Test byte buffer write. */ - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testByteBufferWrite() throws Exception { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doByteBufferWrite(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); - doByteBufferWrite(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); - - doByteBufferWrite(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); - doByteBufferWrite(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); - } - - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testExceptions() throws Exception { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doExceptionTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); - doExceptionTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); - - doExceptionTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); - doExceptionTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); - } - - @Test - @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) - public void testFieldGetters() throws Exception { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doFieldGetterTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); - doFieldGetterTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); - - doFieldGetterTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); - doFieldGetterTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); + private void byteBufferFinalReadCheck(final InputStream in, final ByteBuffer buf, final int bufPos) + throws Exception { + buf.position(bufPos); + int len = 0; + int n = 0; + do { + n = ((ReadableByteChannel) in).read(buf); + len += n; + } while (n > 0); + buf.rewind(); + final byte[] readData = new byte[len + 1]; + buf.get(readData); + final byte[] expectedData = new byte[len + 1]; + System.arraycopy(data, 0, expectedData, 0, len + 1); + assertArrayEquals(readData, expectedData); } - protected void doSkipTest(final String cipherClass, final boolean withChannel) - throws IOException { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } - try (@SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoInputStream. - InputStream in = newCryptoInputStream( - new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { - final byte[] result = new byte[dataLen]; - final int n1 = readAll(in, result, 0, dataLen / 5); - - assertEquals(in.skip(0), 0); - - long skipped = in.skip(dataLen / 5); - final int n2 = readAll(in, result, 0, dataLen); - - assertEquals(dataLen, n1 + skipped + n2); - final byte[] readData = new byte[n2]; - System.arraycopy(result, 0, readData, 0, n2); - final byte[] expectedData = new byte[n2]; - System.arraycopy(data, dataLen - n2, expectedData, 0, n2); - assertArrayEquals(readData, expectedData); - - final Exception e = assertThrows(IllegalArgumentException.class, () -> in.skip(-3)); - assertTrue(e.getMessage().contains("Negative skip length")); + private void byteBufferReadCheck(final InputStream in, final ByteBuffer buf, final int bufPos) + throws Exception { + buf.position(bufPos); + final int n = ((ReadableByteChannel) in).read(buf); + assertEquals(bufPos + n, buf.position()); + final byte[] readData = new byte[n]; + buf.rewind(); + buf.position(bufPos); + buf.get(readData); + final byte[] expectedData = new byte[n]; + System.arraycopy(data, 0, expectedData, 0, n); + assertArrayEquals(readData, expectedData); - // Skip after EOF - skipped = in.skip(3); - assertEquals(skipped, 0); - } + assertThrows(IndexOutOfBoundsException.class, () -> in.read(readData, -1, 0)); } protected void doByteBufferRead(final String cipherClass, final boolean withChannel) - throws Exception { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } + throws Exception { + // Skip this test if no JNI + assumeJniPresence(cipherClass); ByteBuffer buf = ByteBuffer.allocate(dataLen + 100); // Default buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { byteBufferReadCheck(in, buf, 0); } // Default buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Small buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - smallBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, smallBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 0); } // Small buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - smallBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, smallBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, default buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { buf = ByteBuffer.allocateDirect(dataLen + 100); byteBufferReadCheck(in, buf, 0); } // Direct buffer, default buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, small buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - smallBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, smallBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 0); } // Direct buffer, small buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - smallBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, smallBufferSize, iv, withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, small buffer size, initial buffer position is 0, final read - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - smallBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, smallBufferSize, iv, withChannel)) { buf.clear(); byteBufferFinalReadCheck(in, buf, 0); } // Default buffer size, initial buffer position is 0, insufficient dest buffer length - try (InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), getCipher(cipherClass), - defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { buf = ByteBuffer.allocate(100); byteBufferReadCheck(in, buf, 0); } // Default buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf = ByteBuffer.allocate(dataLen + 100); byteBufferReadCheck(in, buf, 0); } // Default buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Small buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 0); } // Small buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, default buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf = ByteBuffer.allocateDirect(dataLen + 100); byteBufferReadCheck(in, buf, 0); } // Direct buffer, default buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, small buffer size, initial buffer position is 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 0); } // Direct buffer, small buffer size, initial buffer position is not 0 - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferReadCheck(in, buf, 11); } // Direct buffer, default buffer size, initial buffer position is 0, final read - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf.clear(); byteBufferFinalReadCheck(in, buf, 0); } // Default buffer size, initial buffer position is 0, insufficient dest buffer length - try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, - new IvParameterSpec(iv), withChannel)) { + try (InputStream in = newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), key, new IvParameterSpec(iv), withChannel)) { buf = ByteBuffer.allocate(100); byteBufferReadCheck(in, buf, 0); } } + private void doByteBufferWrite(final CryptoOutputStream out, final boolean withChannel) throws Exception { + ByteBuffer buf = ByteBuffer.allocateDirect(dataLen / 2); + buf.put(data, 0, dataLen / 2); + buf.flip(); + final int n1 = out.write(buf); + + buf.clear(); + buf.put(data, n1, dataLen / 3); + buf.flip(); + final int n2 = out.write(buf); + + buf.clear(); + buf.put(data, n1 + n2, dataLen - n1 - n2 - 1); + buf.flip(); + final int n3 = out.write(buf); + + out.write(1); + + assertEquals(dataLen, n1 + n2 + n3 + 1); + + assertThrows(IndexOutOfBoundsException.class, () -> out.write(data, 0, data.length + 1)); + out.flush(); + + try (CryptoCipher cipher = out.getCipher(); + InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { + buf = ByteBuffer.allocate(dataLen + 100); + byteBufferReadCheck(in, buf, 0); + } + } + protected void doByteBufferWrite(final String cipherClass, final ByteArrayOutputStream baos, final boolean withChannel) - throws Exception { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } + throws Exception { + assumeJniPresence(cipherClass); baos.reset(); - CryptoOutputStream out = newCryptoOutputStream(baos, - getCipher(cipherClass), defaultBufferSize, iv, withChannel); + CryptoOutputStream out = newCryptoOutputStream(baos, getCipher(cipherClass), defaultBufferSize, iv, withChannel); doByteBufferWrite(out, withChannel); baos.reset(); - final CryptoCipher cipher = getCipher(cipherClass); - final String transformation = cipher.getAlgorithm(); - out = newCryptoOutputStream(transformation, props, baos, key, - new IvParameterSpec(iv), withChannel); - doByteBufferWrite(out, withChannel); - out.write(1); - assertTrue(out.isOpen()); + try (final CryptoCipher cipher = getCipher(cipherClass)) { + final String transformation = cipher.getAlgorithm(); + out = newCryptoOutputStream(transformation, props, baos, key, new IvParameterSpec(iv), withChannel); + doByteBufferWrite(out, withChannel); + out.write(1); + assertTrue(out.isOpen()); - out = newCryptoOutputStream(transformation, props, baos, key, - new IvParameterSpec(iv), withChannel); - out.close(); - assertFalse(out.isOpen()); + out = newCryptoOutputStream(transformation, props, baos, key, new IvParameterSpec(iv), withChannel); + out.close(); + assertFalse(out.isOpen()); + } } protected void doExceptionTest(final String cipherClass, final ByteArrayOutputStream baos, final boolean withChannel) throws IOException { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } + assumeJniPresence(cipherClass); // Test InvalidAlgorithmParameters - Exception ex = assertThrows(IOException.class, () -> newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), - new SecretKeySpec(key, "AES"), new GCMParameterSpec(0, new byte[0]), withChannel)); - assertEquals(ex.getMessage(),"Illegal parameters"); + Exception ex = assertThrows(IOException.class, () -> newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), + AES.newSecretKeySpec(key), new GCMParameterSpec(0, new byte[0]), withChannel)); + assertEquals(ex.getMessage(), "Illegal parameters"); // Test InvalidAlgorithmParameters - ex = assertThrows(IOException.class, () -> newCryptoOutputStream(transformation, props, baos, - new SecretKeySpec(key, "AES"), new GCMParameterSpec(0, - new byte[0]), withChannel)); - assertEquals(ex.getMessage(),"Illegal parameters"); + ex = assertThrows(IOException.class, + () -> newCryptoOutputStream(transformation, props, baos, AES.newSecretKeySpec(key), new GCMParameterSpec(0, new byte[0]), withChannel)); + assertEquals(ex.getMessage(), "Illegal parameters"); // Test Invalid Key - assertThrows(IOException.class, () -> newCryptoInputStream(transformation,props, new ByteArrayInputStream(encData), - new SecretKeySpec(new byte[10], "AES"), new IvParameterSpec(iv), withChannel)); - // Test Invalid Key - assertThrows(IOException.class, () -> newCryptoOutputStream(transformation, props, baos, new byte[10], + assertThrows(IOException.class, () -> newCryptoInputStream(transformation, props, new ByteArrayInputStream(encData), AES.newSecretKeySpec(new byte[10]), new IvParameterSpec(iv), withChannel)); + // Test Invalid Key + assertThrows(IOException.class, () -> newCryptoOutputStream(transformation, props, baos, new byte[10], new IvParameterSpec(iv), withChannel)); // Test reading a closed stream. InputStream closedIn; - try (final InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), - getCipher(cipherClass), defaultBufferSize, iv, withChannel)) { + try (CryptoCipher cipher = getCipher(cipherClass); + final InputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { closedIn = in; } // Throw exception. @@ -382,8 +341,7 @@ protected void doExceptionTest(final String cipherClass, final ByteArrayOutputSt } // Test checking a closed stream. - final OutputStream out = newCryptoOutputStream(transformation, props, baos, key, new IvParameterSpec(iv), - withChannel); + final OutputStream out = newCryptoOutputStream(transformation, props, baos, key, new IvParameterSpec(iv), withChannel); out.close(); // Throw exception. assertThrows(IOException.class, ((CryptoOutputStream) out)::checkStream); @@ -396,8 +354,8 @@ protected void doExceptionTest(final String cipherClass, final ByteArrayOutputSt } // Test checkStreamCipher - try { - CryptoInputStream.checkStreamCipher(getCipher(cipherClass)); + try (CryptoCipher cipher = getCipher(cipherClass)) { + CryptoInputStream.checkStreamCipher(cipher); } catch (final IOException ioEx) { assertEquals(ioEx.getMessage(), "AES/CTR/NoPadding is required"); } finally { @@ -405,126 +363,206 @@ protected void doExceptionTest(final String cipherClass, final ByteArrayOutputSt } // Test unsupported operation handling. - final InputStream inNewCrytptoStr = newCryptoInputStream(new ByteArrayInputStream(encData), - getCipher(cipherClass), defaultBufferSize, iv, false); - closedIn.mark(0); - assertFalse(closedIn.markSupported()); - ex = assertThrows(IOException.class, inNewCrytptoStr::reset); - assertEquals(ex.getMessage(), "mark/reset not supported"); + try (CryptoCipher cipher = getCipher(cipherClass); + final InputStream inNewCrytptoStr = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, false)) { + closedIn.mark(0); + assertFalse(closedIn.markSupported()); + ex = assertThrows(IOException.class, inNewCrytptoStr::reset); + assertEquals(ex.getMessage(), "mark/reset not supported"); + } } protected void doFieldGetterTest(final String cipherClass, final ByteArrayOutputStream baos, - final boolean withChannel) throws Exception { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI + final boolean withChannel) throws Exception { + assumeJniPresence(cipherClass); + + try (final CryptoCipher cipher = getCipher(cipherClass); + final CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { + + final Properties props = new Properties(); + final String bufferSize = Integer.toString(defaultBufferSize / 2); + props.put(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, bufferSize); + + assertEquals(CryptoInputStream.getBufferSize(props), Integer.parseInt(bufferSize)); + assertEquals(in.getBufferSize(), defaultBufferSize); + assertEquals(in.getCipher().getClass(), Class.forName(cipherClass)); + assertEquals(in.getKey().getAlgorithm(), AES.ALGORITHM); + assertEquals(in.getParams().getClass(), IvParameterSpec.class); + assertNotNull(in.getInput()); + + try (final CryptoOutputStream out = newCryptoOutputStream(baos, getCipher(cipherClass), defaultBufferSize, iv, withChannel)) { + assertEquals(out.getOutBuffer().capacity(), defaultBufferSize + cipher.getBlockSize()); + assertEquals(out.getInBuffer().capacity(), defaultBufferSize); + assertEquals(out.getBufferSize(), defaultBufferSize); + } } - - final CryptoCipher cipher = getCipher(cipherClass); - - final CryptoInputStream in = newCryptoInputStream( - new ByteArrayInputStream(encData), cipher, defaultBufferSize, - iv, withChannel); - - final Properties props = new Properties(); - final String bufferSize = Integer.toString(defaultBufferSize / 2); - props.put(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, bufferSize); - - assertEquals(CryptoInputStream.getBufferSize(props), Integer.parseInt(bufferSize)); - assertEquals(in.getBufferSize(), defaultBufferSize); - assertEquals(in.getCipher().getClass(), Class.forName(cipherClass)); - assertEquals(in.getKey().getAlgorithm(), "AES"); - assertEquals(in.getParams().getClass(), IvParameterSpec.class); - assertNotNull(in.getInput()); - - final CryptoOutputStream out = newCryptoOutputStream(baos, getCipher(cipherClass), - defaultBufferSize, iv, withChannel); - - assertEquals(out.getOutBuffer().capacity(), defaultBufferSize + cipher.getBlockSize()); - assertEquals(out.getInBuffer().capacity(), defaultBufferSize); - assertEquals(out.getBufferSize(), defaultBufferSize); } - private void byteBufferReadCheck(final InputStream in, final ByteBuffer buf, final int bufPos) - throws Exception { - buf.position(bufPos); - final int n = ((ReadableByteChannel) in).read(buf); - assertEquals(bufPos + n, buf.position()); - final byte[] readData = new byte[n]; - buf.rewind(); - buf.position(bufPos); - buf.get(readData); - final byte[] expectedData = new byte[n]; - System.arraycopy(data, 0, expectedData, 0, n); - assertArrayEquals(readData, expectedData); - - assertThrows(IndexOutOfBoundsException.class, () -> in.read(readData, -1, 0)); + protected void doReadWriteTest(final int count, final String encCipherClass, + final String decCipherClass, final byte[] iv) throws IOException { + doReadWriteTestForInputStream(count, encCipherClass, decCipherClass, iv); + doReadWriteTestForReadableByteChannel(count, encCipherClass, + decCipherClass, iv); } - private void byteBufferFinalReadCheck(final InputStream in, final ByteBuffer buf, final int bufPos) - throws Exception { - buf.position(bufPos); - int len = 0; - int n = 0; - do { - n = ((ReadableByteChannel) in).read(buf); - len += n; - } while (n > 0); - buf.rewind(); - final byte[] readData = new byte[len + 1]; - buf.get(readData); - final byte[] expectedData = new byte[len + 1]; - System.arraycopy(data, 0, expectedData, 0, len + 1); - assertArrayEquals(readData, expectedData); - } + private void doReadWriteTestForInputStream(final int count, + final String encCipherClass, final String decCipherClass, final byte[] iv) + throws IOException { + assumeJniPresence(encCipherClass); + assumeJniPresence(decCipherClass); + // Created a cipher object of type encCipherClass; + try (final CryptoCipher encCipher = getCipher(encCipherClass)) { + + // Generate data + final SecureRandom random = new SecureRandom(); + final byte[] originalData = new byte[count]; + final byte[] decryptedData = new byte[count]; + random.nextBytes(originalData); + + // Encrypt data + final ByteArrayOutputStream encryptedData = new ByteArrayOutputStream(); + try (CryptoOutputStream out = newCryptoOutputStream(encryptedData, encCipher, defaultBufferSize, iv, false)) { + out.write(originalData, 0, originalData.length); + out.flush(); + } - private void prepareData() throws IOException { - CryptoCipher cipher = null; - try { - cipher = (CryptoCipher) ReflectionUtils.newInstance( - ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props, - transformation); - } catch (final ClassNotFoundException cnfe) { - throw new IOException("Illegal crypto cipher!"); + // Created a cipher object of type decCipherClass; + try (final CryptoCipher decCipher = getCipher(decCipherClass)) { + + // Decrypt data + try (CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, + false)) { + + // Check + int remainingToRead = count; + int offset = 0; + while (remainingToRead > 0) { + final int n = in.read(decryptedData, offset, decryptedData.length - offset); + if (n >= 0) { + remainingToRead -= n; + offset += n; + } + } + + assertArrayEquals(originalData, decryptedData, "originalData and decryptedData not equal"); + } + + // Decrypt data byte-at-a-time + try (CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, + false)) { + + // Check + final DataInputStream originalIn = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(originalData))); + int expected; + do { + expected = originalIn.read(); + assertEquals(expected, in.read(), "Decrypted stream read by byte does not match"); + } while (expected != -1); + + // Completed checking records; + } + } } + } - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (OutputStream out = new CryptoOutputStream(baos, cipher, - defaultBufferSize, new SecretKeySpec(key, "AES"), - new IvParameterSpec(iv))) { - out.write(data); - out.flush(); + private void doReadWriteTestForReadableByteChannel(final int count, + final String encCipherClass, final String decCipherClass, final byte[] iv) + throws IOException { + assumeJniPresence(encCipherClass); + assumeJniPresence(decCipherClass); + // Creates a cipher object of type encCipherClass; + try (final CryptoCipher encCipher = getCipher(encCipherClass)) { + + // Generate data + final SecureRandom random = new SecureRandom(); + final byte[] originalData = new byte[count]; + final byte[] decryptedData = new byte[count]; + random.nextBytes(originalData); + + // Encrypt data + final ByteArrayOutputStream encryptedData = new ByteArrayOutputStream(); + try (CryptoOutputStream out = newCryptoOutputStream(encryptedData, encCipher, defaultBufferSize, iv, true)) { + out.write(originalData, 0, originalData.length); + out.flush(); + } + + // Creates a cipher object of type decCipherClass + try (final CryptoCipher decCipher = getCipher(decCipherClass)) { + + // Decrypt data + try (CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, + true)) { + + // Check + int remainingToRead = count; + int offset = 0; + while (remainingToRead > 0) { + final int n = in.read(decryptedData, offset, decryptedData.length - offset); + if (n >= 0) { + remainingToRead -= n; + offset += n; + } + } + + assertArrayEquals(originalData, decryptedData); + } + + // Decrypt data byte-at-a-time + try (CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, + true)) { + + // Check + final DataInputStream originalIn = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(originalData))); + int expected; + do { + expected = originalIn.read(); + assertEquals(expected, in.read()); + } while (expected != -1); + + // Completed checking records + } + } } - encData = baos.toByteArray(); } - private void doByteBufferWrite(final CryptoOutputStream out, final boolean withChannel) throws Exception { - ByteBuffer buf = ByteBuffer.allocateDirect(dataLen / 2); - buf.put(data, 0, dataLen / 2); - buf.flip(); - final int n1 = out.write(buf); + protected void doSkipTest(final String cipherClass, final boolean withChannel) + throws IOException { + assumeJniPresence(cipherClass); + try (@SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoInputStream. + InputStream in = newCryptoInputStream( + new ByteArrayInputStream(encData), getCipher(cipherClass), + defaultBufferSize, iv, withChannel)) { + final byte[] result = new byte[dataLen]; + final int n1 = readAll(in, result, 0, dataLen / 5); - buf.clear(); - buf.put(data, n1, dataLen / 3); - buf.flip(); - final int n2 = out.write(buf); + assertEquals(in.skip(0), 0); - buf.clear(); - buf.put(data, n1 + n2, dataLen - n1 - n2 - 1); - buf.flip(); - final int n3 = out.write(buf); + long skipped = in.skip(dataLen / 5); + final int n2 = readAll(in, result, 0, dataLen); - out.write(1); + assertEquals(dataLen, n1 + skipped + n2); + final byte[] readData = new byte[n2]; + System.arraycopy(result, 0, readData, 0, n2); + final byte[] expectedData = new byte[n2]; + System.arraycopy(data, dataLen - n2, expectedData, 0, n2); + assertArrayEquals(readData, expectedData); - assertEquals(dataLen, n1 + n2 + n3 + 1); + final Exception e = assertThrows(IllegalArgumentException.class, () -> in.skip(-3)); + assertTrue(e.getMessage().contains("Negative skip length")); - assertThrows(IndexOutOfBoundsException.class, () -> out.write(data, 0, data.length + 1)); - out.flush(); + // Skip after EOF + skipped = in.skip(3); + assertEquals(skipped, 0); + } + } - try (InputStream in = newCryptoInputStream( - new ByteArrayInputStream(encData), out.getCipher(), - defaultBufferSize, iv, withChannel)) { - buf = ByteBuffer.allocate(dataLen + 100); - byteBufferReadCheck(in, buf, 0); + protected CryptoCipher getCipher(final String cipherClass) throws IOException { + try { + return (CryptoCipher) ReflectionUtils.newInstance( + ReflectionUtils.getClassByName(cipherClass), props, + transformation); + } catch (final ClassNotFoundException cnfe) { + throw new IOException("Illegal crypto cipher!"); } } @@ -533,20 +571,20 @@ protected CryptoInputStream newCryptoInputStream(final ByteArrayInputStream bais throws IOException { if (withChannel) { return new CryptoInputStream(Channels.newChannel(bais), cipher, - bufferSize, new SecretKeySpec(key, "AES"), + bufferSize, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); } return new CryptoInputStream(bais, cipher, bufferSize, - new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); + AES.newSecretKeySpec(key), new IvParameterSpec(iv)); } protected CryptoInputStream newCryptoInputStream(final String transformation, final Properties props, final ByteArrayInputStream bais, final byte[] key, final AlgorithmParameterSpec params, final boolean withChannel) throws IOException { if (withChannel) { - return new CryptoInputStream(transformation, props, Channels.newChannel(bais), new SecretKeySpec(key, "AES"), params); + return new CryptoInputStream(transformation, props, Channels.newChannel(bais), AES.newSecretKeySpec(key), params); } - return new CryptoInputStream(transformation, props, bais, new SecretKeySpec(key, "AES"), params); + return new CryptoInputStream(transformation, props, bais, AES.newSecretKeySpec(key), params); } protected CryptoInputStream newCryptoInputStream(final String transformation, @@ -563,11 +601,11 @@ protected CryptoOutputStream newCryptoOutputStream( final byte[] iv, final boolean withChannel) throws IOException { if (withChannel) { return new CryptoOutputStream(Channels.newChannel(baos), cipher, - bufferSize, new SecretKeySpec(key, "AES"), + bufferSize, AES.newSecretKeySpec(key), new IvParameterSpec(iv)); } return new CryptoOutputStream(baos, cipher, bufferSize, - new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); + AES.newSecretKeySpec(key), new IvParameterSpec(iv)); } protected CryptoOutputStream newCryptoOutputStream(final String transformation, @@ -575,9 +613,9 @@ protected CryptoOutputStream newCryptoOutputStream(final String transformation, final AlgorithmParameterSpec param, final boolean withChannel) throws IOException { if (withChannel) { return new CryptoOutputStream(transformation, props, Channels.newChannel(baos), - new SecretKeySpec(key, "AES"), param); + AES.newSecretKeySpec(key), param); } - return new CryptoOutputStream(transformation, props, baos, new SecretKeySpec(key, "AES"), + return new CryptoOutputStream(transformation, props, baos, AES.newSecretKeySpec(key), param); } @@ -590,6 +628,18 @@ protected CryptoOutputStream newCryptoOutputStream(final String transformation, return new CryptoOutputStream(transformation, props, baos, key, params); } + private void prepareData() throws IOException, ClassNotFoundException { + try (CryptoCipher cipher = (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props, + transformation)) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStream out = new CryptoOutputStream(baos, cipher, defaultBufferSize, AES.newSecretKeySpec(key), new IvParameterSpec(iv))) { + out.write(data); + out.flush(); + } + encData = baos.toByteArray(); + } + } + private int readAll(final InputStream in, final byte[] b, final int offset, final int len) throws IOException { int n = 0; @@ -605,14 +655,51 @@ private int readAll(final InputStream in, final byte[] b, final int offset, fina return total; } - protected CryptoCipher getCipher(final String cipherClass) throws IOException { - try { - return (CryptoCipher) ReflectionUtils.newInstance( - ReflectionUtils.getClassByName(cipherClass), props, - transformation); - } catch (final ClassNotFoundException cnfe) { - throw new IOException("Illegal crypto cipher!"); - } + public abstract void setUp() throws IOException; + + /** Test byte buffer read with different buffer size. */ + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testByteBufferRead() throws Exception { + doByteBufferRead(AbstractCipherTest.JCE_CIPHER_CLASSNAME, false); + doByteBufferRead(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, false); + + doByteBufferRead(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true); + doByteBufferRead(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true); + } + + /** Test byte buffer write. */ + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testByteBufferWrite() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doByteBufferWrite(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); + doByteBufferWrite(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); + + doByteBufferWrite(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); + doByteBufferWrite(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); + } + + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testExceptions() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doExceptionTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); + doExceptionTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); + + doExceptionTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); + doExceptionTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); + } + + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testFieldGetters() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doFieldGetterTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, false); + doFieldGetterTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, false); + + doFieldGetterTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, baos, true); + doFieldGetterTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, baos, true); } @Test @@ -625,7 +712,7 @@ public void testReadWrite() throws Exception { doReadWriteTest(count, AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv); // Overflow test, IV: xx xx xx xx xx xx xx xx ff ff ff ff ff ff ff ff for (int i = 0; i < 8; i++) { - iv[8 + i] = (byte) 0xff; + iv[8 + i] = (byte) Utils.BYTE_MASK; } doReadWriteTest(count, AbstractCipherTest.JCE_CIPHER_CLASSNAME, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv); doReadWriteTest(count, AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, iv); @@ -633,138 +720,14 @@ public void testReadWrite() throws Exception { doReadWriteTest(count, AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv); } - protected void doReadWriteTest(final int count, final String encCipherClass, - final String decCipherClass, final byte[] iv) throws IOException { - doReadWriteTestForInputStream(count, encCipherClass, decCipherClass, iv); - doReadWriteTestForReadableByteChannel(count, encCipherClass, - decCipherClass, iv); - } - - private void doReadWriteTestForInputStream(final int count, - final String encCipherClass, final String decCipherClass, final byte[] iv) - throws IOException { - if ((AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(encCipherClass) - || - AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(decCipherClass)) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } - // Created a cipher object of type encCipherClass; - final CryptoCipher encCipher = getCipher(encCipherClass); - - // Generate data - final SecureRandom random = new SecureRandom(); - final byte[] originalData = new byte[count]; - final byte[] decryptedData = new byte[count]; - random.nextBytes(originalData); - - // Encrypt data - final ByteArrayOutputStream encryptedData = new ByteArrayOutputStream(); - try (CryptoOutputStream out = newCryptoOutputStream(encryptedData, - encCipher, defaultBufferSize, iv, false)) { - out.write(originalData, 0, originalData.length); - out.flush(); - } - - // Created a cipher object of type decCipherClass; - final CryptoCipher decCipher = getCipher(decCipherClass); - - // Decrypt data - CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream( - encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, - false); - - // Check - int remainingToRead = count; - int offset = 0; - while (remainingToRead > 0) { - final int n = in.read(decryptedData, offset, decryptedData.length - - offset); - if (n >= 0) { - remainingToRead -= n; - offset += n; - } - } - - assertArrayEquals(originalData, decryptedData,"originalData and decryptedData not equal"); - - // Decrypt data byte-at-a-time - in = newCryptoInputStream( - new ByteArrayInputStream(encryptedData.toByteArray()), - decCipher, defaultBufferSize, iv, false); - - // Check - final DataInputStream originalIn = new DataInputStream( - new BufferedInputStream(new ByteArrayInputStream(originalData))); - int expected; - do { - expected = originalIn.read(); - assertEquals(expected, in.read(),"Decrypted stream read by byte does not match"); - } while (expected != -1); - - // Completed checking records; - } - - private void doReadWriteTestForReadableByteChannel(final int count, - final String encCipherClass, final String decCipherClass, final byte[] iv) - throws IOException { - if ((AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(encCipherClass) - || - AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(decCipherClass)) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } - // Creates a cipher object of type encCipherClass; - final CryptoCipher encCipher = getCipher(encCipherClass); - - // Generate data - final SecureRandom random = new SecureRandom(); - final byte[] originalData = new byte[count]; - final byte[] decryptedData = new byte[count]; - random.nextBytes(originalData); - - // Encrypt data - final ByteArrayOutputStream encryptedData = new ByteArrayOutputStream(); - try (CryptoOutputStream out = newCryptoOutputStream(encryptedData, - encCipher, defaultBufferSize, iv, true)) { - out.write(originalData, 0, originalData.length); - out.flush(); - } - - // Creates a cipher object of type decCipherClass - final CryptoCipher decCipher = getCipher(decCipherClass); - - // Decrypt data - CryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream( - encryptedData.toByteArray()), decCipher, defaultBufferSize, iv, - true); - - // Check - int remainingToRead = count; - int offset = 0; - while (remainingToRead > 0) { - final int n = in.read(decryptedData, offset, decryptedData.length - - offset); - if (n >= 0) { - remainingToRead -= n; - offset += n; - } - } - - assertArrayEquals(originalData, decryptedData); - - // Decrypt data byte-at-a-time - in = newCryptoInputStream( - new ByteArrayInputStream(encryptedData.toByteArray()), - decCipher, defaultBufferSize, iv, true); - - // Check - final DataInputStream originalIn = new DataInputStream( - new BufferedInputStream(new ByteArrayInputStream(originalData))); - int expected; - do { - expected = originalIn.read(); - assertEquals(expected, in.read()); - } while (expected != -1); + /** Test skip. */ + @Test + @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS) + public void testSkip() throws Exception { + doSkipTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, false); + doSkipTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, false); - // Completed checking records + doSkipTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true); + doSkipTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true); } } diff --git a/src/test/java/org/apache/commons/crypto/stream/CbcNoPaddingCipherStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/CbcNoPaddingCipherStreamTest.java index 1dc99765a..3aa6deefd 100644 --- a/src/test/java/org/apache/commons/crypto/stream/CbcNoPaddingCipherStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/CbcNoPaddingCipherStreamTest.java @@ -17,11 +17,13 @@ */ package org.apache.commons.crypto.stream; +import org.apache.commons.crypto.utils.AES; + public class CbcNoPaddingCipherStreamTest extends AbstractCipherStreamTest { @Override public void setUp() { - transformation = "AES/CBC/NoPadding"; + transformation = AES.CBC_NO_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/stream/CbcPkcs5PaddingCipherStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/CbcPkcs5PaddingCipherStreamTest.java index ea90283dc..57a0445bd 100644 --- a/src/test/java/org/apache/commons/crypto/stream/CbcPkcs5PaddingCipherStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/CbcPkcs5PaddingCipherStreamTest.java @@ -17,10 +17,12 @@ */ package org.apache.commons.crypto.stream; +import org.apache.commons.crypto.utils.AES; + public class CbcPkcs5PaddingCipherStreamTest extends AbstractCipherStreamTest { @Override public void setUp() { - transformation = "AES/CBC/PKCS5Padding"; + transformation = AES.CBC_PKCS5_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java index 894ab83ae..e5f4e24df 100644 --- a/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java @@ -34,20 +34,81 @@ import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; -import org.apache.commons.crypto.Crypto; import org.apache.commons.crypto.cipher.AbstractCipherTest; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.stream.input.ChannelInput; import org.apache.commons.crypto.stream.input.StreamInput; import org.apache.commons.crypto.stream.output.ChannelOutput; +import org.apache.commons.crypto.utils.AES; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; public class CtrCryptoStreamTest extends AbstractCipherStreamTest { + protected void doDecryptTest(final String cipherClass, final boolean withChannel) + throws IOException { + try (CryptoCipher cipher = getCipher(cipherClass); + CtrCryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), cipher, defaultBufferSize, iv, withChannel)) { + + final ByteBuffer buf = ByteBuffer.allocateDirect(dataLen); + buf.put(encData); + buf.rewind(); + in.decrypt(buf, 0, dataLen); + final byte[] readData = new byte[dataLen]; + final byte[] expectedData = new byte[dataLen]; + buf.get(readData); + System.arraycopy(data, 0, expectedData, 0, dataLen); + assertArrayEquals(readData, expectedData); + final Exception ex = assertThrows(IOException.class, () -> in.decryptBuffer(buf)); + assertEquals(ex.getCause().getClass(), ShortBufferException.class); + } + } + @Override - public void setUp() { - transformation = "AES/CTR/NoPadding"; + protected void doFieldGetterTest(final String cipherClass, final ByteArrayOutputStream baos, + final boolean withChannel) throws Exception { + assumeJniPresence(cipherClass); + + final StreamInput streamInput = new StreamInput(new ByteArrayInputStream(encData), 0); + Exception ex = assertThrows(UnsupportedOperationException.class, () -> streamInput.seek(0)); + assertEquals(ex.getMessage(), "Seek is not supported by this implementation"); + + ex = assertThrows(UnsupportedOperationException.class, () -> streamInput.read(0, new byte[0], 0, 0)); + assertEquals(ex.getMessage(), "Positioned read is not supported by this implementation"); + + assertEquals(streamInput.available(), encData.length); + + try (ChannelInput channelInput = new ChannelInput(Channels.newChannel(new ByteArrayInputStream(encData)))) { + ex = assertThrows(UnsupportedOperationException.class, () -> channelInput.seek(0)); + assertEquals(ex.getMessage(), "Seek is not supported by this implementation"); + + ex = assertThrows(UnsupportedOperationException.class, () -> channelInput.read(0, new byte[0], 0, 0)); + assertEquals(ex.getMessage(), "Positioned read is not supported by this implementation"); + assertEquals(channelInput.available(), 0); + + final String bufferSize = "4096"; + try (CryptoCipher cipher = getCipher(cipherClass); + CtrCryptoInputStream in = new CtrCryptoInputStream(channelInput, cipher, defaultBufferSize, key, iv)) { + final Properties props = new Properties(); + props.put(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, bufferSize); + in.setStreamOffset(smallBufferSize); + + assertEquals(CryptoInputStream.getBufferSize(props), Integer.parseInt(bufferSize)); + assertEquals(smallBufferSize, in.getStreamOffset()); + assertEquals(in.getBufferSize(), 8192); + assertEquals(in.getCipher().getClass(), Class.forName(cipherClass)); + assertEquals(in.getKey().getAlgorithm(), AES.ALGORITHM); + assertEquals(in.getParams().getClass(), IvParameterSpec.class); + assertNotNull(in.getInput()); + } + + try (CryptoCipher cipher = getCipher(cipherClass); + CtrCryptoOutputStream out = new CtrCryptoOutputStream(new ChannelOutput(Channels.newChannel(baos)), cipher, Integer.parseInt(bufferSize), + key, iv)) { + out.setStreamOffset(smallBufferSize); + assertEquals(out.getStreamOffset(), smallBufferSize); + } + } } @Override @@ -62,7 +123,7 @@ protected CtrCryptoInputStream newCryptoInputStream( } @Override - protected CtrCryptoInputStream newCryptoInputStream(final String transformation, final Properties props, + protected CryptoInputStream newCryptoInputStream(final String transformation, final Properties props, final ByteArrayInputStream bais, final byte[] key, final AlgorithmParameterSpec params, final boolean withChannel) throws IOException { if (withChannel) { @@ -95,54 +156,8 @@ protected CtrCryptoOutputStream newCryptoOutputStream(final String transformatio } @Override - protected void doFieldGetterTest(final String cipherClass, final ByteArrayOutputStream baos, - final boolean withChannel) throws Exception { - if (AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME.equals(cipherClass) && !Crypto.isNativeCodeLoaded()) { - return; // Skip this test if no JNI - } - - final StreamInput streamInput = new StreamInput(new ByteArrayInputStream(encData), 0); - Exception ex = assertThrows(UnsupportedOperationException.class, () -> streamInput.seek(0)); - assertEquals(ex.getMessage(), "Seek is not supported by this implementation"); - - ex = assertThrows(UnsupportedOperationException.class, () -> streamInput.read(0, new byte[0], 0, 0)); - assertEquals(ex.getMessage(), "Positioned read is not supported by this implementation"); - - assertEquals(streamInput.available(), encData.length); - - final ChannelInput channelInput = new ChannelInput(Channels.newChannel(new ByteArrayInputStream(encData))); - ex = assertThrows(UnsupportedOperationException.class, () -> channelInput.seek(0)); - assertEquals(ex.getMessage(), "Seek is not supported by this implementation"); - - ex = assertThrows(UnsupportedOperationException.class, () -> channelInput.read(0, new byte[0], 0, 0)); - assertEquals(ex.getMessage(), "Positioned read is not supported by this implementation"); - assertEquals(channelInput.available(), 0); - - final CtrCryptoInputStream in = new CtrCryptoInputStream(channelInput, getCipher(cipherClass), - defaultBufferSize, key, iv); - - final Properties props = new Properties(); - final String bufferSize = "4096"; - props.put(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, bufferSize); - in.setStreamOffset(smallBufferSize); - - assertEquals(CryptoInputStream.getBufferSize(props), Integer.parseInt(bufferSize)); - assertEquals(smallBufferSize, in.getStreamOffset()); - assertEquals(in.getBufferSize(), 8192); - assertEquals(in.getCipher().getClass(), Class.forName(cipherClass)); - assertEquals(in.getKey().getAlgorithm(), "AES"); - assertEquals(in.getParams().getClass(), IvParameterSpec.class); - assertNotNull(in.getInput()); - - in.close(); - - final CtrCryptoOutputStream out = new CtrCryptoOutputStream(new ChannelOutput( - Channels.newChannel(baos)), getCipher(cipherClass), - Integer.parseInt(bufferSize), key, iv); - out.setStreamOffset(smallBufferSize); - assertEquals(out.getStreamOffset(), smallBufferSize); - - out.close(); + public void setUp() { + transformation = AES.CTR_NO_PADDING; } @Test @@ -154,24 +169,4 @@ public void testDecrypt() throws Exception { doDecryptTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true); doDecryptTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true); } - - protected void doDecryptTest(final String cipherClass, final boolean withChannel) - throws IOException { - - final CtrCryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encData), - getCipher(cipherClass), defaultBufferSize, iv, withChannel); - - final ByteBuffer buf = ByteBuffer.allocateDirect(dataLen); - buf.put(encData); - buf.rewind(); - in.decrypt(buf, 0, dataLen); - final byte[] readData = new byte[dataLen]; - final byte[] expectedData = new byte[dataLen]; - buf.get(readData); - System.arraycopy(data, 0, expectedData, 0, dataLen); - assertArrayEquals(readData, expectedData); - final Exception ex = assertThrows(IOException.class, () -> in.decryptBuffer(buf)); - assertEquals(ex.getCause().getClass(), ShortBufferException.class); - - } } diff --git a/src/test/java/org/apache/commons/crypto/stream/CtrNoPaddingCipherStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/CtrNoPaddingCipherStreamTest.java index e7358ac84..0c0c7f93e 100644 --- a/src/test/java/org/apache/commons/crypto/stream/CtrNoPaddingCipherStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/CtrNoPaddingCipherStreamTest.java @@ -17,11 +17,13 @@ */ package org.apache.commons.crypto.stream; +import org.apache.commons.crypto.utils.AES; + public class CtrNoPaddingCipherStreamTest extends AbstractCipherStreamTest { @Override public void setUp() { - transformation = "AES/CTR/NoPadding"; + transformation = AES.CTR_NO_PADDING; } } diff --git a/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java index 0926b9ef8..d661f29f5 100644 --- a/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java +++ b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java @@ -34,18 +34,104 @@ import java.util.Random; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import org.apache.commons.crypto.Crypto; import org.apache.commons.crypto.cipher.AbstractCipherTest; import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.utils.AES; import org.apache.commons.crypto.utils.ReflectionUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class PositionedCryptoInputStreamTest { + static class PositionedInputForTest implements Input { + + final byte[] data; + long pos; + final long count; + + public PositionedInputForTest(final byte[] data) { + this.data = data; + this.pos = 0; + this.count = data.length; + } + + @Override + public int available() { + return (int) (count - pos); + } + + @Override + public void close() { + // empty + } + + @Override + public int read(final ByteBuffer dst) { + final int remaining = (int) (count - pos); + if (remaining <= 0) { + return -1; + } + + final int length = Math.min(dst.remaining(), remaining); + dst.put(data, (int) pos, length); + pos += length; + return length; + } + + @Override + public int read(final long position, final byte[] buffer, final int offset, int length) { + Objects.requireNonNull(buffer, "buffer"); + if (offset < 0 || length < 0 + || length > buffer.length - offset) { + throw new IndexOutOfBoundsException(); + } + + if (position < 0 || position >= count) { + return -1; + } + + final long avail = count - position; + if (length > avail) { + length = (int) avail; + } + if (length <= 0) { + return 0; + } + System.arraycopy(data, (int) position, buffer, offset, length); + return length; + } + + @Override + public void seek(final long position) throws IOException { + if (pos < 0) { + throw new IOException("Negative seek offset"); + } + if (position >= 0 && position < count) { + pos = position; + } else { + // to the end of file + pos = count; + } + } + + @Override + public long skip(long n) { + if (n <= 0) { + return 0; + } + + final long remaining = count - pos; + if (remaining < n) { + n = remaining; + } + pos += n; + + return n; + } + } private final int dataLen = 20000; private final byte[] testData = new byte[dataLen]; private byte[] encData; @@ -57,9 +143,10 @@ public class PositionedCryptoInputStreamTest { private final int bufferSizeMore = bufferSize + 1; private final int length = 1024; private final int lengthLess = length - 1; + private final int lengthMore = length + 1; - private final String transformation = "AES/CTR/NoPadding"; + private final String transformation = AES.CTR_NO_PADDING; @BeforeEach public void before() throws IOException { @@ -70,65 +157,32 @@ public void before() throws IOException { prepareData(); } - private void prepareData() throws IOException { - final CryptoCipher cipher; - try { - cipher = (CryptoCipher) ReflectionUtils.newInstance( - ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props, - transformation); - } catch (final ClassNotFoundException cnfe) { - throw new IOException("Illegal crypto cipher!"); - } - - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - // encryption data - try (final OutputStream out = new CryptoOutputStream(baos, cipher, bufferSize, - new SecretKeySpec(key, "AES"), new IvParameterSpec(iv))) { - out.write(testData); - out.flush(); - } - encData = baos.toByteArray(); - } - - private PositionedCryptoInputStream getCryptoInputStream( - final CryptoCipher cipher, final int bufferSize) throws IOException { - return new PositionedCryptoInputStream(props, new PositionedInputForTest( - Arrays.copyOf(encData, encData.length)), cipher, bufferSize, - key, iv, 0); - } - - private PositionedCryptoInputStream getCryptoInputStream(final int streamOffset) - throws IOException { - return new PositionedCryptoInputStream(props, new PositionedInputForTest( - Arrays.copyOf(encData, encData.length)), key, iv, streamOffset); - } - - @Test - public void doTestJCE() throws Exception { - testCipher(AbstractCipherTest.JCE_CIPHER_CLASSNAME); - } - - @Test - public void doTestJNI() throws Exception { - assumeTrue(Crypto.isNativeCodeLoaded()); - testCipher(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME); + /** compare the data from pos with length and data2 from 0 with length. */ + private void compareByteArray(final byte[] data1, final int pos, final byte[] data2, + final int length) { + final byte[] expectedData = new byte[length]; + final byte[] realData = new byte[length]; + // get the expected data with the position and length + System.arraycopy(data1, pos, expectedData, 0, length); + // get the real data + System.arraycopy(data2, 0, realData, 0, length); + assertArrayEquals(expectedData, realData); } - protected void testCipher(final String cipherClass) throws Exception { - doPositionedReadTests(cipherClass); - doPositionedReadTests(); - doReadFullyTests(cipherClass); - doReadFullyTests(); - doSeekTests(cipherClass); - doSeekTests(); - doMultipleReadTest(cipherClass); - doMultipleReadTest(); + private void doMultipleReadTest() throws Exception { + try (PositionedCryptoInputStream in = getCryptoInputStream(0); + CryptoCipher cipher = in.getCipher()) { + doMultipleReadTest(cipher.getClass().getName()); + } } - // when there are multiple positioned read actions and one read action, - // they will not interfere each other. + /** + * when there are multiple positioned read actions and one read action, + * they will not interfere each other. + */ private void doMultipleReadTest(final String cipherClass) throws Exception { - try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { + try (CryptoCipher cipher = getCipher(cipherClass); + PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { int position = 0; while (in.available() > 0) { final ByteBuffer buf = ByteBuffer.allocate(length); @@ -148,20 +202,20 @@ private void doMultipleReadTest(final String cipherClass) throws Exception { compareByteArray(testData, position, bytes2, pn2); } - if (n > 0) { - compareByteArray(testData, position, buf.array(), n); - position += n; - } else { + if (n <= 0) { break; } + compareByteArray(testData, position, buf.array(), n); + position += n; } } } - private void doMultipleReadTest() throws Exception{ - final PositionedCryptoInputStream in = getCryptoInputStream(0); - final String cipherClass = in.getCipher().getClass().getName(); - doMultipleReadTest(cipherClass); + private void doPositionedReadTests() throws Exception { + try (PositionedCryptoInputStream in = getCryptoInputStream(0); + CryptoCipher cipher = in.getCipher()) { + doPositionedReadTests(cipher.getClass().getName()); + } } private void doPositionedReadTests(final String cipherClass) throws Exception { @@ -181,10 +235,11 @@ private void doPositionedReadTests(final String cipherClass) throws Exception { testPositionedReadNone(cipherClass, dataLen, length, bufferSize); } - private void doPositionedReadTests() throws Exception { - final PositionedCryptoInputStream in = getCryptoInputStream(0); - final String cipherClass = in.getCipher().getClass().getName(); - doPositionedReadTests(cipherClass); + private void doReadFullyTests() throws Exception { + try (PositionedCryptoInputStream in = getCryptoInputStream(0); + CryptoCipher cipher = in.getCipher()) { + doReadFullyTests(cipher.getClass().getName()); + } } private void doReadFullyTests(final String cipherClass) throws Exception { @@ -203,10 +258,11 @@ private void doReadFullyTests(final String cipherClass) throws Exception { bufferSize); } - private void doReadFullyTests() throws Exception { - final PositionedCryptoInputStream in = getCryptoInputStream(0); - final String cipherClass = in.getCipher().getClass().getName(); - doReadFullyTests(cipherClass); + private void doSeekTests() throws Exception { + try (PositionedCryptoInputStream in = getCryptoInputStream(0); + CryptoCipher cipher = in.getCipher()) { + doSeekTests(cipher.getClass().getName()); + } } private void doSeekTests(final String cipherClass) throws Exception { @@ -220,68 +276,110 @@ private void doSeekTests(final String cipherClass) throws Exception { testSeekFailed(cipherClass, -1, bufferSize); } - private void doSeekTests() throws Exception{ - final PositionedCryptoInputStream in = getCryptoInputStream(0); - final String cipherClass = in.getCipher().getClass().getName(); - doSeekTests(cipherClass); + private CryptoCipher getCipher(final String cipherClass) throws IOException { + try { + return (CryptoCipher) ReflectionUtils.newInstance( + ReflectionUtils.getClassByName(cipherClass), props, + transformation); + } catch (final ClassNotFoundException cnfe) { + throw new IOException("Illegal crypto cipher!"); + } } - private void testSeekLoop(final String cipherClass, int position, final int length, - final int bufferSize) throws Exception { - try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { - while (in.available() > 0) { - in.seek(position); - final ByteBuffer buf = ByteBuffer.allocate(length); - final int n = in.read(buf); - if (n > 0) { - compareByteArray(testData, position, buf.array(), n); - position += n; - } else { - break; - } + private PositionedCryptoInputStream getCryptoInputStream( + final CryptoCipher cipher, final int bufferSize) throws IOException { + return new PositionedCryptoInputStream(props, new PositionedInputForTest( + Arrays.copyOf(encData, encData.length)), cipher, bufferSize, + key, iv, 0); + } + + private PositionedCryptoInputStream getCryptoInputStream(final int streamOffset) + throws IOException { + return new PositionedCryptoInputStream(props, new PositionedInputForTest( + Arrays.copyOf(encData, encData.length)), key, iv, streamOffset); + } + + private void prepareData() throws IOException { + try (CryptoCipher cipher = (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props, + transformation)) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encryption data + try (final OutputStream out = new CryptoOutputStream(baos, cipher, bufferSize, AES.newSecretKeySpec(key), new IvParameterSpec(iv))) { + out.write(testData); + out.flush(); } + encData = baos.toByteArray(); + } catch (final ClassNotFoundException cnfe) { + throw new IOException("Illegal crypto cipher!"); } } - // test for the out of index position, eg, -1. - private void testSeekFailed(final String cipherClass, final int position, final int bufferSize) - throws Exception { - try (final PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { - assertThrows(IllegalArgumentException.class, () -> in.seek(position)); - } + protected void testCipher(final String cipherClass) throws Exception { + doPositionedReadTests(cipherClass); + doPositionedReadTests(); + doReadFullyTests(cipherClass); + doReadFullyTests(); + doSeekTests(cipherClass); + doSeekTests(); + doMultipleReadTest(cipherClass); + doMultipleReadTest(); + } + + @Test + public void testJCE() throws Exception { + testCipher(AbstractCipherTest.JCE_CIPHER_CLASSNAME); + } + + @Test + public void testJNI() throws Exception { + assumeTrue(Crypto.isNativeCodeLoaded()); + testCipher(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME); } private void testPositionedReadLoop(final String cipherClass, int position, final int length, final int bufferSize, final int total) throws Exception { - try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { + try (CryptoCipher cipher = getCipher(cipherClass); + PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { // do the position read until the end of data while (position < total) { final byte[] bytes = new byte[length]; final int n = in.read(position, bytes, 0, length); - if (n >= 0) { - compareByteArray(testData, position, bytes, n); - position += n; - } else { + if (n < 0) { break; } + compareByteArray(testData, position, bytes, n); + position += n; } } } - // test for the out of index position, eg, -1. + /** test for the out of index position, eg, -1. */ private void testPositionedReadNone(final String cipherClass, final int position, final int length, final int bufferSize) throws Exception { - try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { + try (CryptoCipher cipher = getCipher(cipherClass); + PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { final byte[] bytes = new byte[length]; final int n = in.read(position, bytes, 0, length); assertEquals(n, -1); } } + /** test for the End of file reached before reading fully. */ + private void testReadFullyFailed(final String cipherClass, final int position, + final int length, final int bufferSize) throws Exception { + try (CryptoCipher cipher = getCipher(cipherClass); + final PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { + final byte[] bytes = new byte[length]; + assertThrows(IOException.class, () -> in.readFully(position, bytes, 0, length)); + in.close(); + in.close(); // Don't throw exception. + } + } + private void testReadFullyLoop(final String cipherClass, int position, final int length, final int bufferSize, final int total) throws Exception { - try (PositionedCryptoInputStream in = getCryptoInputStream( - getCipher(cipherClass), bufferSize)) { + try (CryptoCipher cipher = getCipher(cipherClass); + PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { // do the position read full until remain < length while (position + length <= total) { @@ -294,121 +392,29 @@ private void testReadFullyLoop(final String cipherClass, int position, } } - // test for the End of file reached before reading fully - private void testReadFullyFailed(final String cipherClass, final int position, - final int length, final int bufferSize) throws Exception { - try (final PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) { - final byte[] bytes = new byte[length]; - assertThrows(IOException.class, () -> in.readFully(position, bytes, 0, length)); - in.close(); - in.close(); // Don't throw exception. - } - } - - // compare the data from pos with length and data2 from 0 with length - private void compareByteArray(final byte[] data1, final int pos, final byte[] data2, - final int length) { - final byte[] expectedData = new byte[length]; - final byte[] realData = new byte[length]; - // get the expected data with the position and length - System.arraycopy(data1, pos, expectedData, 0, length); - // get the real data - System.arraycopy(data2, 0, realData, 0, length); - assertArrayEquals(expectedData, realData); - } - - private CryptoCipher getCipher(final String cipherClass) throws IOException { - try { - return (CryptoCipher) ReflectionUtils.newInstance( - ReflectionUtils.getClassByName(cipherClass), props, - transformation); - } catch (final ClassNotFoundException cnfe) { - throw new IOException("Illegal crypto cipher!"); + /** test for the out of index position, eg, -1. */ + private void testSeekFailed(final String cipherClass, final int position, final int bufferSize) + throws Exception { + try (CryptoCipher cipher = getCipher(cipherClass); + final PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { + assertThrows(IllegalArgumentException.class, () -> in.seek(position)); } } - static class PositionedInputForTest implements Input { - - final byte[] data; - long pos; - final long count; - - public PositionedInputForTest(final byte[] data) { - this.data = data; - this.pos = 0; - this.count = data.length; - } - - @Override - public int read(final ByteBuffer dst) { - final int remaining = (int) (count - pos); - if (remaining <= 0) { - return -1; - } - - final int length = Math.min(dst.remaining(), remaining); - dst.put(data, (int) pos, length); - pos += length; - return length; - } - - @Override - public long skip(long n) { - if (n <= 0) { - return 0; - } - - final long remaining = count - pos; - if (remaining < n) { - n = remaining; - } - pos += n; - - return n; - } - - @Override - public int read(final long position, final byte[] buffer, final int offset, int length) { - Objects.requireNonNull(buffer, "buffer"); - if (offset < 0 || length < 0 - || length > buffer.length - offset) { - throw new IndexOutOfBoundsException(); - } - - if (position < 0 || position >= count) { - return -1; - } - - final long avail = count - position; - if (length > avail) { - length = (int) avail; - } - if (length <= 0) { - return 0; - } - System.arraycopy(data, (int) position, buffer, offset, length); - return length; - } - - @Override - public void seek(final long position) throws IOException { - if (pos < 0) { - throw new IOException("Negative seek offset"); - } else if (position >= 0 && position < count) { - pos = position; - } else { - // to the end of file - pos = count; + private void testSeekLoop(final String cipherClass, int position, final int length, + final int bufferSize) throws Exception { + try (CryptoCipher cipher = getCipher(cipherClass); + PositionedCryptoInputStream in = getCryptoInputStream(cipher, bufferSize)) { + while (in.available() > 0) { + in.seek(position); + final ByteBuffer buf = ByteBuffer.allocate(length); + final int n = in.read(buf); + if (n <= 0) { + break; + } + compareByteArray(testData, position, buf.array(), n); + position += n; } } - - @Override - public void close() { - } - - @Override - public int available() { - return (int) (count - pos); - } } } diff --git a/src/test/java/org/apache/commons/crypto/utils/EnumTest.java b/src/test/java/org/apache/commons/crypto/utils/EnumTest.java index 392ba5e7b..194de85f0 100644 --- a/src/test/java/org/apache/commons/crypto/utils/EnumTest.java +++ b/src/test/java/org/apache/commons/crypto/utils/EnumTest.java @@ -1,24 +1,29 @@ - /* - * 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. - */ +/* +* 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. +*/ package org.apache.commons.crypto.utils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.commons.crypto.cipher.CryptoCipher; import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.cipher.CryptoCipherFactory.CipherProvider; +import org.apache.commons.crypto.random.CryptoRandom; import org.apache.commons.crypto.random.CryptoRandomFactory; import org.apache.commons.crypto.random.CryptoRandomFactory.RandomProvider; import org.junit.jupiter.api.Test; @@ -28,17 +33,31 @@ */ public class EnumTest { + private void checkImplClass(final CipherProvider value) { + final Class extends CryptoCipher> implClass = value.getImplClass(); + assertTrue(CryptoCipher.class.isAssignableFrom(implClass), implClass.toString()); + assertEquals(value.getClassName(), implClass.getName()); + } + + private void checkImplClass(final RandomProvider value) { + final Class extends CryptoRandom> implClass = value.getImplClass(); + assertTrue(CryptoRandom.class.isAssignableFrom(implClass), implClass.toString()); + assertEquals(value.getClassName(), implClass.getName()); + } + @Test - public void testRandom() throws Exception { - for(final RandomProvider value : CryptoRandomFactory.RandomProvider.values()) { + public void testCipher() throws Exception { + for (final CipherProvider value : CryptoCipherFactory.CipherProvider.values()) { ReflectionUtils.getClassByName(value.getClassName()); + checkImplClass(value); } } @Test - public void testCipher() throws Exception { - for(final CipherProvider value : CryptoCipherFactory.CipherProvider.values()) { + public void testRandom() throws Exception { + for (final RandomProvider value : CryptoRandomFactory.RandomProvider.values()) { ReflectionUtils.getClassByName(value.getClassName()); + checkImplClass(value); } } diff --git a/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java b/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java index ee151b167..33965f96c 100644 --- a/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java +++ b/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java @@ -28,6 +28,21 @@ public class UtilsTest { + @Test + public void testGetProperties() { + final Properties props = new Properties(); + props.setProperty( + "garbage.in", + "out"); + final Properties allprops = Utils.getProperties(props); + assertEquals(allprops.getProperty("garbage.in"), "out"); + } + + @Test + public void testSplitNull() { + assertEquals(Collections. emptyList(), Utils.splitClassNames(null, ",")); + } + @Test public void testSplitOmitEmptyLine() { List clazzNames = Utils.splitClassNames("", ","); @@ -40,19 +55,4 @@ public void testSplitOmitEmptyLine() { clazzNames = Utils.splitClassNames("a, b,", ","); assertEquals(Arrays.asList("a", "b"), clazzNames); } - - @Test - public void testSplitNull() { - assertEquals(Collections. emptyList(), Utils.splitClassNames(null, ",")); - } - - @Test - public void testGetProperties() { - final Properties props = new Properties(); - props.setProperty( - "garbage.in", - "out"); - final Properties allprops = Utils.getProperties(props); - assertEquals(allprops.getProperty("garbage.in"), "out"); - } }