diff --git a/AUTHORS b/AUTHORS index 164a93b..c1751b3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Stephan Fuhrmann - +Daniel Thertell +Jim Showalter diff --git a/Dockerfile b/Dockerfile index e1cb216..0d67ec0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,15 @@ -# -# Dockerfile for building a per-platform build -# in github actions. -# -# See .github/workflows/build.yml -# -FROM debian:11 +FROM debian:10 ENV JAVA_HOME=/opt/java/openjdk COPY --from=eclipse-temurin:11 $JAVA_HOME $JAVA_HOME ENV PATH="${JAVA_HOME}/bin:${PATH}" RUN apt-get update && apt-get install -y \ - make gcc libssl1.1 libssl-dev +make gcc libssl1.1 libssl-dev COPY . openssl4j ENV JAVA_HOME=/opt/java/openjdk/ RUN echo "JAVA_HOME is ${JAVA_HOME}" RUN echo "OS_ARCH is $(cd openssl4j/build-helper && ${JAVA_HOME}/bin/java -Xint OsArch.java)" RUN cd openssl4j && \ - make +make RUN cd openssl4j/target && ls -al diff --git a/LICENSE b/LICENSE index d645695..65c5ca8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Makefile b/Makefile index bb6f667..4a70f99 100644 --- a/Makefile +++ b/Makefile @@ -3,43 +3,107 @@ # Makefile for generating the native C library # #### -JAVA_OS_ARCH:=$(shell cd build-helper && ${JAVA_HOME}/bin/java -Xint OsArch.java ) +JAVA_OS_ARCH:=$(shell cd build-helper && ${JAVA_HOME}/bin/javac OsArch.java && ${JAVA_HOME}/bin/java -cp . OsArch) +CUR_DIR := $(shell pwd) + + +$(info JavaArch: ${JAVA_OS_ARCH}) +$(info Cur Dir: ${CUR_DIR}) + +BASE_DIR?=/openssl4j JNI_JAVA_SOURCES=openssl4j/src/main/java -JNI_C_SOURCES=openssl4j-objects/src/main/c -TARGET=target -INSTALL_TARGET=openssl4j-objects/src/main/resources/objects -JNI_JAVA_FILES=${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java -JNI_HEADER_FILES=${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLMessageDigestNative.h +JNI_C_SOURCES=openssl4j/src/main/c +JNI_C_TEST_SOURCES=openssl4j/src/test/c +TARGET=${BASE_DIR}/openssl4j/c +INSTALL_TARGET=openssl4j/src/main/resources/objects +JNI_JAVA_FILES=${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java ${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLCipherNative.java ${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java ${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLSecureRandomNative.java ${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLMacNative.java +JNI_HEADER_FILES=${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLMessageDigestNative.h ${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLCipherNative.h ${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLCryptoNative.h ${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLSecureRandomNative.h ${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLMacNative.h +empty:= +space:= $(empty) $(empty) +escapedSpace := \$(space) + +UNAME_S := $(shell uname -s) + +libs:= +test_libs := ${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so + +INCLUDES= -I${TARGET}/include/ -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -I/usr/local/include/openssl +TEST_INCLUDES = -I${TARGET}/include/ -I${JNI_C_SOURCES}/ -I${JAVA_HOME}/include -I/usr/local/include/openssl + +$(info Target: ${TARGET}) + +ifeq ("${JAVA_OS_ARCH}", "Mac_OS_X-aarch64") +$(info Building for Apple arm) +INCLUDES+= -I${JAVA_HOME}/include/darwin -I${BASE_DIR}/OpenSSL/include +TEST_INCLUDES+= -I${JAVA_HOME}/include/darwin -I${BASE_DIR}/OpenSSL/include +libs+= -L${BASE_DIR}/OpenSSL/lib/ ${BASE_DIR}/OpenSSL/lib/libssl.dylib ${BASE_DIR}/OpenSSL/lib/libcrypto.dylib +else ifeq ("${JAVA_OS_ARCH}", "Mac_OS_X-x86_64") +$(info Building for Apple x86) +INCLUDES+= -I${JAVA_HOME}/include/darwin -I${BASE_DIR}/OpenSSL/include +TEST_INCLUDES+= -I${JAVA_HOME}/include/darwin -I${BASE_DIR}/OpenSSL/include +libs+= -L${BASE_DIR}/OpenSSL/lib/ ${BASE_DIR}/OpenSSL/lib/libssl.dylib ${BASE_DIR}/OpenSSL/lib/libcrypto.dylib +else +$(info Building for RedHat x86) +INCLUDES+= -I/usr/local/include/openssl +TEST_INCLUDES+= -I${JAVA_HOME}/include/linux +libs+= -L/lib64/ /lib64/libssl.so.3 /lib64/libcrypto.so.3 +endif .PHONY: all .PHONY: clean .PHONY: install install: ${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so - mkdir -p ${INSTALL_TARGET} - cp $< ${INSTALL_TARGET} - + sudo mkdir -p ${INSTALL_TARGET} + sudo cp $< ${INSTALL_TARGET} + cp ./openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java temp.java + ls -lah ./openssl4j/src/main/java/de/sfuhrm/openssl4j/ + sed 's@private static String openssl4JBasePath = "\/openssl4j";@private static String openssl4JBasePath = "\${BASE_DIR}";@g' ./temp.java > ./openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java clean: rm -fr ${TARGET} ${INSTALL_TARGET} +buildTest: + mkdir output +ifeq ($(UNAME_S),Darwin) + gcc -Wall -Werror -fPIC -o output/openssl4jTest -lc -g -O0 -Wl,-v \ + -Wl,-rpath,/usr/local/lib64,-rpath,@loader_path ${test_libs} ${TEST_INCLUDES} \ + ${JNI_C_TEST_SOURCES}/main.c +else + sudo gcc -Wall -Werror -fPIC -o output/openssl4jTest -lc -g -O0 -Wl,-v \ + -Wl,-z,defs -Wl,-rpath,/lib64/ossl-modules,-rpath,'$$ORIGIN',-rpath,/lib64 -Wl,-z,origin ${TEST_INCLUDES} \ + ${JNI_C_TEST_SOURCES}/main.c \ + ${test_libs} +endif + cp ${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so output/libopenssl4j-${JAVA_OS_ARCH}.so + $(info Test files copied to ./output/) + + ${TARGET}/include/%.h: ${JNI_JAVA_FILES} - mkdir -p ${TARGET}/include - ${JAVA_HOME}/bin/javac -J-Xint -classpath ${JNI_JAVA_SOURCES} -h ${TARGET}/include -d ${TARGET} -s ${TARGET} ${JNI_JAVA_FILES} - -${TARGET}/%.o: ${JNI_C_SOURCES}/%.c ${JNI_HEADER_FILES} - gcc -Wall -Werror -fPIC -c -o $@ \ - -I${TARGET}/include/ \ - -I${JAVA_HOME}/include \ - -I${JAVA_HOME}/include/linux \ - $< - -${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so: ${TARGET}/openssl4j_common.o ${TARGET}/openssl4j_messagedigest.o - # link libssl statically, libc dynamically - # this avoids the need for specific libssl versions - # in the system - ld --verbose --pic-executable -fPIC -shared -o $@ \ - ${TARGET}/openssl4j_common.o \ - ${TARGET}/openssl4j_messagedigest.o \ - --whole-archive -Bstatic -lssl \ - --no-whole-archive -Bdynamic -lcrypto -lc + sudo mkdir -p ${TARGET}/include + mvn install -DskipTests + sudo ${JAVA_HOME}/bin/javac -J-Xint -classpath ${JNI_JAVA_SOURCES} -h ${TARGET}/include -d ${TARGET} -s ${TARGET} ${JNI_JAVA_FILES} + +${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so: ${JNI_HEADER_FILES} + $(info Includes: ${INCLUDES}) +ifeq ($(UNAME_S),Darwin) + sudo gcc -Wall -Werror -fPIC -o "$@" -lc -Wl,-v \ + -Wl,-rpath,/usr/local/lib64,-rpath,@loader_path ${libs} -shared ${INCLUDES} \ + ${JNI_C_SOURCES}/openssl4j_common.c \ + ${JNI_C_SOURCES}/openssl4j_messagedigest.c \ + ${JNI_C_SOURCES}/openssl4j_cipher.c \ + ${JNI_C_SOURCES}/openssl4j_crypto.c \ + ${JNI_C_SOURCES}/openssl4j_secureRandom.c \ + ${JNI_C_SOURCES}/openssl4j_mac.c + $(info Output File: "$@") +else + sudo gcc -Wall -Werror -fPIC -o "$@" -lc -Wl,-v \ + -Wl,-z,defs -Wl,-rpath,/lib64/ossl-modules,-rpath,'$$ORIGIN',-rpath,/lib64 -Wl,-z,origin -shared ${INCLUDES} \ + ${JNI_C_SOURCES}/openssl4j_common.c \ + ${JNI_C_SOURCES}/openssl4j_messagedigest.c \ + ${JNI_C_SOURCES}/openssl4j_cipher.c \ + ${JNI_C_SOURCES}/openssl4j_crypto.c \ + ${JNI_C_SOURCES}/openssl4j_secureRandom.c \ + ${JNI_C_SOURCES}/openssl4j_mac.c ${libs} + $(info Output File: "$@") +endif \ No newline at end of file diff --git a/README.md b/README.md index f7c456d..e01cb46 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,51 @@ OpenSSL4J JNI Java Library =================== - -[![Single-Platform Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-singleplatform.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-singleplatform.yml) -[![Java Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-java.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-java.yml) -[![Crossplatform Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-crossplatform.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-crossplatform.yml) +![Travis CI Status](https://travis-ci.org/sfuhrm/openssl4j.svg?branch=master) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.sfuhrm/openssl4j/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.sfuhrm/openssl4j) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - -OpenSSL4J is a Java bridge to the native OpenSSL library. -On the Java side it's offering the -conventional [MessageDigest](https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html) class. In the background the calls -will be translated to the native OpenSSL library with all its -[optimizations](https://www.openssl.org/docs/faq-4-build.txt): - -> On x86, the assembly code uses the CPUID instruction (see the -> OPENSSL_ia32cap.pod manpage) to determine if various instructions (AES, -> SSE, MMX, etc) are available and will use them if so. For other processors, -> similar tests are performed if at all possible. - -## Features - -* Performance: The main feature of OpenSSL4J is performance: The MD5-implementation of OpenSSL4J is -typically 67% to 102% faster than the pure Java version from SUN. -* Functionality: There are some algorithms available in OpenSSL4J that are not available in the normal SUN crypto provider. - -## Performance - -The following picture shows a performance comparison of - -* BouncyCastle crypto provider (version 1.70) -* Adoptium JDK SUN crypto provider (JDK 17.0.6) -* OpenSSL4j (version 0.3.0) +[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) -Each bar shows different throughputs in megabytes per second. -The per-bar throughputs contain multiple different test scenarios -regarding blocks sizes and data structures used for -data passing (byte, array, direct ByteBuffer, heap ByteBuffer). -The median of the tests is presented by a dark-blue horizontal line -within the bar. The 25% and 75% quantile make up the -area of the bars. +OpenSSL4J is a Java bridge to the native OpenSSL library. On the Java side you're +using the conventional MessageDigest class, but this library calls in the +background the native OpenSSL library with all its +optimizations for performance reasons. -![bc-sun-ossl-performance.png](./images/bc-sun-ossl-performance.png) - -The benchmark was conducted on a i7-3840QM CPU. - -## Building OpenSSL4J for your platform +## Building OpenSSL4J For building the application you need -* JDK 8+, +* JDK 8, * Apache Maven, * GNU Make, * GNU GCC, * OpenSSL development headers -To build the C library for your current platform, wrap it into a maven artifact (openssl4j-objects), build the java parts (openssl4j), execute: +To build the C library and install it to the right place in `openssl4j/src/main/resources/objects`, execute: -```bash -$ build.sh -... -[INFO] Reactor Summary for OpenSSL4J Parent 0.2.1-SNAPSHOT: -[INFO] -[INFO] OpenSSL4J Parent ................................... SUCCESS [ 0.953 s] -[INFO] OpenSSL4J JNI ...................................... SUCCESS [ 5.859 s] -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.912 s -[INFO] Finished at: 2023-05-28T20:38:43+02:00 -[INFO] ------------------------------------------------------------------------ -``` + $ make -## Building OpenSSL4J for cross-platform +To build the Java package, execute: -The current cross-platform build is driven by github actions, using QEMU -to build different platform shared object library. -The github actions are visible to everyone. -For the cross-platform build to work with your fork, there -are some project secrets needed to be set in your -Github fork settings: + $ mvn clean package -* DOCKERHUB_USERNAME: Dockerhub username for getting the parent of the build image. -* DOCKERHUB_TOKEN: Dockerhub secret token. -* GH_USER: Github username for storing artifacts. -* GH_PASSWORD: Github password for storing artifacts. -* SONATYPE_USER: (optional) sonatype username for pushing snapshots. -* SONATYPE_PASSWORD: (optional) sonatype password for pushing snapshots. +## Features -(Date of last update: 2023-05-28) +* Performance: The main feature of OpenSSL4J is performance: The MD5-implementation of OpenSSL4J is + typically 67% to 102% faster than the pure Java version from SUN. +* Functionality: There are some algorithms available in OpenSSL4J that are not available in the + normal SUN crypto provider. +* FIPS: When compiled against OpenSSL FIPS + (https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4271.pdf, + https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4282.pdf), + this library delivers FIPS-140-2 compliance. This is preferable to using bc-fips in combination with bouncy-castle, + because this library doesn't know or care at all about Java, specific Java versions, or classpath ordering. You can + use any version of Java you want, with whatever classpath you desire. And, unlike with bc-fips, you don't have to wait + years for the next Java version to be supported. ## Restrictions -* MessageDigest restriction: The current milestone only contains MessageDigest algorithms. -* Restricted platforms: The code uses dynamic linking to an object library on the machine. - Native object code within the JAR file is used for binding the Java code to the native code. - There is a restricted amount of platforms supported by the Github Actions - builder (see below). +* This library is not a full wrapper over Open SSL. All message digests are supported, but only those ciphers, + algorithms, HMAC, and SecureRandom needed for FIPS are currently supported. However, there is no technical reason full + Open SSL support can't be added, and we welcome contributions. ## Usage @@ -111,9 +61,9 @@ import de.sfuhrm.openssl4j.OpenSSL4JProvider; ... -MessageDigest messageDigest = MessageDigest.getInstance("MD5", new OpenSSL4JProvider()); -messageDigest.update("hello world!".getBytes(Charset.forName("ASCII"))); -byte[] digest = messageDigest.digest(); + MessageDigest messageDigest = MessageDigest.getInstance("MD5", OpenSSL4JProvider.getInstance()); + messageDigest.update("hello world!".getBytes(Charset.forName("ASCII"))); + byte[]digest=messageDigest.digest(): ``` --------------------------------------- @@ -171,17 +121,16 @@ The recommended way of including the library into your project is using maven: --------------------------------------- ```xml + - de.sfuhrm - openssl4j - 0.5.0 + de.sfuhrm + openssl4j + 0.2.0 ``` --------------------------------------- -## Native platforms supported - There are the following native implementations available inside the JAR file: * Linux-aarch64 @@ -196,20 +145,15 @@ Please note that the current version is experimental. ## Versions -The version numbers used by `openssl4j` itself comply to the +The version numbers comply to the [semantic versioning](https://semver.org/) schema. Especially major version changes come with breaking API changes. -The temporary internal `openssl4j-objects` artifact is using -date-derived versions, but it is invisible to maven users. - ## Author -Written 2020-2023 by Stephan Fuhrmann. You can reach me via email to s (at) sfuhrm.de +Written 2020-2022 by Stephan Fuhrmann. You can reach me via email to s (at) sfuhrm.de ## License -The project *is* licensed under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) after excluding OpenSSL4j release v0.3.0. - -The project *was* licensed under [LGPL 3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html) until including OpenSSL4j release v0.3.0. +The project is licensed under [LGPL 3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). diff --git a/openssl4j/pom.xml b/openssl4j/pom.xml index ba7e46d..407078e 100644 --- a/openssl4j/pom.xml +++ b/openssl4j/pom.xml @@ -1,16 +1,17 @@ - + 4.0.0 de.sfuhrm openssl4j-parent - 0.5.1-SNAPSHOT + 0.3.1-SNAPSHOT openssl4j OpenSSL4J JNI Java binding to OpenSSL library functions - 2023-10-21-08-46-22 + 2023-05-28-18-05-08 @@ -57,10 +58,10 @@ - org.bouncycastle - bcprov-jdk18on - 1.77 - test + org.bouncycastle + bcprov-jdk15on + 1.70 + test diff --git a/openssl4j/src/main/c/openssl4j.h b/openssl4j/src/main/c/openssl4j.h new file mode 100644 index 0000000..9dc392e --- /dev/null +++ b/openssl4j/src/main/c/openssl4j.h @@ -0,0 +1,59 @@ +/* +** OpenSSL to Java Header Code +** @author Stephan Fuhrmann +** @author Daniel Thertell +*/ + +#ifndef OPENSSL4J_H +#define OPENSSL4J_H + +#include +#include + +#define NULL_POINTER_EXCEPTION "java/lang/NullPointerException" +#define ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException" +#define UNSUPPORTED_OPERATION_EXCEPTION "java/lang/UnsupportedOperationException" + + +struct StringArrayPosition { + /* The next write index in the array below. */ + jint index; + /* The length of the array below. */ + jint length; + /* The JNI env to use for doing JNI environment calls. */ + JNIEnv *env; + /* The String array to store the names in. */ + jobjectArray array; +}; + +struct OpenSSLProviderHolder { + OSSL_PROVIDER *baseProvider; + OSSL_PROVIDER *legacyProvider; + OSSL_PROVIDER *fipsProvider; + OSSL_LIB_CTX *lib; +}; + +/* +* Throws an exception. Actually signals the JVM that an exception shall be thrown. +* C methods need to terminate normally. +* @param env the JNI environment. +* @param exceptionClassName the Java Name of the exception to throw, for example java/lang/NullPointerException. +* @param message the exception message to pass to the exception constructor. +*/ +void throw_error(JNIEnv *env, const char *exceptionClassName, const char *message); + + +void throwErrorWithOpenSSLInternalError(JNIEnv *env, const char *exceptionClassName, const char *message); + +/* +* Returns the MD / Crypto context from a passed in ByteBuffer jobject. +* @param env the JNI environment. +* @param context a pointer to the context ByteBuffer object. +*/ +void* get_context_from(JNIEnv *env, jobject context); + +EVP_MD* GetMessageDigest(const char* OriginalName, long libCtx); + +unsigned long long createOpenSSLLibNative(int setFips, const char* confLocation, const char* libLocations, int *errCode); + +#endif diff --git a/openssl4j/src/main/c/openssl4j_cipher.c b/openssl4j/src/main/c/openssl4j_cipher.c new file mode 100644 index 0000000..9db8356 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_cipher.c @@ -0,0 +1,476 @@ +/* +** OpenSSL Cipher to Java Binding Code +** See here for an example of the EVP API: +** https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption +** @author Stephan Fuhrmann +** @author Daniel Thertell +*/ + +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#elif __linux__ +#include +#endif + +#include "openssl4j.h" +#include "openssl4j_cipher.h" + +#include "de_sfuhrm_openssl4j_OpenSSLCipherNative.h" + +/* Callback for EVP_CIPHER_do_all that counts the number of CIPHER algorithms. */ +static void EVP_CIPHER_do_all_count_func(const EVP_CIPHER *ciph, const char *from, const char *to, void *x) { + if (ciph != NULL) { + jint *numOfAlgos = (jint*)x; + (*numOfAlgos)++; + } +} + +/* Callback for EVP_CIPHER_do_all that sets the string array elements. +** @param ciph cipher, can be NULL if this is an alias. +** @param from the name of the algorithm. +** @param to NULL if this is not an alias, or the target EVP_MD if this is a an alias. +** @param x the last param passed to the EVP_MD_do_all() call. +*/ +static void EVP_CIPHER_do_all_string_array_set(const EVP_CIPHER *ciph, const char *from, const char *to, void *x) { + struct StringArrayPosition *sap = (struct StringArrayPosition*)x; + if (ciph == NULL) { + // alias + return; + } + const char *evp_name = EVP_CIPHER_name(ciph); + + jstring algoNameString = (*sap->env)->NewStringUTF(sap->env, evp_name); + if (algoNameString == NULL) { + return; + } + + (*sap->env)->SetObjectArrayElement(sap->env, sap->array, sap->index, algoNameString); + sap->index++; +} + +// Looks up cipher by name, corrects _ to - and attempts to use the EVP lookup first, +// falling back to the EVP cipher functions if the EVP lookup fails. +EVP_CIPHER* GetCipher(const char* OriginalName, long libCtx) { + + char FixedName[strlen(OriginalName)]; + + strcpy(FixedName, OriginalName); + + for(int i = 0; i < strlen(FixedName); i++) { + if(FixedName[i] == '_') { + FixedName[i] = '-'; + } + } + + EVP_CIPHER* cipher = NULL; + if(libCtx != 0) { + struct OpenSSLProviderHolder* handle = (struct OpenSSLProviderHolder*)libCtx; + cipher = EVP_CIPHER_fetch(handle->lib, OriginalName, "-default"); + }else { + cipher = EVP_CIPHER_fetch(NULL, OriginalName, "-default"); + } + + if(cipher == NULL) { + if(strcmp(FixedName, "AES-256-GCM") == 0) { + return (EVP_CIPHER*)EVP_aes_256_gcm(); + }else if (strcmp(FixedName, "AES-256-CTR") == 0) { + return (EVP_CIPHER*)EVP_aes_256_ctr(); + }else if (strcmp(FixedName, "AES-256-CBC") == 0) { + return (EVP_CIPHER*)EVP_aes_256_cbc(); + }else if (strcmp(FixedName, "AES-128-CBC") == 0) { + return (EVP_CIPHER*)EVP_aes_128_cbc(); + }else if (strcmp(FixedName, "AES-256-ECB") == 0) { + return (EVP_CIPHER*)EVP_aes_256_ecb(); + }else if (strcmp(FixedName, "AES-128-ECB") == 0) { + return (EVP_CIPHER*)EVP_aes_128_ecb(); + } + } + + return cipher; +} + +JNIEXPORT jobjectArray JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_listCiphers + (JNIEnv *env, jclass clazz) { + struct StringArrayPosition sap; + jobjectArray result = NULL; + + sap.index = 0; + sap.length = 0; + sap.env = env; + sap.array = NULL; + + EVP_CIPHER_do_all(EVP_CIPHER_do_all_count_func, &sap.length); + jclass stringClass = (*env)->FindClass(env, "java/lang/String"); + if (stringClass == NULL) { + return NULL; + } + + result = (*env)->NewObjectArray(env, sap.length, stringClass, NULL); + sap.array = result; + + EVP_CIPHER_do_all(EVP_CIPHER_do_all_string_array_set, &sap); + + return result; +} + +// Creates a context for the cipher. +JNIEXPORT jobject JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_nativeContext + (JNIEnv *env, jobject obj) { + EVP_CIPHER_CTX *cipherctx; + + if ((cipherctx = EVP_CIPHER_CTX_new()) == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not allocate context"); + return NULL; + } + +#ifdef __APPLE__ + size_t usableSize = malloc_size(cipherctx); +#elif __linux__ + size_t usableSize = malloc_usable_size(cipherctx); +#endif + + jobject result = (*env)->NewDirectByteBuffer(env, cipherctx, usableSize); + if (result == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not NewDirectByteBuffer()"); + } + + return result; +} + +// This will clean up the cipher context when called. +// The JVM will only call this when it runs low on RAM or has extra CPU cycles that can be spent on GC. +// As a result, this will be flagged as a memory "leak" by Valgrind most of the time, but it is a false positive. +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_removeContext + (JNIEnv *env, jclass clazz, jobject context) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + if (cipherctx != NULL) { + EVP_CIPHER_CTX_free(cipherctx); + } +} + +// Looks up a cipher and sets its IV and key for the provided cipher context. Uses the provided lib context. +int initCipher(const char* cName,const unsigned char* cKey,int cKeyLen, unsigned char * cIV, int cIVLen, EVP_CIPHER_CTX * cipherctx, long libCtxInt, int encrypt) { + EVP_CIPHER* cipher = GetCipher(cName, libCtxInt); + + if(cipher == NULL) { + return -1; + } + + jint res; + + // GCM requires a special pre-init sequence to set the IV len. + if(strstr(cName, "gcm") != NULL || strstr(cName, "GCM") != NULL || strstr(cName, "ctr") != NULL || strstr(cName, "CTR") != NULL) { + OSSL_PARAM params[] = { + { NULL, 0, NULL, 0, 0 }, + { NULL, 0, NULL, 0, 0 } + }; + + OSSL_PARAM ivLenParam; + ivLenParam.key = "ivlen"; + ivLenParam.data_type = OSSL_PARAM_UNSIGNED_INTEGER; + ivLenParam.data = &cIVLen; + ivLenParam.data_size = 1; + ivLenParam.return_size = 0; + + params[0] = ivLenParam; + + res = EVP_CipherInit_ex2(cipherctx, cipher, NULL, NULL, !!encrypt, params); // Re-init with key and iv = 0. params will be called automaticly after pre init. + + if(res == 0) { + return -3; + } + + res = EVP_CipherInit_ex2(cipherctx, NULL , cKey, (const unsigned char*)cIV, !!encrypt, NULL); // Post init now that IV len is set. cipher should be NULL for this call. + }else { + res = EVP_CipherInit_ex2(cipherctx, cipher, cKey, (const unsigned char*)cIV, !!encrypt, NULL); // !! forces encrypt to be a 1 or 0. + } + + EVP_CIPHER_free(cipher); + + return res; +} + +// Creates a new cipher that can be used by the JVM. +// The JINT that is returned indicates success or failure. +// The context is a pointer to a cipher that must already be created. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_engineInitNative + (JNIEnv *env, jclass clazz, jobject context, jstring name, jbyteArray key, jbyteArray iv, jint encrypt, jlong libCtxInt) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + + if(cipherctx == NULL) { + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, "Cipher context is null for engine init."); + } + + const char* cName = (*env)->GetStringUTFChars(env, name, 0); + const unsigned char* cKey = (const unsigned char*)((*env)->GetByteArrayElements(env, key, 0)); + unsigned char* cIV = (unsigned char*)((*env)->GetByteArrayElements(env, iv, 0)); + int cKeyLen = (*env)->GetArrayLength(env, key); + unsigned int cIVLen = (*env)->GetArrayLength(env, iv); + + int res = initCipher(cName, cKey, cKeyLen, cIV, cIVLen, cipherctx, libCtxInt, encrypt); + + switch(res) { + case -1: { + int msgLen = strlen("Failed to load cipher: ") + strlen(cName) + 2; + char ErrorMessage[msgLen]; + + snprintf(ErrorMessage, msgLen, "Failed to load cipher: %s", cName); + + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, ErrorMessage); + + break; + } + case -2: { + int msgLen = strlen("Provided key length of 000000 does not match the expected key length for the requested cipher. ") + 1; + char ErrorMessage[msgLen]; + + snprintf(ErrorMessage, msgLen, "Provided key length of %d does not match the expected key length for the requested cipher. ", cKeyLen); + + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, ErrorMessage); + + break; + } + case -3: { + int msgLen = strlen("Error running engine int native. Error pre-initilizing for GCM. IV len: 0000 ") + 1; + + char ErrorMessage[msgLen]; + + snprintf(ErrorMessage, msgLen, "Error running engine int native. Error pre-initilizing for GCM. IV len: %d", cIVLen); + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, ErrorMessage); + break; + } + case 0: { + int msgLen = strlen("Error running engine int native. GCM: 0 IV len: 0000 ") + 1; + + char ErrorMessage[msgLen]; + + snprintf(ErrorMessage, msgLen, "Error running engine int native. GCM: %d IV len: %d", strstr(cName, "gcm") != NULL || strstr(cName, "GCM") != NULL, cIVLen); + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, ErrorMessage); + break; + } + } + + (*env)->ReleaseByteArrayElements(env, key, (jbyte*)cKey, 0); + (*env)->ReleaseStringUTFChars(env, name, (const char*)cName); + (*env)->ReleaseByteArrayElements(env, iv, (jbyte*)cIV, 0); + + return res; +} + +// Attempts to get the native block size for a given cipher via name. Uses native OpenSSL functions. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_getBlockSizeNative(JNIEnv *env, jclass clazz, jobject context, jstring name, jlong libCtxInt) { + ERR_clear_error(); + const char* cName = (*env)->GetStringUTFChars(env, name, 0); + + EVP_CIPHER* cipher = GetCipher(cName, libCtxInt); + + (*env)->ReleaseStringUTFChars(env, name, (const char*)cName); + + if(cipher == NULL) { + char ErrorMessage[strlen("Request for returned null. using libCtx: ") + strlen(cName) + 6]; + strcpy(ErrorMessage, "Request for "); + strcat(ErrorMessage, cName); + strcat(ErrorMessage, " returned null. using libCtx: "); + + if(libCtxInt != 0) { + strcat(ErrorMessage, "true"); + }else { + strcat(ErrorMessage, "false"); + } + + throwErrorWithOpenSSLInternalError(env, NULL_POINTER_EXCEPTION, ErrorMessage); + return -1; + } + + jint size = EVP_CIPHER_get_block_size(cipher); + + EVP_CIPHER_free(cipher); + + return size; +} + +// Attempts to get the IV originaly used to initilize a given cipher context. +JNIEXPORT jbyteArray JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_getOriginalIVNative(JNIEnv *env, jclass clazz, jobject context) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + int cipherLen = EVP_CIPHER_CTX_get_iv_length(cipherctx); + char* buf = (char*)malloc(cipherLen); + + if(buf == NULL) { + return NULL; + } + + int res = EVP_CIPHER_CTX_get_original_iv(cipherctx, buf, cipherLen); + + if(res == 0) { + return NULL; + } + + jbyteArray IvArr = (*env)->NewByteArray(env, cipherLen); + + jbyte *IvArrPtr = (*env)->GetByteArrayElements(env, IvArr, NULL); + + for(int i = 0; i < cipherLen; i++) { + IvArrPtr[i] = buf[i]; + } + + (*env)->ReleaseByteArrayElements(env, IvArr, IvArrPtr, 0); + + free(buf); + + return IvArr; +} + +// Calls the update function as part of an encryption or decryption operation. +int opensslUpdate(EVP_CIPHER_CTX *cipherctx, const unsigned char * bytesIn, unsigned char * bytesOut, int inLen, int* outLen, int encrypt) { + int resp = 1; + + if(encrypt) { + resp = EVP_EncryptUpdate(cipherctx, bytesOut, outLen, bytesIn, inLen); + }else { + resp = EVP_DecryptUpdate(cipherctx, bytesOut, outLen, bytesIn, inLen); + } + + return resp; +} + +// JNI function to pass in bytes for encryption or decryption. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_updateNative(JNIEnv *env, jclass clazz, jobject context, jbyteArray out, jbyteArray in, jint inlen, jint encrypt, jint inputOffset) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + + const unsigned char *bytesIn = (const unsigned char*)((*env)->GetByteArrayElements(env, in, 0)); + bytesIn += inputOffset; + + unsigned char *bytesOut = (unsigned char*)(*env)->GetByteArrayElements(env, out, 0); + + int outlen = 0; + + int res = opensslUpdate(cipherctx, bytesIn, bytesOut, inlen, &outlen, encrypt); + + bytesIn -= inputOffset;// Reset offset before release. + + (*env)->ReleaseByteArrayElements(env, in, (jbyte*)bytesIn, 0); + (*env)->ReleaseByteArrayElements(env, out, (jbyte*)bytesOut, 0); + + if(res != 0) { + return outlen; + } + + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, "Error running update native"); + + return -1; +} + +// Attempts to finalize the encryption or decryption, writing any remaining bytes out to the output buffer. +int opensslDoFinal(EVP_CIPHER_CTX *cipherctx, unsigned char* bytesOut, int outputOffset, int* outputLen, int encrypt) { + int resp = 1; + + if(encrypt) { + resp = EVP_EncryptFinal_ex(cipherctx, bytesOut + outputOffset, outputLen); + }else { + resp = EVP_DecryptFinal_ex(cipherctx, bytesOut + outputOffset, outputLen); + } + + return resp; +} + +// JNI interface for finalizing an encryption or decryption operation. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_doFinalNative(JNIEnv *env, jclass clazz, jobject context, jbyteArray out, jint encrypt, jint outputOffset) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + unsigned char *bytesOut = (unsigned char*)(*env)->GetByteArrayElements(env, out, 0); + + int resp = 1, outLength = 0; + + resp = opensslDoFinal(cipherctx, bytesOut, outputOffset, &outLength, encrypt); + + (*env)->ReleaseByteArrayElements(env, out, (jbyte*)bytesOut, 0); + + if(resp != 0) { + return outLength; // outLength is set to the numebr of bytes written. Return this instead of the 0 (fail) or 1 (pass) when resp == 1. + } + + char ErrorMessage[strlen("Error running dofinal. Encrypting: ") + 6]; + + strcpy(ErrorMessage, "Error running dofinal. Encrypting: "); + + if(encrypt) { + strcat(ErrorMessage, "true"); + }else { + strcat(ErrorMessage, "false"); + } + + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, ErrorMessage); + + return -1; +} + +// JNI method for setting a special GCM tag that is required for GCM decryption. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_setGCMTagNative(JNIEnv *env, jclass clazz, jobject context, jbyteArray key) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + unsigned char *cKey = (unsigned char*)(*env)->GetByteArrayElements(env, key, 0); + int cKeyLen = (*env)->GetArrayLength(env, key); + + OSSL_PARAM params[] = { + { NULL, 0, NULL, 0, 0 }, + { NULL, 0, NULL, 0, 0 } + }; + // This is an easy way to initialize an array that is two params long, without calling malloc. + // This way, it is cleaned up when it goes out of scope. + // A null param indicates the end of the params array. + // A few lines below, we overwrite the first element in the array with a struct that contains the correct values. + + OSSL_PARAM keyParam; + keyParam.key = "tag"; + keyParam.data_type = OSSL_PARAM_OCTET_STRING; + keyParam.data = cKey; + keyParam.data_size = cKeyLen; + keyParam.return_size = 0; + + params[0] = keyParam; + + int res = EVP_CIPHER_CTX_set_params(cipherctx, params); + + (*env)->ReleaseByteArrayElements(env, key, (jbyte*)cKey, 0); + + return res; +} + +// JNI method for reading a special tag used by GCM encryption. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCipherNative_getGCMTagNative(JNIEnv *env, jclass clazz, jobject context, jbyteArray key) { + EVP_CIPHER_CTX *cipherctx = get_context_from(env, context); + unsigned char *cKey = (unsigned char*)(*env)->GetByteArrayElements(env, key, 0); + int cKeyLen = (*env)->GetArrayLength(env, key); + + OSSL_PARAM params[] = { + { NULL, 0, NULL, 0, 0 }, + { NULL, 0, NULL, 0, 0 } + }; + // This is an easy way to initialize an array that is two params long, without calling malloc. + // This way, it is cleaned up when it goes out of scope. + // A null param indicates the end of the params array. + // A few lines below, we overwrite the first element in the array with a struct that contains the correct values. + + OSSL_PARAM keyParam; + keyParam.key = "tag"; + keyParam.data_type = OSSL_PARAM_OCTET_STRING; + keyParam.data = cKey; + keyParam.data_size = cKeyLen; + keyParam.return_size = 0; + + params[0] = keyParam; + + int res = EVP_CIPHER_CTX_get_params(cipherctx, params); + + (*env)->ReleaseByteArrayElements(env, key, (jbyte*)cKey, 0); + + if(res >= 0) { + return keyParam.return_size; + } + + return res; +} diff --git a/openssl4j/src/main/c/openssl4j_cipher.h b/openssl4j/src/main/c/openssl4j_cipher.h new file mode 100644 index 0000000..a1def62 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_cipher.h @@ -0,0 +1,14 @@ +/* +** Header file to make testing ciphers in c easier +** @author Daniel Thertell +*/ + +#ifndef OPENSSL4J_CIPHER +#define OPENSSL4J_CIPHER +#include + +int initCipher(const char* cName,const unsigned char* cKey,int cKeyLen, unsigned char * cIV, int cIVLen, EVP_CIPHER_CTX * cipherctx, long libCtxInt, int encrypt); +int opensslUpdate(EVP_CIPHER_CTX *cipherctx, const unsigned char * bytesIn, unsigned char * bytesOut, int inLen, int* outLen, int encrypt); +int opensslDoFinal(EVP_CIPHER_CTX *cipherctx, unsigned char* bytesOut, int outputOffset, int* outputLen, int encrypt); + +#endif diff --git a/openssl4j/src/main/c/openssl4j_common.c b/openssl4j/src/main/c/openssl4j_common.c new file mode 100644 index 0000000..7dcd5c6 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_common.c @@ -0,0 +1,94 @@ +/* +** OpenSSL Common Java Binding Code +** @author Stephan Fuhrmann +** @author Daniel Thertell +*/ + +#include +#include "openssl4j.h" +#include +#include +#include +#include + + +void throw_error(JNIEnv *env, const char *exceptionClassName, const char *message) { + jclass exceptionClass = (*env)->FindClass(env, exceptionClassName); + if (exceptionClass != NULL) { + jint success = (*env)->ThrowNew(env, exceptionClass, message); + if (0 != success) { + (*env)->FatalError(env, "Could not throw exception"); + } + } else { + (*env)->FatalError(env, "Didn't find IllegalStateException class"); + } +} + +void* get_context_from(JNIEnv *env, jobject context) { + if (context == NULL) { + throw_error(env, NULL_POINTER_EXCEPTION, "context is NULL"); + return NULL; + } + void* context_data = (void*) (*env)->GetDirectBufferAddress(env, context); + if (context_data == NULL) { + throw_error(env, ILLEGAL_STATE_EXCEPTION, "GetDirectBufferAddress() for Context failed"); + } + return context_data; +} + +// Recursive function that pops all errors off of the OpenSSL errorstack and gets each message, appending to the previous message (if any). +char * getAllErrorMessages(char* previousMessage, const char* delimiter) { + unsigned long errCode = ERR_get_error(); + + if(errCode == 0) { + if(previousMessage == NULL) { + return "No Errors"; // Return a default value if no previous message was set, and no errors were found. + } + return previousMessage; + } + + char* newMessage = ERR_error_string(errCode, NULL); + size_t totalLen = strlen(newMessage); + + if(previousMessage != NULL) { + totalLen += strlen(previousMessage) + strlen(delimiter); + } + + char* newFullString = malloc(totalLen+1); + + if(previousMessage != NULL) { + strcpy(newFullString, previousMessage); + free((void *)previousMessage); + strcat(newFullString, delimiter); + strcat(newFullString, newMessage); + }else { + strcpy(newFullString, newMessage); + } + + return getAllErrorMessages(newFullString, delimiter); +} + +// Throws a Java error that contains all errors that are on the OpenSSL error stack, and clears the error stack. +void throwErrorWithOpenSSLInternalError(JNIEnv *env, const char* errorClass, const char *message) { + char* internalMessage = getAllErrorMessages(NULL, ", \n"); + char ErrorMessage[strlen(message) + strlen(" internal error: \n") + strlen(internalMessage) + 1]; + strcpy(ErrorMessage, message); + + strcat(ErrorMessage, " internal error: "); + strcat(ErrorMessage, internalMessage); + + throw_error(env, errorClass, ErrorMessage); + + if(strcmp(internalMessage, "No Errors") != 0) { + free(internalMessage); + } +} + +EVP_MD* GetMessageDigest(const char* OriginalName, long libCtx) { + if(libCtx != 0) { + struct OpenSSLProviderHolder* handle = (struct OpenSSLProviderHolder*)libCtx; + return EVP_MD_fetch(handle->lib, OriginalName, NULL); + }else { + return EVP_MD_fetch(NULL, OriginalName, NULL); + } +} \ No newline at end of file diff --git a/openssl4j/src/main/c/openssl4j_crypto.c b/openssl4j/src/main/c/openssl4j_crypto.c new file mode 100644 index 0000000..c86029d --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_crypto.c @@ -0,0 +1,283 @@ +/* +** OpenSSL Cipher to Java Binding Code +** Contains helper functions to assist in setting up a library instance in FIPS mode +** @author Daniel Thertell +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "openssl4j.h" + +#include "de_sfuhrm_openssl4j_OpenSSLCryptoNative.h" + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +#define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_new +#define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_free +#else +#define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_create +#define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_destroy +#endif + +#define backupOSSLConf "/etc/ssl/ossl3/openssl.cnf" + +char* opensslConfigLocation = NULL; + +// Checks to make sure FIPS is enabled for the provided lib and the default lib. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCryptoNative_FIPSMode + (JNIEnv *env, jclass obj, jlong handle) { + struct OpenSSLProviderHolder *ctx = (struct OpenSSLProviderHolder*) handle; + return EVP_default_properties_is_fips_enabled(ctx->lib) && EVP_default_properties_is_fips_enabled(NULL); +} + +// Sets the location of the OpenSSL config file. +void setOpensslConfigLocation(const char* source) { + if(opensslConfigLocation != NULL) { + free(opensslConfigLocation); + opensslConfigLocation = NULL; + } + + opensslConfigLocation = (char*)malloc(strlen(source) +1); + + if(opensslConfigLocation == NULL) { + return; + } + + strcpy(opensslConfigLocation, source); +} + +// Creates a native lib instance and passes back a pointer to it. +unsigned long long createOpenSSLLibNative(int setFips, const char* confLocation, const char* libLocations, int *errCode) { + ERR_clear_error(); + + struct OpenSSLProviderHolder *handle = malloc(sizeof(struct OpenSSLProviderHolder)); // Create the pointer. + + setOpensslConfigLocation(confLocation); + if(opensslConfigLocation == NULL) { + free(handle); + *errCode = 1; + return 0; + } + + int usedBackup = 0; + + if(strlen(opensslConfigLocation) < 3) { + usedBackup++; + if(OSSL_LIB_CTX_load_config(NULL, backupOSSLConf) == 0){ + free(handle); + *errCode = 2; + return 0; + } + }else { + if(OSSL_LIB_CTX_load_config(NULL, opensslConfigLocation) == 0){ + free(handle); + *errCode = 3; + return 0; + } + } + + handle->lib = OSSL_LIB_CTX_get0_global_default(); + + if(handle->lib == NULL) { + free(handle); + *errCode = 4; + return 0; + } + + if(usedBackup) { + if(OSSL_LIB_CTX_load_config(handle->lib, backupOSSLConf) == 0){ + free(handle); + *errCode = 5; + return 0; + } + }else { + if(OSSL_LIB_CTX_load_config(handle->lib, opensslConfigLocation) == 0){ + free(handle); + *errCode = 6; + return 0; + } + } + + // Set search path for provider libs (fips.so, default.so, etc.). + if(OSSL_PROVIDER_set_default_search_path(handle->lib, libLocations) == 0) { + free(handle); + *errCode = 7; + return 0; + } + + // Load base provider. + handle->baseProvider = OSSL_PROVIDER_load(handle->lib, "base"); + + if (handle->baseProvider == NULL) { + free(handle); + *errCode = 8; + return 0; + } + + // Load legacy provider. + handle->legacyProvider = OSSL_PROVIDER_load(handle->lib, "legacy"); + + if (handle->legacyProvider == NULL) { + free(handle); + *errCode = 9; + return 0; + } + + // Load FIPS provider. + handle->fipsProvider = OSSL_PROVIDER_load(handle->lib, "fips"); + + + if (handle->fipsProvider == NULL) { + free(handle); + *errCode = 10; + return 0; + } + + // Set FIPS for target lib and default lib. + // FIPS limits available algorithms in all of the loaded providers to those that are FIPS-compliant. + if (!EVP_default_properties_enable_fips(handle->lib, setFips)) { + free(handle); + *errCode = 11; + return 0; + } + + if (!EVP_default_properties_enable_fips(NULL, setFips)) { + free(handle); + *errCode = 12; + return 0; + } + + + return (unsigned long long)handle; +} + +// Based on https://www.openssl.org/docs/man3.0/man7/fips_module.html +// JNI interface for creating the OpenSSL library. +JNIEXPORT jlong JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCryptoNative_CreateOpenSSLLibNative + (JNIEnv *env, jclass obj, jint setFips, jstring config, jstring libDir) { + + if(setFips == 0) { + return 0; // We don't support non-FIPS mode. + } + + ERR_clear_error(); + + + const char* confLocation = (*env)->GetStringUTFChars(env, config, 0); + const char* libLocations = (*env)->GetStringUTFChars(env, libDir, 0); + + int errCode = 0; + jlong ptr = createOpenSSLLibNative(setFips, confLocation, libLocations, &errCode); + + (*env)->ReleaseStringUTFChars(env, config, confLocation); + (*env)->ReleaseStringUTFChars(env, libDir, libLocations); + + // Error code switch, to help with debugging. + switch (errCode) + { + case 1: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Failed to save config path!"); + break; + case 2: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl backup config"); + break; + case 3: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl config"); + break; + case 4: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not get openssl library context"); + break; + case 5: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl backup config (part 2)"); + break; + case 6: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl config (part 2)"); + break; + case 7: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not set default search path for modules"); + break; + case 8: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl base provider"); + break; + case 9: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl legacy provider"); + break; + case 10: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not load openssl FIPS provider"); + break; + case 11: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not enable FIPS for openssl lib"); + break; + case 12: + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not enable FIPS for default openssl lib"); + break; + + default: + if(ptr == 0) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Unknown error while creating lib instance"); + } + break; + } + + return ptr; +} + + +// Function to decrypt any private key and encode it into an expected format for the calling Java functions. +JNIEXPORT jbyteArray JNICALL Java_de_sfuhrm_openssl4j_OpenSSLCryptoNative_decryptPrivateKeyNative(JNIEnv *env, jclass clazz, jstring privateKey, jstring passPhrase) { + + const char* pKey = (*env)->GetStringUTFChars(env, privateKey, 0); + const char* pass = (*env)->GetStringUTFChars(env, passPhrase, 0); + + int keyLen = (*env)->GetStringUTFLength(env, privateKey); + + BIO* pKeyMem = BIO_new_mem_buf(pKey, keyLen); + + EVP_PKEY* k = PEM_read_bio_PrivateKey(pKeyMem, NULL, NULL, (void *)pass); + + (*env)->ReleaseStringUTFChars(env, privateKey, (const char*)pKey); + (*env)->ReleaseStringUTFChars(env, passPhrase, (const char*)pass); + BIO_free(pKeyMem); + + OSSL_ENCODER_CTX *ectx; + const char *format = "PEM"; + const char *structure = "PrivateKeyInfo"; + + ectx = OSSL_ENCODER_CTX_new_for_pkey(k, OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, format, structure, NULL); + + if(ectx == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "unable to convert private key to unencrypted pem format, no encoders found"); + EVP_PKEY_free(k); + return NULL; + } + + unsigned char *data = NULL; + size_t datalen; + + if(OSSL_ENCODER_to_data(ectx, &data, &datalen) == 0) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "unable to convert private key, failed to export key"); + EVP_PKEY_free(k); + return NULL; + } + + jbyteArray pKeyJava = (*env)->NewByteArray(env, datalen); + + jbyte *pKeyJavaPtr = (*env)->GetByteArrayElements(env, pKeyJava, NULL); + + for(int i = 0; i < datalen; i++) { + pKeyJavaPtr[i] = data[i]; + } + + (*env)->ReleaseByteArrayElements(env, pKeyJava, pKeyJavaPtr, 0); + OSSL_ENCODER_CTX_free(ectx); + EVP_PKEY_free(k); + + OPENSSL_clear_free(data, datalen); + + return pKeyJava; +} diff --git a/openssl4j/src/main/c/openssl4j_mac.c b/openssl4j/src/main/c/openssl4j_mac.c new file mode 100644 index 0000000..6683ee2 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_mac.c @@ -0,0 +1,44 @@ +/* +** OpenSSL Common Java Binding Code +** OpenSSL3 HMAC Wrapper +** @author Daniel Thertell +*/ + +#include "openssl4j.h" + +#include +#include + +#include "de_sfuhrm_openssl4j_OpenSSLMacNative.h" + +// Returns max MAC length. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMacNative_getMaxMacLength(JNIEnv *env, jclass clazz) { + return EVP_MAX_MD_SIZE; +} + +// Calculates an HMAC for the given data. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMacNative_hmacNative(JNIEnv *env, jclass clazz, jlong libCtx, jstring algName, jbyteArray key, jint keyLen, jbyteArray data, jint dataLen, jbyteArray output) { + const char* cAlgName = (*env)->GetStringUTFChars(env, algName, 0); + EVP_MD* digest = GetMessageDigest(cAlgName, libCtx); + + const unsigned char* cKey = (const unsigned char*)((*env)->GetByteArrayElements(env, key, 0)); + const unsigned char* cData = (const unsigned char*)((*env)->GetByteArrayElements(env, data, 0)); + unsigned char* cOutput = (unsigned char*)((*env)->GetByteArrayElements(env, output, 0)); + + unsigned int outputLen = 0; + + unsigned char* res = HMAC(digest, cKey, keyLen, cData, dataLen, cOutput, &outputLen); + + // Cleanup. + (*env)->ReleaseByteArrayElements(env, key, (jbyte*)cKey, 0); + (*env)->ReleaseByteArrayElements(env, data, (jbyte*)cData, 0); + (*env)->ReleaseByteArrayElements(env, output, (jbyte*)cOutput, 0); + (*env)->ReleaseStringUTFChars(env, algName, cAlgName); + + if(res == NULL) { + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, "Error calculating hmac"); + return -1; + } + + return outputLen; +} \ No newline at end of file diff --git a/openssl4j/src/main/c/openssl4j_messagedigest.c b/openssl4j/src/main/c/openssl4j_messagedigest.c new file mode 100644 index 0000000..58caac6 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_messagedigest.c @@ -0,0 +1,235 @@ +/* +** OpenSSL MessageDigest to Java Binding Code. +** +** See here for an example of the EVP API: +** https://wiki.openssl.org/index.php/EVP_Message_Digests +** @author Stephan Fuhrmann +*/ + +#include +#include +#include + +#ifdef __APPLE__ +#include +#elif __linux__ +#include +#endif + +#include "openssl4j.h" + +#include "de_sfuhrm_openssl4j_OpenSSLMessageDigestNative.h" + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +#define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_new +#define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_free +#else +#define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_create +#define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_destroy +#endif + +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_digestLength + (JNIEnv *env, jclass clazz, jobject context) { + EVP_MD_CTX *mdctx = get_context_from(env, context); + if (mdctx != NULL) { + return EVP_MD_CTX_size(mdctx); + } + return 0; +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_removeContext + (JNIEnv *env, jclass clazz, jobject context) { + EVP_MD_CTX *mdctx = get_context_from(env, context); + if (mdctx != NULL) { + OPENSSL_MD_FREE_FUNC(mdctx); + } +} + + +/* Callback for EVP_MD_do_all that counts the number of MD algorithms. */ +static void EVP_MD_do_all_count_func(const EVP_MD *ciph, const char *from, const char *to, void *x) { + if (ciph != NULL) { + jint *numOfAlgos = (jint*)x; + (*numOfAlgos)++; + } +} + +/* Callback for EVP_MD_do_all that sets the string array elements. +** @param ciph cipher, can be NULL if this is an alias. +** @param from the name of the algorithm. +** @param to NULL if this is not an alias, or the target EVP_MD if this is a an alias. +** @param x the last param passed to the EVP_MD_do_all() call. +*/ +static void EVP_MD_do_all_string_array_set(const EVP_MD *ciph, const char *from, const char *to, void *x) { + struct StringArrayPosition *sap = (struct StringArrayPosition*)x; + if (ciph == NULL) { + // alias + return; + } + const char *evp_name = EVP_MD_name(ciph); + + jstring algoNameString = (*sap->env)->NewStringUTF(sap->env, evp_name); + if (algoNameString == NULL) { + return; + } + + (*sap->env)->SetObjectArrayElement(sap->env, sap->array, sap->index, algoNameString); + sap->index++; +} + +JNIEXPORT jobjectArray JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_listMessageDigests + (JNIEnv *env, jclass clazz) { + struct StringArrayPosition sap; + jobjectArray result = NULL; + + sap.index = 0; + sap.length = 0; + sap.env = env; + sap.array = NULL; + + EVP_MD_do_all(EVP_MD_do_all_count_func, &sap.length); + jclass stringClass = (*env)->FindClass(env, "java/lang/String"); + if (stringClass == NULL) { + return NULL; + } + + result = (*env)->NewObjectArray(env, sap.length, stringClass, NULL); + sap.array = result; + + EVP_MD_do_all(EVP_MD_do_all_string_array_set, &sap); + + return result; +} + +JNIEXPORT jobject JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeContext + (JNIEnv *env, jobject obj) { + EVP_MD_CTX *mdctx; + + if ((mdctx = OPENSSL_MD_NEW_FUNC()) == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not allocate context"); + return NULL; + } + +#ifdef __APPLE__ + size_t usableSize = malloc_size(mdctx); +#elif __linux__ + size_t usableSize = malloc_usable_size(mdctx); +#endif + + jobject result = (*env)->NewDirectByteBuffer(env, mdctx, usableSize); + if (result == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Could not NewDirectByteBuffer()"); + } + + return result; +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByte + (JNIEnv *env, jobject obj, jobject context, jbyte byteData) { + EVP_MD_CTX* context_data = get_context_from(env, context); + if (context_data != NULL) { + if (1 != EVP_DigestUpdate(context_data, &byteData, 1)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); + } + } +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByteArray + (JNIEnv *env, jobject obj, jobject context, jbyteArray jarray, jint offset, jint length) { + if (jarray == NULL) { + throwErrorWithOpenSSLInternalError(env, NULL_POINTER_EXCEPTION, "array is NULL"); + return; + } + + EVP_MD_CTX* context_data = get_context_from(env, context); + if (context_data != NULL) { + jboolean isCopy = JNI_FALSE; + + /* TODO this copies the whole array, even if length is 1 byte */ + jbyte *carray = (*env)->GetByteArrayElements(env, jarray, &isCopy); + if (carray != NULL) { + if (1 != EVP_DigestUpdate(context_data, carray + offset, length)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); + } + /* JNI_ABORT: Don't copy back the array, nothing has changed */ + (*env)->ReleaseByteArrayElements(env, jarray, carray, JNI_ABORT); + } else { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "GetByteArrayElements for array failed"); + } + } +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByteBuffer + (JNIEnv *env, jobject obj, jobject context, jobject bb, jint offset, jint length) { + if (bb == NULL) { + throwErrorWithOpenSSLInternalError(env, NULL_POINTER_EXCEPTION, "ByteBuffer is NULL"); + return; + } + EVP_MD_CTX* context_data = get_context_from(env, context); + if (context_data != NULL) { + jbyte* buffer = (*env)->GetDirectBufferAddress(env, bb); + if (buffer != NULL) { + jbyte* offset_buffer = buffer + offset; + + if (1 != EVP_DigestUpdate(context_data, offset_buffer, length)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); + } + } else { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "GetDirectBufferAddress for ByteBuffer failed"); + } + } +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeFinal + (JNIEnv *env, jobject obj, jobject context, jbyteArray jdigest) { + EVP_MD_CTX* context_data = get_context_from(env, context); + if (jdigest == NULL) { + throwErrorWithOpenSSLInternalError(env, NULL_POINTER_EXCEPTION, "Digest array is NULL"); + return; + } + if (context_data != NULL) { + jbyte cdigest[EVP_MAX_MD_SIZE]; + unsigned int actualSize; + if (1 != EVP_DigestFinal_ex(context_data, (unsigned char*)cdigest, &actualSize)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestFinal_ex failed"); + } + (*env)->SetByteArrayRegion(env, jdigest, 0, actualSize, cdigest); + } +} + +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeInit + (JNIEnv *env, jobject obj, jobject context, jstring jalgoName) { + if (jalgoName == NULL) { + throwErrorWithOpenSSLInternalError(env, NULL_POINTER_EXCEPTION, "Algorithm name is NULL"); + return; + } + EVP_MD_CTX* context_data = get_context_from(env, context); + if (context_data == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestInit_ex failed"); + return; + } + + jsize nameLength = (*env)->GetStringUTFLength(env, jalgoName); + + char javaNameC[256]; + if (nameLength > sizeof(javaNameC)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Algorithm name exceeds the limit"); + return; + } + + jboolean isCopy; + const char * cstr = (*env)->GetStringUTFChars(env, jalgoName, &isCopy); + strncpy(javaNameC, cstr, sizeof(javaNameC)); + (*env)->ReleaseStringUTFChars(env, jalgoName, cstr); + + const EVP_MD *evp_md = EVP_get_digestbyname(javaNameC); + if (evp_md == NULL) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "Named MessageDigest was not found"); + return; + } + + if (1 != EVP_DigestInit_ex(context_data, evp_md, NULL)) { + throwErrorWithOpenSSLInternalError(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestInit_ex failed"); + return; + } +} diff --git a/openssl4j/src/main/c/openssl4j_secureRandom.c b/openssl4j/src/main/c/openssl4j_secureRandom.c new file mode 100644 index 0000000..f303f45 --- /dev/null +++ b/openssl4j/src/main/c/openssl4j_secureRandom.c @@ -0,0 +1,36 @@ +/* +** OpenSSL Common Java Binding Code +** OpenSSL3 Secure Random wrapper +** @author Daniel Thertell +*/ + +#include "openssl4j.h" + +#include +#include + +#include "de_sfuhrm_openssl4j_OpenSSLSecureRandomNative.h" + +// Sets a seed for the secure-random engine by adding the given bytes. +JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLSecureRandomNative_engineSetSeedNative(JNIEnv *env, jclass clazz, jbyteArray seed, jint seedLen, jdouble seedRandomness) { + const unsigned char *seedBytes = (const unsigned char*)((*env)->GetByteArrayElements(env, seed, 0)); + RAND_add(seedBytes, seedLen, seedRandomness); + (*env)->ReleaseByteArrayElements(env, seed, (jbyte*)seedBytes, 0); +} + +// Pulls X bytes from the secure-random source. Throws an error if not possible. +JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLSecureRandomNative_engineNextBytesNative(JNIEnv *env, jclass clazz, jbyteArray nextBytesArr, jint nextBytesArrLen) { + unsigned char *nextBytesC = (unsigned char*)((*env)->GetByteArrayElements(env, nextBytesArr, 0)); + int res = RAND_bytes(nextBytesC, nextBytesArrLen); + (*env)->ReleaseByteArrayElements(env, nextBytesArr, (jbyte*)nextBytesC, 0); + + if(res == -1) { + // Not supported. + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, "Secure random is not supported on this hardware"); + }else if(res == 0) { + // Other error. + throwErrorWithOpenSSLInternalError(env, UNSUPPORTED_OPERATION_EXCEPTION, "error getting random bytess"); + } + + return res; +} \ No newline at end of file diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/Cipher.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/Cipher.java new file mode 100644 index 0000000..08e1bf7 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/Cipher.java @@ -0,0 +1,70 @@ +package de.sfuhrm.openssl4j; + +import de.sfuhrm.openssl4j.*; + + +public class Cipher { + private Cipher() { + + } + + public static final class AES extends OpenSSLCipherNative { + public AES() { + super("AES-256"); + } + } + + public static final class SHA512 extends OpenSSLCipherNative { + public SHA512() { + super("SHA512"); + } + } + + public static final class SHA256 extends OpenSSLCipherNative { + public SHA256() { + super("SHA256"); + } + } + + public static final class SHA1 extends OpenSSLCipherNative{ + public SHA1() { + super("SHA1"); + } + } + + public static final class AES_256_GCM extends OpenSSLCipherNative{ + public AES_256_GCM() { + super("AES-256-GCM"); + } + } + + public static final class AES_256_CBC extends OpenSSLCipherNative{ + public AES_256_CBC() { + super("AES-256-CBC"); + } + } + + public static final class AES_256_CTR extends OpenSSLCipherNative{ + public AES_256_CTR() { + super("AES_256_CTR"); + } + } + + public static final class AES_256_ECB extends OpenSSLCipherNative{ + public AES_256_ECB() { + super("AES_256_ECB"); + } + } + + public static final class id_aes256_GCM extends OpenSSLCipherNative{ + public id_aes256_GCM() { + super("AES_256_GCM"); + } + } + + public static final class HMACSHA256 extends OpenSSLCipherNative { + public HMACSHA256() { + super("HMAC_SHA_256"); + } + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/Mac.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/Mac.java new file mode 100644 index 0000000..77440ad --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/Mac.java @@ -0,0 +1,13 @@ +package de.sfuhrm.openssl4j; + +public class Mac { + private Mac() { + + } + + public static final class HMAC_SHA256 extends OpenSSLMacNative { + public HMAC_SHA256() { + super("sha256"); + } + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/MessageDigest.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/MessageDigest.java index 09e0a79..44451ef 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/MessageDigest.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/MessageDigest.java @@ -1,138 +1,139 @@ package de.sfuhrm.openssl4j; -/** Class definitions for the message digest spis. +/** + * Class definitions for the message digest spis. + * * @author Stephan Fuhrmann - * */ + */ public final class MessageDigest { - private MessageDigest() { - // no instances allowed - } - - /** MD5 message digest implementation. - * */ - public final static class MD5 extends OpenSSLMessageDigestNative { + private MessageDigest() { + // No instances allowed. + } - /** Creates a new instance. */ - public MD5() { super("MD5"); } + /** BLAKE2b512 message digest implementation. */ + public static final class BLAKE2b512 extends OpenSSLMessageDigestNative { + public BLAKE2b512() { + super("BLAKE2b512"); } + } - /** SHA1 message digest implementation. - * */ - public final static class SHA1 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA1() { super("SHA1"); } + /** BLAKE2s256 message digest implementation. */ + public static final class BLAKE2s256 extends OpenSSLMessageDigestNative { + public BLAKE2s256() { + super("BLAKE2s256"); } + } - /** SHA-224 message digest implementation. - * */ - public final static class SHA_224 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_224() { super("SHA224"); } + /** MD4 message digest implementation. */ + public static final class MD4 extends OpenSSLMessageDigestNative { + public MD4() { + super("MD4"); } + } - /** SHA-256 message digest implementation. - * */ - public final static class SHA_256 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_256() { super("SHA256"); } + /** MD5 message digest implementation. */ + public static final class MD5 extends OpenSSLMessageDigestNative { + public MD5() { + super("MD5"); } + } - /** SHA-384 message digest implementation. - * */ - public final static class SHA_384 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_384() { super("SHA384"); } + /** RIPEMD160 message digest implementation. */ + public static final class RIPEMD160 extends OpenSSLMessageDigestNative { + public RIPEMD160() { + super("RIPEMD160"); } + } - /** SHA-512 message digest implementation. - * */ - public final static class SHA_512 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_512() { super("SHA512"); } + /** SHA1 message digest implementation. */ + public static final class SHA1 extends OpenSSLMessageDigestNative { + public SHA1() { + super("SHA1"); } + } - /** SHA-512/224 message digest implementation. - * */ - public final static class SHA_512_224 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_512_224() { super("SHA512-224"); } + /** SHA-224 message digest implementation. */ + public static final class SHA_224 extends OpenSSLMessageDigestNative { + public SHA_224() { + super("SHA224"); } + } - /** SHA-512/256 message digest implementation. - * */ - public final static class SHA_512_256 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA_512_256() { super("SHA512-256"); } + /** SHA-256 message digest implementation. */ + public static final class SHA_256 extends OpenSSLMessageDigestNative { + public SHA_256() { + super("SHA256"); } + } - /** SHA3-224 message digest implementation. - * */ - public final static class SHA3_224 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA3_224() { super("SHA3-224"); } + /** SHA3-224 message digest implementation. */ + public static final class SHA3_224 extends OpenSSLMessageDigestNative { + public SHA3_224() { + super("SHA3-224"); } + } - /** SHA3-256 message digest implementation. - * */ - public final static class SHA3_256 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA3_256() { super("SHA3-256"); } + /** SHA3-256 message digest implementation. */ + public static final class SHA3_256 extends OpenSSLMessageDigestNative { + public SHA3_256() { + super("SHA3-256"); } + } - /** SHA3-384 message digest implementation. - * */ - public final static class SHA3_384 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA3_384() { super("SHA3-384"); } + /** SHA3-384 message digest implementation. */ + public static final class SHA3_384 extends OpenSSLMessageDigestNative { + public SHA3_384() { + super("SHA3-384"); } + } - /** SHA3-512 message digest implementation. - * */ - public final static class SHA3_512 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SHA3_512() { super("SHA3-512"); } + /** SHA3-512 message digest implementation. */ + public static final class SHA3_512 extends OpenSSLMessageDigestNative { + public SHA3_512() { + super("SHA3-512"); } + } - /** BLAKE2b512 message digest implementation. - * */ - public final static class BLAKE2b512 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public BLAKE2b512() { super("BLAKE2b512"); } + /** SHA-384 message digest implementation. */ + public static final class SHA_384 extends OpenSSLMessageDigestNative { + public SHA_384() { + super("SHA384"); } + } - /** BLAKE2s256 message digest implementation. - * */ - public final static class BLAKE2s256 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public BLAKE2s256() { super("BLAKE2s256"); } + /** SHA-512 message digest implementation. */ + public static final class SHA_512 extends OpenSSLMessageDigestNative { + public SHA_512() { + super("SHA512"); } + } - /** MD4 message digest implementation. - * */ - public final static class MD4 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public MD4() { super("MD4"); } + /** SHA-512/224 message digest implementation. */ + public static final class SHA_512_224 extends OpenSSLMessageDigestNative { + public SHA_512_224() { + super("SHA512-224"); } + } - /** RIPEMD160 message digest implementation. - * */ - public final static class RIPEMD160 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public RIPEMD160() { super("RIPEMD160"); } + /** SHA-512/256 message digest implementation. */ + public static final class SHA_512_256 extends OpenSSLMessageDigestNative { + public SHA_512_256() { + super("SHA512-256"); } + } - /** SM3 message digest implementation. - * */ - public final static class SM3 extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public SM3() { super("SM3"); } + /** SM3 message digest implementation. */ + public static final class SM3 extends OpenSSLMessageDigestNative { + public SM3() { + super("SM3"); } + } - /** Whirlpool message digest implementation. - * */ - public final static class Whirlpool extends OpenSSLMessageDigestNative { - /** Creates a new instance. */ - public Whirlpool() { super("whirlpool"); } + /** Whirlpool message digest implementation. */ + public static final class Whirlpool extends OpenSSLMessageDigestNative { + public Whirlpool() { + super("whirlpool"); } + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/NativeLoader.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/NativeLoader.java index 78af598..2440aec 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/NativeLoader.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/NativeLoader.java @@ -1,6 +1,8 @@ package de.sfuhrm.openssl4j; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; @@ -8,46 +10,81 @@ /** * Loads the object files. + * * @author Stephan Fuhrmann */ class NativeLoader { - /** Which objects have already been loaded? */ - private final Set loaded; + /** Which objects have already been loaded? */ + private final Set loaded; - private static boolean isLoaded = false; + private static boolean isLoaded = false; - static final String[] OBJECTS = { - "libopenssl4j" - }; + private static NativeLoader instance; - NativeLoader() { - loaded = new HashSet<>(); + private ObjectTransfer objTransfer; + + static final String[] OBJECTS = {"libopenssl4j"}; + + NativeLoader() { + loaded = new HashSet<>(); + } + + public static Path getLibraryDir() { + if(instance == null) { + return null; + } + + return instance.objTransfer.getTargetDir(); + } + + /** + * Loads all object files. + * + * @throws IOException if transferring the object files failed. + */ + static void loadAll() throws IOException { + if (isLoaded) { + return; } + NativeLoader.instance = new NativeLoader(); + NativeLoader.instance.objTransfer = new ObjectTransfer(); + NativeLoader.instance.objTransfer.transfer(OBJECTS); + for (Path path : NativeLoader.instance.objTransfer.getObjectFiles()) { + if(!ObjectTransfer.getOsName().toLowerCase().contains("mac") && !path.toString().contains("openssl4j")){ + continue; + } + NativeLoader.instance.load(path); + } + isLoaded = true; + } + + String getLibDependencies(Path file) { + try{ + Process proc = Runtime.getRuntime().exec("ldd " + file.toString()); - /** - * Loads all object files. - * @throws IOException if transferring the object files failed. - */ - static void loadAll() throws IOException { - if (isLoaded) { - return; - } - NativeLoader nativeLoader = new NativeLoader(); - ObjectTransfer objectTransfer = new ObjectTransfer(); - objectTransfer.transfer(OBJECTS); - for (Path path : objectTransfer.getObjectFiles()) { - nativeLoader.load(path); - } - isLoaded = true; + BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); + + proc.waitFor(); + + String output = ""; + + for(String curLine = reader.readLine(); curLine != null; curLine = reader.readLine()) { + output += curLine + " \n"; + } + + return output; + }catch(Exception ex) { + return ex.toString(); } + } - /** Loads an object file and remembers it was loaded. */ - final void load(Path name) { - if (!loaded.contains(name)) { - if (Files.isRegularFile(name)) { - System.load(name.toString()); - loaded.add(name); - } - } + /** Loads an object file and remembers it was loaded. */ + final void load(Path name) { + if (!loaded.contains(name)) { + if (Files.isRegularFile(name)) { + System.load(name.toString()); + loaded.add(name); + } } + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/ObjectTransfer.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/ObjectTransfer.java index cc8e5ca..5780357 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/ObjectTransfer.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/ObjectTransfer.java @@ -1,5 +1,10 @@ package de.sfuhrm.openssl4j; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -7,112 +12,151 @@ import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.sound.sampled.LineUnavailableException; /** - * Transfers the object files from the JAR file - * to a temporary directory. The temporary directory + * Transfers the object files from the JAR file to a temporary directory. The temporary directory * will be deleted when the JVM shuts down. + * * @author Stephan Fuhrmann */ final class ObjectTransfer { - /** The destination temporary directory. */ - private final Path targetDirectory; - - /** The libraries copies. */ - private final List libraries; - - ObjectTransfer() throws IOException { - targetDirectory = Files.createTempDirectory("native"); - libraries = new ArrayList<>(); - Runnable removeTarget = () -> { - for (Path p : libraries) { - try { - Files.delete(p); - } catch (IOException e) { - e.printStackTrace(); - } - } - try { - Files.delete(targetDirectory); - } catch (IOException e) { - e.printStackTrace(); - } - }; - Runtime.getRuntime().addShutdownHook(new Thread(removeTarget)); - } + /** The destination temporary directory. */ + private Path targetDirectory; - /** Gets a system property, enforcing that the value string is alphanumeric. - * @param property the name of the property to get. - * @return the value of the property consisting of its alphanumeric parts - * and the non-alphanumeric parts being replaced with an - * underscore character ('_'). - * @throws NullPointerException if property is null or not set. - * */ - static String getSystemPropertyAlnum(String property) { - Objects.requireNonNull(property); - - final String value = System.getProperty(property); - Objects.requireNonNull(value, "System property " + property + " is null"); - StringBuilder result = new StringBuilder(value.length()); - for (int i = 0; i < value.length(); i++) { - final char c = value.charAt(i); - if (Character.isLetterOrDigit(c)) { - result.append(c); - } else { - result.append('_'); - } - } - return result.toString(); - } + /** The libraries copies. */ + private final List libraries; - private static String getOsName() { - return getSystemPropertyAlnum("os.name"); - } + private static Pattern linuxLibRegex = Pattern.compile("(\\w+)(\\.\\d+)?"); + public Path getTargetDir() { + return targetDirectory; + } - private static String getArchName() { - return getSystemPropertyAlnum("os.arch"); - } + ObjectTransfer() throws IOException { + targetDirectory = Files.createTempDirectory("native"); + + libraries = new ArrayList<>(); - static String toLibraryName(String name) { - return name + "-" + getOsName() + "-" + getArchName() + ".so"; + Runnable removeTarget = + () -> { + for (Path p : libraries) { + try { + Files.delete(p); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + Files.delete(targetDirectory); + } catch (IOException e) { + e.printStackTrace(); + } + }; + Runtime.getRuntime().addShutdownHook(new Thread(removeTarget)); + } + + /** Gets a system property, enforcing that the value string is alphanumeric. + * @param property the name of the property to get. + * @return the value of the property consisting of its alphanumeric parts + * and the non-alphanumeric parts being replaced with an + * underscore character ('_'). + * @throws NullPointerException if property is null or not set. + * */ + static String getSystemPropertyAlnum(String property) { + Objects.requireNonNull(property); + final String value = System.getProperty(property); + Objects.requireNonNull(value, "System property " + property + " is null"); + StringBuilder result = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + final char c = value.charAt(i); + if (Character.isLetterOrDigit(c)) { + result.append(c); + } else { + result.append('_'); + } } + return result.toString(); + } + + public static String getOsName() { + return getSystemPropertyAlnum("os.name"); + } + + public static String getArchName() { + return getSystemPropertyAlnum("os.arch"); + } + + public static String toLibraryName(String name) { + switch(name) { + case "libopenssl4j": + return name + "-" + getOsName().replace(" ", "_") + "-" + getArchName().replace(" ", "_") + ".so"; + default: + { + if(getOsName().toLowerCase().contains("mac")) { + // Running on Mac, need dynlib files. + return name + ".dylib"; + } + + Matcher libScanner = linuxLibRegex.matcher(name); + + if(libScanner.find()) { + if(libScanner.groupCount() > 1 && libScanner.group(2) != null) { + return libScanner.group(1) + ".so" + libScanner.group(2); + } + } - final List getObjectFiles() { - return Collections.unmodifiableList(libraries); + return name + ".so"; + } } + } - final void transfer(String... names) throws IOException { - for (String name : names) { - String libName = toLibraryName(name); - Path targetLibraryPath = targetDirectory.resolve(libName); + final List getObjectFiles() { + return Collections.unmodifiableList(libraries); + } - try (InputStream inputStream = getClass().getResourceAsStream("/objects/" + libName)) { - if (inputStream != null) { - transferTo(inputStream, targetLibraryPath); - break; - } - } + final void transfer(String... names) throws IOException { + for (String name : names) { - try (InputStream inputStream = Files.newInputStream(Paths.get("src/main/resources/objects").resolve(libName))) { - transferTo(inputStream, targetLibraryPath); - break; - } + String libName = toLibraryName(name); + Path targetLibraryPath = targetDirectory.resolve(libName); + + try (InputStream inputStream = getClass().getResourceAsStream("/objects/" + libName)) { + if (inputStream != null) { + transferTo(inputStream, targetLibraryPath); + continue; } + } + + try (InputStream inputStream = + Files.newInputStream(Paths.get("src/main/resources/objects").resolve(libName))) { + transferTo(inputStream, targetLibraryPath); + continue; + } } - private void transferTo(InputStream inputStream, Path targetFile) throws IOException { - Files.copy(inputStream, targetFile); - Set set = new HashSet<>(); - set.add(PosixFilePermission.OWNER_EXECUTE); - set.add(PosixFilePermission.OWNER_READ); - Files.setPosixFilePermissions(targetFile, set); - libraries.add(targetFile); + try { + }catch (Exception ex) { + System.out.println("Error logging unpacking of jar. Error " + ex.toString()); } + + } + + private void transferTo(InputStream inputStream, Path targetFile) throws IOException { + Files.copy(inputStream, targetFile); + Files.setPosixFilePermissions( + targetFile, + new HashSet<>( + Arrays.asList(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ))); + libraries.add(targetFile); + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JException.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JException.java new file mode 100644 index 0000000..c8c0a49 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JException.java @@ -0,0 +1,12 @@ +package de.sfuhrm.openssl4j; + +public class OpenSSL4JException extends RuntimeException { + + public OpenSSL4JException(final String msg) { + super(msg); + } + + public OpenSSL4JException(final String msg, Throwable t) { + super(msg, t); + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProvider.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProvider.java index 56c4ce4..1814277 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProvider.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProvider.java @@ -1,135 +1,108 @@ package de.sfuhrm.openssl4j; import java.io.IOException; +import java.nio.file.Paths; +import java.security.PrivateKey; import java.security.Provider; import java.util.HashMap; import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * JCA provider directing all calls to the system native OpenSSL library. + * * @author Stephan Fuhrmann */ public final class OpenSSL4JProvider extends Provider { - /** The provider name as passed to JCA. */ - public final static String PROVIDER_NAME = "OpenSSL4J"; - - private static Set openSslMessageDigestAlgorithms; - - /** Constructor for the JCA Provider for OpenSSL JNI. - * @throws IllegalStateException if the native object file can't be loaded and the - * class can't be used. - * */ - public OpenSSL4JProvider() { - super(PROVIDER_NAME, - getLibraryVersion(), - "OpenSSL4J provider v" - + PropertyAccessor.get("version", "unknown") + ", implementing " - + "multiple message digest algorithms."); - - try { - NativeLoader.loadAll(); - if (openSslMessageDigestAlgorithms == null) { - openSslMessageDigestAlgorithms = OpenSSLMessageDigestNative.getMessageDigestList(); - } - - Map names = getNames(openSslMessageDigestAlgorithms); - putAll(names); - } catch (IOException e) { - throw new IllegalStateException("Could not initialize", e); - } - } + /** The provider name passed to JCA. */ + public static final String PROVIDER_NAME = "OpenSSL4J"; - private static double getLibraryVersion() { - double result = 0.0; - String stringVersion = PropertyAccessor.get("version", "0.0.0"); - Pattern versionPattern = Pattern.compile("(\\d+\\.\\d+).*"); - Matcher matcher = versionPattern.matcher(stringVersion); - if (matcher.matches()) { - result = Double.parseDouble(matcher.group(1)); - } - return result; - } + public static final String PROVIDER_INFO = PROVIDER_NAME + " JSP/JCE"; - /** Gets the names and the aliases of all message digest - * algorithms. - * @return a map mapping from algorithm name / alias to algorithm class. - * */ - private static Map getNames(Set availableOpenSslAlgorithmNames) { - Map result = getOpenSSLHashnames(availableOpenSslAlgorithmNames); - result.putAll(createAliases(result)); - return result; - } + private OpenSSLCryptoNative crypto = new OpenSSLCryptoNative(); - /** Creates some aliases for an input map. - * @param map a map with keys being algorithm names of the form "MessageDigest.MD5" - * and the keys being java class names. - * @return a map mapping from algorithm name / alias to algorithm class. - * */ - private static Map createAliases(Map map) { - Map aliases = new HashMap<>(); - Pattern pattern = Pattern.compile("([^0-9]*)-([0-9]+)"); - - for (Map.Entry entry : map.entrySet()) { - Matcher matcher = pattern.matcher(entry.getKey()); - if (matcher.matches()) { - - // adds for MessageDigest.SHA512 an alias like MessageDigest.SHA-512 - aliases.put( - matcher.group(1) + matcher.group(2), - entry.getValue()); - } - } - aliases.put("MessageDigest.SHA", map.get("MessageDigest.SHA1")); - return aliases; - } + private static OpenSSL4JProvider instance = null; + + /** + * Constructor for the JCA Provider for OpenSSL JNI. + * + * @throws IllegalStateException if the native object file can't be loaded and the class can't be + * used. + */ + + public static synchronized OpenSSL4JProvider getInstance() { + if(OpenSSL4JProvider.instance == null) { + OpenSSL4JProvider.instance = new OpenSSL4JProvider(); + } + + return OpenSSL4JProvider.instance; + } + + private OpenSSL4JProvider() { + // TODO: Get the major.middle versions from somewhere instead of hardcoding (the original + // gets them from Maven). Unfortunately a double can't support major.middle.minor because it + // only has one decimal point. + super(PROVIDER_NAME, 1.0, PROVIDER_INFO); + + try { + + // Load. + NativeLoader.loadAll(); + + // Initialize sets of names if not already initialized. + if (OpenSSL4JProviderUtils.openSslMessageDigestAlgorithms == null) { + OpenSSL4JProviderUtils.openSslMessageDigestAlgorithms = + OpenSSLMessageDigestNative.getMessageDigestList(); + } + if (OpenSSL4JProviderUtils.openSslCiphers == null) { + OpenSSL4JProviderUtils.openSslCiphers = OpenSSLCipherNative.getCipherList(); + } - /** Name pairs mapping from SSL to Java. - * First one is SSL name, second one is Java name. - * */ - private static final String[] SSL_TO_JAVA_NAMES = { - "MD5", "MD5", - "SHA1", "SHA1", - "SHA224", "SHA-224", - "SHA256", "SHA-256", - "SHA384", "SHA-384", - "SHA512", "SHA-512", - "SHA512-224", "SHA-512/224", - "SHA512-256", "SHA-512/256", - "SHA3-224", "SHA3-224", - "SHA3-256", "SHA3-256", - "SHA3-384", "SHA3-384", - "SHA3-512", "SHA3-512", - "BLAKE2b512", "BLAKE2b512", - "BLAKE2s256", "BLAKE2s256", - "MD4", "MD4", - "RIPEMD160", "RIPEMD160", - "SM3", "SM3", - "whirlpool", "Whirlpool" - }; - - /** Fills a map with the names of all algorithms in - * OpenSSL-JNA. - * @return mapping from algorithm name to class name. - * */ - private static Map getOpenSSLHashnames(Set availableOpenSslAlgos) { - Map map = new HashMap<>(); - - for (int i = 0; i < SSL_TO_JAVA_NAMES.length; i+= 2) { - String sslName = SSL_TO_JAVA_NAMES[i]; - String javaName = SSL_TO_JAVA_NAMES[i + 1]; - - // only if OpenSSL has the algorithm available, add it - if (availableOpenSslAlgos.contains(sslName)) { - String javaClass = MessageDigest.class.getName() + "$" + - (javaName.replaceAll("-", "_").replaceAll("/", "_")); - map.put("MessageDigest." + javaName, javaClass); - } - } - - return map; + // Sanity check. This should never happen, because it would be a serious bug in OpenSSL. + OpenSSL4JProviderUtils.assertNoOverlap( + OpenSSL4JProviderUtils.openSslMessageDigestAlgorithms, + OpenSSL4JProviderUtils.openSslCiphers); + + // Output debug info if enabled. + if (OpenSSL4JProviderUtils.DEBUG) { + OpenSSL4JProviderUtils.dumpNames( + "MESSAGE DIGEST ALGORITHMS", OpenSSL4JProviderUtils.openSslMessageDigestAlgorithms); + OpenSSL4JProviderUtils.dumpNames("CIPHERS", OpenSSL4JProviderUtils.openSslCiphers); + } + + // Register names and implementations with parent Provider class. + Map messageDigestNames = + OpenSSL4JProviderUtils.getMessageDigestAlgorithmNames( + OpenSSL4JProviderUtils.openSslMessageDigestAlgorithms); + Map cipherNames = + OpenSSL4JProviderUtils.getCipherNames(OpenSSL4JProviderUtils.openSslCiphers); + + // Output debug info if enabled. + if (OpenSSL4JProviderUtils.DEBUG) { + OpenSSL4JProviderUtils.dumpNames( + "MESSAGE DIGEST NAMES AND IMPLEMENTATIONS", messageDigestNames); + OpenSSL4JProviderUtils.dumpNames("CIPHER NAMES AND IMPLEMENTATIONS", cipherNames); + } + + Map messageDigestAndCipherNames = new HashMap<>(); + messageDigestAndCipherNames.putAll(messageDigestNames); + messageDigestAndCipherNames.putAll(cipherNames); + putAll(messageDigestAndCipherNames); + + String defaultSecureRandomClassName = SecureRandom.class.getName() + "$DEFAULT"; + + put("SecureRandom.DEFAULT", defaultSecureRandomClassName); + + put("Mac.HMACSHA256", Mac.class.getName() + "$HMAC_SHA256"); + + // putService(new Service(this, "SecureRandom", "DEFAULT", defaultSecureRandomClassName, null, null)); + + } catch (IOException e) { + throw new IllegalStateException("Could not initialize: " + e.getMessage(), e); } + } + + public PrivateKey decryptPrivateKey(String privateKey, String passPhrase) { + return crypto.decryptPrivateKey(privateKey, passPhrase); + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProviderUtils.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProviderUtils.java new file mode 100644 index 0000000..0fc0918 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProviderUtils.java @@ -0,0 +1,357 @@ +package de.sfuhrm.openssl4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OpenSSL4JProviderUtils { + + private OpenSSL4JProviderUtils() {} + + static final boolean DEBUG = false; + + /** + * Name pairs mapping from OpenSSL message-digest algorithm names to Java lookup names. First one + * is message-digest algorithm name, second one is Java lookup name. + */ + static final String[] MESSAGE_DIGEST_ALGORITHM_NAMES_TO_JAVA_LOOKUP_NAMES = { + "BLAKE2b512", "BLAKE2b512", + "BLAKE2s256", "BLAKE2s256", + "MD4", "MD4", + "MD5", "MD5", + "RIPEMD160", "RIPEMD160", + "SHA1", "SHA1", + "SHA224", "SHA-224", + "SHA256", "SHA-256", + "SHA3-224", "SHA3-224", + "SHA3-256", "SHA3-256", + "SHA3-384", "SHA3-384", + "SHA3-512", "SHA3-512", + "SHA384", "SHA-384", + "SHA512", "SHA-512", + "SHA512-224", "SHA-512/224", + "SHA512-256", "SHA-512/256", + "SM3", "SM3", + "whirlpool", "Whirlpool" + }; + + static Set openSslMessageDigestAlgorithms; + + /** + * Fills a map with the names of all algorithms in OpenSSL-JNA. + * + * @return mapping from algorithm name to class name. + */ + static Map getOpenSSLMessageDigestAlgorithmsToImplementations( + Set availableOpenSslMessageDigestAlgorithms) { + Map map = new HashMap<>(); + + for (int i = 0; i < MESSAGE_DIGEST_ALGORITHM_NAMES_TO_JAVA_LOOKUP_NAMES.length; i += 2) { + String sslName = MESSAGE_DIGEST_ALGORITHM_NAMES_TO_JAVA_LOOKUP_NAMES[i]; + String javaName = MESSAGE_DIGEST_ALGORITHM_NAMES_TO_JAVA_LOOKUP_NAMES[i + 1]; + + // Only if OpenSSL has the algorithm available, add it. + if (availableOpenSslMessageDigestAlgorithms.contains(sslName)) { + String javaClass = + MessageDigest.class.getName() + "$" + (javaName.replace("-", "_").replace("/", "_")); + map.put("MessageDigest." + javaName, javaClass); + } + } + + return map; + } + + static final Pattern MESSAGE_DIGEST_ALIAS_PATTERN = Pattern.compile("([^0-9]*)-([0-9]+)"); + + /** + * Creates some aliases for an input map. The motivation is that the Sun provider accepts, for + * example, "SHA1" and "SHA-1", and this library needs to stay API compatible as much as possible + * in order to be a drop-in replacement for the Sun provider. + * + * @param map a map with keys being algorithm names of the form "MessageDigest.MD5" and the keys + * being Java class names. + * @return a map mapping from algorithm name / alias to algorithm class. + */ + static Map createMessageDigestAliases(Map map) { + Map aliases = new HashMap<>(); + + for (Map.Entry entry : map.entrySet()) { + Matcher matcher = MESSAGE_DIGEST_ALIAS_PATTERN.matcher(entry.getKey()); + if (matcher.matches()) { + + // Adds for MessageDigest.SHA512 an alias like MessageDigest.SHA-512. + aliases.put(matcher.group(1) + matcher.group(2), entry.getValue()); + } + } + aliases.put("MessageDigest.SHA", map.get("MessageDigest.SHA1")); + return aliases; + } + + /** + * Gets the names and the aliases of all message-digest algorithms. + * + * @return a map mapping from algorithm name/alias to message-digest algorithm class. + */ + static Map getMessageDigestAlgorithmNames( + Set availableOpenSslMessageDigestAlgorithmNames) { + Map result = + getOpenSSLMessageDigestAlgorithmsToImplementations( + availableOpenSslMessageDigestAlgorithmNames); + result.putAll(createMessageDigestAliases(result)); + return result; + } + + /** + * Name pairs mapping from OpenSSL cipher names to Java lookup names. First one is cipher name, + * second one is Java lookup name. + */ + static final String[] CIPHER_NAMES_TO_JAVA_LOOKUP_NAMES = { + "AES-128-CBC", "AES-128-CBC", + "AES-128-CFB", "AES-128-CFB", + "AES-128-CFB1", "AES-128-CFB1", + "AES-128-CFB8", "AES-128-CFB8", + "AES-128-CTR", "AES-128-CTR", + "AES-128-ECB", "AES-128-ECB", + "AES-128-OCB", "AES-128-OCB", + "AES-128-OFB", "AES-128-OFB", + "AES-128-XTS", "AES-128-XTS", + "AES-192-CBC", "AES-192-CBC", + "AES-192-CFB", "AES-192-CFB", + "AES-192-CFB1", "AES-192-CFB1", + "AES-192-CFB8", "AES-192-CFB8", + "AES-192-CTR", "AES-192-CTR", + "AES-192-ECB", "AES-192-ECB", + "AES-192-OCB", "AES-192-OCB", + "AES-192-OFB", "AES-192-OFB", + "AES-256-CBC", "AES-256-CBC", + "AES-256-CFB", "AES-256-CFB", + "AES-256-CFB1", "AES-256-CFB1", + "AES-256-CFB8", "AES-256-CFB8", + "AES-256-CTR", "AES-256-CTR", + "AES-256-ECB", "AES-256-ECB", + "AES-256-OCB", "AES-256-OCB", + "AES-256-OFB", "AES-256-OFB", + "AES-256-XTS", "AES-256-XTS", + "ARIA-128-CBC", "ARIA-128-CBC", + "ARIA-128-CCM", "ARIA-128-CCM", + "ARIA-128-CFB", "ARIA-128-CFB", + "ARIA-128-CFB1", "ARIA-128-CFB1", + "ARIA-128-CFB8", "ARIA-128-CFB8", + "ARIA-128-CTR", "ARIA-128-CTR", + "ARIA-128-ECB", "ARIA-128-ECB", + "ARIA-128-GCM", "ARIA-128-GCM", + "ARIA-128-OFB", "ARIA-128-OFB", + "ARIA-192-CBC", "ARIA-192-CBC", + "ARIA-192-CCM", "ARIA-192-CCM", + "ARIA-192-CFB", "ARIA-192-CFB", + "ARIA-192-CFB1", "ARIA-192-CFB1", + "ARIA-192-CFB8", "ARIA-192-CFB8", + "ARIA-192-CTR", "ARIA-192-CTR", + "ARIA-192-ECB", "ARIA-192-ECB", + "ARIA-192-GCM", "ARIA-192-GCM", + "ARIA-192-OFB", "ARIA-192-OFB", + "ARIA-256-CBC", "ARIA-256-CBC", + "ARIA-256-CCM", "ARIA-256-CCM", + "ARIA-256-CFB", "ARIA-256-CFB", + "ARIA-256-CFB1", "ARIA-256-CFB1", + "ARIA-256-CFB8", "ARIA-256-CFB8", + "ARIA-256-CTR", "ARIA-256-CTR", + "ARIA-256-ECB", "ARIA-256-ECB", + "ARIA-256-GCM", "ARIA-256-GCM", + "ARIA-256-OFB", "ARIA-256-OFB", + "BF-CBC", "BF-CBC", + "BF-CFB", "BF-CFB", + "BF-ECB", "BF-ECB", + "BF-OFB", "BF-OFB", + "CAMELLIA-128-CBC", "CAMELLIA-128-CBC", + "CAMELLIA-128-CFB", "CAMELLIA-128-CFB", + "CAMELLIA-128-CFB1", "CAMELLIA-128-CFB1", + "CAMELLIA-128-CFB8", "CAMELLIA-128-CFB8", + "CAMELLIA-128-CTR", "CAMELLIA-128-CTR", + "CAMELLIA-128-ECB", "CAMELLIA-128-ECB", + "CAMELLIA-128-OFB", "CAMELLIA-128-OFB", + "CAMELLIA-192-CBC", "CAMELLIA-192-CBC", + "CAMELLIA-192-CFB", "CAMELLIA-192-CFB", + "CAMELLIA-192-CFB1", "CAMELLIA-192-CFB1", + "CAMELLIA-192-CFB8", "CAMELLIA-192-CFB8", + "CAMELLIA-192-CTR", "CAMELLIA-192-CTR", + "CAMELLIA-192-ECB", "CAMELLIA-192-ECB", + "CAMELLIA-192-OFB", "CAMELLIA-192-OFB", + "CAMELLIA-256-CBC", "CAMELLIA-256-CBC", + "CAMELLIA-256-CFB", "CAMELLIA-256-CFB", + "CAMELLIA-256-CFB1", "CAMELLIA-256-CFB1", + "CAMELLIA-256-CFB8", "CAMELLIA-256-CFB8", + "CAMELLIA-256-CTR", "CAMELLIA-256-CTR", + "CAMELLIA-256-ECB", "CAMELLIA-256-ECB", + "CAMELLIA-256-OFB", "CAMELLIA-256-OFB", + "CAST5-CBC", "CAST5-CBC", + "CAST5-CFB", "CAST5-CFB", + "CAST5-ECB", "CAST5-ECB", + "CAST5-OFB", "CAST5-OFB", + "ChaCha20", "ChaCha20", + "ChaCha20-Poly1305", "ChaCha20-Poly1305", + "DES-CBC", "DES-CBC", + "DES-CFB", "DES-CFB", + "DES-CFB1", "DES-CFB1", + "DES-CFB8", "DES-CFB8", + "DES-ECB", "DES-ECB", + "DES-EDE", "DES-EDE", + "DES-EDE-CBC", "DES-EDE-CBC", + "DES-EDE-CFB", "DES-EDE-CFB", + "DES-EDE-OFB", "DES-EDE-OFB", + "DES-EDE3", "DES-EDE3", + "DES-EDE3-CBC", "DES-EDE3-CBC", + "DES-EDE3-CFB", "DES-EDE3-CFB", + "DES-EDE3-CFB1", "DES-EDE3-CFB1", + "DES-EDE3-CFB8", "DES-EDE3-CFB8", + "DES-EDE3-OFB", "DES-EDE3-OFB", + "DES-OFB", "DES-OFB", + "DESX-CBC", "DESX-CBC", + "IDEA-CBC", "IDEA-CBC", + "IDEA-CFB", "IDEA-CFB", + "IDEA-ECB", "IDEA-ECB", + "IDEA-OFB", "IDEA-OFB", + "RC2-40-CBC", "RC2-40-CBC", + "RC2-64-CBC", "RC2-64-CBC", + "RC2-CBC", "RC2-CBC", + "RC2-CFB", "RC2-CFB", + "RC2-ECB", "RC2-ECB", + "RC2-OFB", "RC2-OFB", + "RC4", "RC4", + "RC4-40", "RC4-40", + "RC4-HMAC-MD5", "RC4-HMAC-MD5", + "SEED-CBC", "SEED-CBC", + "SEED-CFB", "SEED-CFB", + "SEED-ECB", "SEED-ECB", + "SEED-OFB", "SEED-OFB", + "SM4-CBC", "SM4-CBC", + "SM4-CFB", "SM4-CFB", + "SM4-CTR", "SM4-CTR", + "SM4-ECB", "SM4-ECB", + "SM4-OFB", "SM4-OFB", + "id-aes128-CCM", "id-aes128-CCM", + "id-aes128-GCM", "id-aes128-GCM", + "id-aes128-wrap", "id-aes128-wrap", + "id-aes128-wrap-pad", "id-aes128-wrap-pad", + "id-aes192-CCM", "id-aes192-CCM", + "id-aes192-GCM", "id-aes192-GCM", + "id-aes192-wrap", "id-aes192-wrap", + "id-aes192-wrap-pad", "id-aes192-wrap-pad", + "id-aes256-CCM", "id-aes256-CCM", + "id-aes256-GCM", "id-aes256-GCM", + "id-aes256-wrap", "id-aes256-wrap", + "id-aes256-wrap-pad", "id-aes256-wrap-pad", + "id-smime-alg-CMS3DESwrap", "id-smime-alg-CMS3DESwrap" + }; + + static Set openSslCiphers; + + /** + * Fills a map with the names of all ciphers in OpenSSL-JNA. + * + * @return mapping from cipher name to class name. + */ + static Map getOpenSSLCiphersToImplementations( + Set availableOpenSslCiphers) { + Map map = new HashMap<>(); + + for (int i = 0; i < CIPHER_NAMES_TO_JAVA_LOOKUP_NAMES.length; i += 2) { + String sslName = CIPHER_NAMES_TO_JAVA_LOOKUP_NAMES[i]; + String javaName = CIPHER_NAMES_TO_JAVA_LOOKUP_NAMES[i + 1]; + + // Only if OpenSSL has the cipher available, add it. + if (availableOpenSslCiphers.contains(sslName)) { + String javaClass = + Cipher.class.getName() + "$" + (javaName.replace("-", "_").replace("/", "_")); + map.put("Cipher." + javaName, javaClass); + } + } + + return map; + } + + /** + * Creates some aliases for an input map. The motivation is that the Sun provider accepts, for + * example, "SHA1" and "SHA-1", and this library needs to stay API compatible as much as possible + * in order to be a drop-in replacement for the Sun provider. + * + * @param map a map with keys being cipher names of the form "Cipher.XYZ" and the keys being Java + * class names. + * @return a map mapping from cipher name / alias to cipher class. + */ + static Map createCipherAliases(Map map) { + Map aliases = new HashMap<>(); + + for (Map.Entry entry : map.entrySet()) { + aliases.put(entry.getKey().replace("-", ""), entry.getValue()); + } + return aliases; + } + + /** + * Gets the names of all ciphers. + * + * @return a map mapping from cipher name to cipher class. + */ + static Map getCipherNames(Set availableOpenSslCipherNames) { + Map result = getOpenSSLCiphersToImplementations(availableOpenSslCipherNames); + result.putAll(createCipherAliases(result)); + result.put("Cipher.AES", "de.sfuhrm.openssl4j.Cipher$AES"); + result.put("Cipher.HMACSHA256", "de.sfuhrm.openssl4j.Cipher$HMACSHA256"); + result.put("Cipher.SHA256", "de.sfuhrm.openssl4j.Cipher$HMACSHA256"); + return result; + } + + static String overlapErrorMessage(Set intersection) { + StringBuilder builder = new StringBuilder(); + builder.append("Overlap between message-digest and cipher names: "); + boolean first = true; + for (String overlap : intersection) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(overlap); + } + return builder.toString(); + } + + static void assertNoOverlap( + Set openSslMessageDigestAlgorithms, Set openSslCiphers) { + Set intersection = new HashSet<>(openSslMessageDigestAlgorithms); + intersection.retainAll(openSslCiphers); + if (!intersection.isEmpty()) { + throw new IllegalStateException(overlapErrorMessage(intersection)); + } + intersection = new HashSet<>(openSslCiphers); + intersection.retainAll(openSslMessageDigestAlgorithms); + if (!intersection.isEmpty()) { + throw new IllegalStateException(overlapErrorMessage(intersection)); + } + } + + static void dumpNames(String heading, Set names) { + System.out.println(heading + ":"); + List sorted = new ArrayList<>(names); + Collections.sort(sorted); + for (String name : sorted) { + System.out.println("\t" + name); + } + } + + static void dumpNames(String heading, Map names) { + System.out.println(heading + ":"); + List sorted = new ArrayList<>(names.keySet()); + Collections.sort(sorted); + for (String key : sorted) { + System.out.println("\t" + key + " -> " + names.get(key)); + } + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCipherNative.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCipherNative.java new file mode 100644 index 0000000..9a67003 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCipherNative.java @@ -0,0 +1,423 @@ +package de.sfuhrm.openssl4j; + +import java.io.Console; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; + +import de.sfuhrm.openssl4j.OpenSSLCryptoNative; + +/** + * An interface to OpenSSL cipher functions. + * + * @author Stephan Fuhrmann + */ +class OpenSSLCipherNative extends CipherSpi { + + /** + * Get the list of Cipher algorithms supported by OpenSSL. + * + * @return an array of supported cipher algorithms from the OpenSSL library. + */ + private static native String[] listCiphers(); + + /** + * Returns the context size in bytes. This is used to allocate the {@link #context direct + * ByteBuffer}. + * + * @return a ByteBuffer containing the native cipher context. + */ + private native ByteBuffer nativeContext(); + + /** + * Removes a context allocated with {@linkplain #nativeContext()}. + * + * @param context the context to free. + */ + private static native void removeContext(ByteBuffer context); + + private static native int engineInitNative( + ByteBuffer context, String name, byte[] key, byte[] iv, int encrypt, long libCtx); + + private static native int getBlockSizeNative(ByteBuffer context, String name, long libCtx); + + private static native byte[] getOriginalIVNative(ByteBuffer context); + + private static native int updateNative( + ByteBuffer context, byte[] out, byte[] in, int inLen, int encrypt, int inputOffset); + + private static native int doFinalNative(ByteBuffer context, byte[] out, int encrypt, int outputOffset); + + private static native int setGCMTagNative(ByteBuffer context, byte[] tag); + private static native int getGCMTagNative(ByteBuffer context, byte[] tag); + + private static native int GCMIVToCTRIVNative(byte[] key, byte[] iv, byte[] ivOut, long libCtxInt); + + /** + * Get the list of digest algorithms supported by the OpenSSL library. + * + * @return a Set of supported message digest algorithms. + */ + protected static Set getCipherList() { + String[] messageDigestAlgorithms = listCiphers(); + return new HashSet<>(Arrays.asList(messageDigestAlgorithms)); + } + + /** + * A native message digest context where the state of the current calculation is stored. Allocated + * with {@linkplain #nativeContext()}, freed by the {@linkplain PhantomReferenceCleanup} with + * {@linkplain #free(ByteBuffer)}. + */ + private final ByteBuffer context; + + /** The OpenSSL algorithm name as returned by {@linkplain #listCiphers()}. */ + private final String algorithmBaseName; + + private String algoMode = "", algoPadding = ""; + private int blockSize = -1; + private int OperationMode = 0; + private long libCtx = 0; + + private AlgorithmParameters algoParams; + + OpenSSLCipherNative(String openSslName) { + try { + NativeLoader.loadAll(); + algorithmBaseName = Objects.requireNonNull(openSslName); + context = nativeContext(); + PhantomReferenceCleanup.enqueueForCleanup(this, OpenSSLCipherNative::free, context); + libCtx = OpenSSLCryptoNative.getLibInstance(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private String getAlgorithimString() { + if(algorithmBaseName == "AES-256-GCM") { + return "id-aes256-GCM"; + } + + return algorithmBaseName; + } + + /** + * Free the native context that came from {@linkplain #nativeContext()}. + * + * @param context the context allocated with {@linkplain #nativeContext()}. + */ + protected static void free(ByteBuffer context) { + Objects.requireNonNull(context); + if (!context.isDirect()) { + throw new IllegalStateException("Illegal buffer passed in"); + } + removeContext(context); + } + + public void setOpenSSLLibCtx(long opensslLibCtx) { + libCtx = opensslLibCtx; + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + algoMode = mode; + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + if (padding.equals("NoPadding")) { + algoPadding = ""; + return; + } + algoPadding = padding; + } + + @Override + protected int engineGetBlockSize() { + if (this.blockSize < 0) { + if(getAlgorithimString().toLowerCase().contains("aes")) { + this.blockSize = 16; // AES is special: OpenSSL returns a block size of 1 but we need it to be 16. + }else { + this.blockSize = + getBlockSizeNative( + context, + getAlgorithimString(), + libCtx); // Cache the block size. It will remain constant for this cipher. + System.out.flush(); + } + + } + return this.blockSize; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + // Based on details from https://www.openssl.org/docs/man3.1/man3/EVP_EncryptUpdate.html + int blockSize = engineGetBlockSize(); + + int fullBlocks = inputLen / blockSize; + + if((inputLen % blockSize) > 0) { + fullBlocks ++; + } + + int baseOutputSize = fullBlocks * blockSize; + + if(getAlgorithimString().toLowerCase().contains("gcm") && OperationMode == Cipher.ENCRYPT_MODE) { + baseOutputSize += 16; + } + + return baseOutputSize; + } + + @Override + protected byte[] engineGetIV() { + return getOriginalIVNative(context); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return algoParams; + } + + @Override + protected void engineInit(final int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + this.blockSize = -1; // Reset block size on engine init. + OperationMode = opmode; + byte[] iv = new byte[engineGetBlockSize()]; + random.nextBytes(iv); + int res = + engineInitNative( + context, + getAlgorithimString(), + key.getEncoded(), + iv, + OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0, + libCtx); + + if (res == 0) { + throw new InvalidKeyException("set cipher returned 0"); + } + } + + @Override + protected void engineInit( + final int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + this.blockSize = -1; // Reset block size on engine init. + OperationMode = opmode; + byte[] iv = new byte[engineGetBlockSize()]; + + if(params instanceof GCMParameterSpec) { + iv = ((GCMParameterSpec) params).getIV(); + }else if (params instanceof IvParameterSpec){ + iv = ((IvParameterSpec) params).getIV(); + } else { + random.nextBytes(iv); // Genereate iv if none is provided or throw an error? + } + + int res = + engineInitNative( + context, + getAlgorithimString(), + key.getEncoded(), + iv, + OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0, + libCtx); + + if (res == 0) { + throw new InvalidKeyException("set cipher returned 0"); + } + } + + @Override + protected void engineInit( + final int opmode, Key key, AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + this.blockSize = -1; // Reset block size on engine init. + OperationMode = opmode; + byte[] iv; + try { + iv = params.getParameterSpec(IvParameterSpec.class).getIV(); + } catch (Exception ex) { + try { + iv = params.getParameterSpec(GCMParameterSpec.class).getIV(); + }catch(Exception ex2) { + iv = new byte[engineGetBlockSize()]; + random.nextBytes( + iv); // Fallback and generate our own IV if there is an error getting the supplied IV. + } + } + + algoParams = params; + + int res = + engineInitNative( + context, + getAlgorithimString(), + key.getEncoded(), + iv, + OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0, + libCtx); + + if (res == 0) { + throw new InvalidKeyException("set cipher returned 0"); + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + int outputLen = engineGetOutputSize(inputLen); + byte[] tempOutput = new byte[outputLen]; + + int encrypt = OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0; + + int res = updateNative(context, tempOutput, input, inputLen, encrypt, inputOffset); + + if (res < 0) { + return null; + } else if (res == outputLen) { + return tempOutput; + } + + byte[] output = new byte[res]; + + System.arraycopy( + tempOutput, + 0, + output, + 0, + res); // Shrink array. Result can be up to output len size, so it could be smaller. + + return output; + } + + @Override + protected int engineUpdate( + byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException { + int outputLen = output.length - outputOffset; + + if (outputLen < engineGetOutputSize(inputLen)) { + throw new ShortBufferException(); + } + + byte[] tempOutput = new byte[outputLen]; + + int encrypt = OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0; + + int res = updateNative(context, tempOutput, input, inputLen, encrypt, inputOffset); + + if (res < 0) { + return res; // Or Throw Exception? not sure or maybe return 0? + } + + for (int i = 0, j = outputOffset; i < res && j < output.length; i++, j++) { + output[j] = tempOutput[i]; + } + + return res; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + + int outputSize = engineGetOutputSize(inputLen); + + byte[] tmpOutput = new byte[outputSize]; + + int size = 0; + + try { + size = engineDoFinal(input, inputOffset, inputLen, tmpOutput, 0); + } catch (ShortBufferException ex) { + // Pass exception back to caller. + throw new RuntimeException(ex); + } + + if (size < 0) { + return null; + } else if (size == outputSize) { + return tmpOutput; + } else if (size == 0) { + return new byte[0]; + } + + byte[] output = new byte[size]; + + System.arraycopy(tmpOutput, 0, output, 0, size); + + return output; + } + + @Override + protected int engineDoFinal( + byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + + int totalLen = 0; + int encrypt = (OperationMode == Cipher.ENCRYPT_MODE ? 1 : 0); + byte[] tag = new byte[0]; + + if(encrypt == 0 && getAlgorithimString().toLowerCase().contains("gcm")) { + // Pull the last 16 bytes off the input to use as the tag if decrypting. + + tag = Arrays.copyOfRange(input, inputLen - 16, inputLen); + inputLen -= 16; + } + + if (inputLen > 0) { + int byteCount = engineUpdate(input, inputOffset, inputLen, output, outputOffset); + if (byteCount == 0) { + return 0; + } + outputOffset += byteCount; + totalLen += byteCount; + } + + if(encrypt == 0 && getAlgorithimString().toLowerCase().contains("gcm")) { + // Must set the "tag" grabbed earlier before do final is called when decrypting. + setGCMTagNative(context, tag); + } + + int len = doFinalNative(context, output, encrypt, outputOffset); + + if(encrypt == 1 && getAlgorithimString().toLowerCase().contains("gcm")) { + // In this case we must get the tag and append it to the output. + tag = new byte[16]; + + getGCMTagNative(context, tag); + + if(totalLen + len + 16 > output.length) { + throw new RuntimeException("Unable to append GCM tag to encrypted data. Output buffer was not large enough"); + }else { + for(int i = totalLen + len, j = 0; j < 16; i++, j++, len++) { + output[i] = tag[j]; + } + } + } + + return totalLen + len; + } +} + +// Read into AEAD INTERFACE. See https://www.openssl.org/docs/man3.1/man3/EVP_EncryptInit.html for more info about AES GCM. \ No newline at end of file diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java new file mode 100644 index 0000000..d59d372 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLCryptoNative.java @@ -0,0 +1,156 @@ +package de.sfuhrm.openssl4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +public class OpenSSLCryptoNative { + + public static native int FIPSMode(long handle); + + public static native long CreateOpenSSLLibNative(int fips, String config, String libDir); + public static native byte[] decryptPrivateKeyNative(String privateKey, String passPhrase); + + private static long libHandle = -1; + private static boolean configsUnpacked = true; + + private static String openssl4JBasePath = "/openssl4j"; + + static String[] ConfFilesToUnpack = {"openssl.cnf"}; + + public static Path getConfigDir() { + if(System.getProperty("os.name").contains("Mac") || System.getProperty("os.name").contains("mac")) { + return Paths.get("/", "etc", "openssl3"); + }else { + return Paths.get("/", "etc", "ssl", "ossl3"); + } + } + + public static String getConfigResourceName(String fileName) { + if(System.getProperty("os.name").contains("Mac") || System.getProperty("os.name").contains("mac")) { + if(ObjectTransfer.getArchName().equals("aarch64")) { + return "mac-config/arm/" + fileName; + } + return "mac-config/x86/" + fileName; + + }else { + return "rhel/x86/" + fileName; + } + } + + static synchronized void installOpenSSLConfigs() { + if(configsUnpacked) { + return; + } + + if(!(System.getProperty("os.name").contains("Mac") || System.getProperty("os.name").contains("mac"))){ + // OS is not Mac, so the conf files should be on the disk. + configsUnpacked = true; + return; + } + + Path exportDir = getConfigDir(); + exportDir.toFile().mkdirs(); + + for(int i = 0; i < ConfFilesToUnpack.length; i++) { + String libName = ConfFilesToUnpack[i]; + + try { + InputStream inputStream = OpenSSLCryptoNative.class.getClassLoader().getResourceAsStream(getConfigResourceName(libName)); + + if(inputStream == null) { + throw new RuntimeException("Failed to load openssl conf resource stream. Stream is null. Path: " + getConfigResourceName(libName)); + } + + if(exportDir.resolve(libName).toFile().exists()) { + boolean match = true; + + InputStream existingFile = new FileInputStream(exportDir.resolve(libName).toFile()); + + for(int lhs = inputStream.read(), rhs = existingFile.read(); inputStream.available() > 0 && existingFile.available() > 0; lhs = inputStream.read(), rhs = existingFile.read()) { + if(lhs != rhs) { + break; + } + } + + if(inputStream.available() > 0 || existingFile.available() > 0) { + match = false; + } + + if(match) { + continue; + }else { + existingFile.close(); + exportDir.resolve(libName).toFile().delete(); + inputStream.close(); + inputStream = OpenSSLCryptoNative.class.getClassLoader().getResourceAsStream(libName); // Reset stream. + } + } + + Files.createDirectories(exportDir); + Files.copy(inputStream, exportDir.resolve(libName)); + + }catch(Exception ex) { + System.err.println("Failed to load resource stream"); + throw new RuntimeException(ex); + } + } + + configsUnpacked = true; + } + + public static synchronized long getLibInstance() { + installOpenSSLConfigs(); + Path confFile = getConfigDir().resolve("openssl.cnf"); + + System.out.flush(); + if (libHandle == -1) { + Path libDirPath = NativeLoader.getLibraryDir(); + String libDirString = ""; + + if(System.getProperty("os.name").contains("Mac") || System.getProperty("os.name").contains("mac")) { + libDirString = Paths.get(openssl4JBasePath, "/OpenSSL/lib/ossl-modules/").toString(); + }else { + libDirString = "/usr/lib64/ossl-modules/"; + } + + libHandle = CreateOpenSSLLibNative(1, confFile.toString(), libDirString); + } + + return libHandle; + } + + PrivateKey decryptPrivateKey(String privateKey, String passPhrase) { + + byte[] pKeyBytes = decryptPrivateKeyNative(privateKey, passPhrase); + + if(pKeyBytes == null || pKeyBytes.length == 0) { + throw new RuntimeException("decryptPrivateKeyNative returned a null byte array"); + } + + String pKeyStr = new String(pKeyBytes); + pKeyStr = pKeyStr.replace("-----BEGIN PRIVATE KEY-----", "").replaceAll(System.lineSeparator(), ""); + pKeyStr = pKeyStr.replace("-----END PRIVATE KEY-----", ""); + + byte[] b = Base64.getMimeDecoder().decode(pKeyStr); + + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b); + PrivateKey privKey = kf.generatePrivate(keySpec); + return privKey; + }catch (Exception ex) { + System.out.println("Error decrypting private key:"); + System.out.println(ex); + } + return null; + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLFIPSMode.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLFIPSMode.java new file mode 100644 index 0000000..3036307 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLFIPSMode.java @@ -0,0 +1,13 @@ +package de.sfuhrm.openssl4j; + +public class OpenSSLFIPSMode { + + public static boolean isReady() { + try { + long handle = OpenSSLCryptoNative.getLibInstance(); + return OpenSSLCryptoNative.FIPSMode(handle) == 1; + } catch (Exception e) { + throw new OpenSSL4JException("Unable to get FIPS isReady: " + e.getMessage(), e); + } + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMacNative.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMacNative.java new file mode 100644 index 0000000..96d06de --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMacNative.java @@ -0,0 +1,81 @@ +package de.sfuhrm.openssl4j; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; + +import javax.crypto.MacSpi; + +import de.sfuhrm.openssl4j.OpenSSLCryptoNative; + +public class OpenSSLMacNative extends MacSpi { + + String curAlgorithim; + Key curKey; + ArrayList buf = new ArrayList(); + + private static native int getMaxMacLength(); + private static native int hmacNative(long libCtx, String algName, byte[] key, int keyLen, byte[] data, int dataLen, byte[] output); + + + OpenSSLMacNative(String algorithim) { + curAlgorithim = algorithim; + } + + @Override + protected int engineGetMacLength() { + return getMaxMacLength(); + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + curKey = key; + } + + @Override + protected void engineUpdate(byte input) { + buf.add(input); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + for(int i = offset; i < len && i < input.length; i++) { + buf.add(input[i]); + } + } + + @Override + protected byte[] engineDoFinal() { + byte[] resp = new byte[engineGetMacLength()]; + byte[] encodedKey = curKey.getEncoded(); + + byte[] buffer = new byte[buf.size()]; + + for(int i = 0; i < buf.size(); i++) { + buffer[i] = buf.get(i); + } + + int respSize = hmacNative(OpenSSLCryptoNative.getLibInstance(), curAlgorithim, encodedKey, encodedKey.length, buffer, buffer.length, resp); + + if(respSize < resp.length) { + byte[] result = new byte[respSize]; + + for(int i = 0 ; i < respSize; i++) { + result[i] = resp[i]; + } + + return result; + } + + + return resp; + } + + @Override + protected void engineReset() { + buf.clear(); + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java index 27800d5..f18269a 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java @@ -4,166 +4,189 @@ import java.nio.ByteBuffer; import java.security.MessageDigestSpi; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Set; /** * An interface to OpenSSL message digest functions. + * * @author Stephan Fuhrmann */ class OpenSSLMessageDigestNative extends MessageDigestSpi { - /** Return the digest length in bytes. - * @return the digest length in bytes. - * */ - private static native int digestLength(ByteBuffer context); - - /** Removes a context allocated with {@linkplain #nativeContext()}. - * @param context the context to free. - * */ - private static native void removeContext(ByteBuffer context); - - /** Get the list of MessageDigest algorithms supported by OpenSSL. - * @return an array of supported message digest algorithms from the OpenSSL library. - * */ - private native static String[] listMessageDigests(); - - /** Returns the context size in bytes. This is used to allocate the {@link #context direct ByteBuffer}. - * @return a ByteBuffer containing the native message digest context. - * */ - private final native ByteBuffer nativeContext(); - - /** Initialize the context. - * @param context the context as allocated in {@link #context}. - * @param algorithmName the OpenSSL algorithm name as returned by {@linkplain #listMessageDigests()}. - * */ - private final native void nativeInit(ByteBuffer context, String algorithmName); - - /** Update the context with a single byte. - * @param context the context as allocated in {@link #context}. - * @param byteData the byte to update the context with. - * */ - private final native void nativeUpdateWithByte(ByteBuffer context, byte byteData); - - /** Update the context with an array. - * @param context the context as allocated in {@link #context}. - * @param byteArray the array to update the context with. - * @param offset the start offset of the array data to update the context with. - * @param length the number of bytes to update the context with. - * */ - private final native void nativeUpdateWithByteArray(ByteBuffer context, byte[] byteArray, int offset, int length); - - /** Update the context with a direct byte buffer. - * @param context the context as allocated in {@link #context}. - * @param data the byte buffer to update the context with. - * @param offset the start offset of the buffer data to update the context with. - * @param length the number of bytes to update the context with. - * */ - private final native void nativeUpdateWithByteBuffer(ByteBuffer context, ByteBuffer data, int offset, int length); - - /** Do the final digest calculation and return it. - * @param context the context as allocated in {@link #context}. - * @param digest the target array to write the digest data to. - * */ - private final native void nativeFinal(ByteBuffer context, byte[] digest); - - /** A native message digest context where the state of the current calculation is stored. - * Allocated with {@linkplain #nativeContext()}, freed by the - * {@linkplain PhantomReferenceCleanup} with {@linkplain #free(ByteBuffer)}. - * */ - private final ByteBuffer context; - - /** The OpenSSL algorithm name as returned by {@linkplain #listMessageDigests()}. */ - private final String algorithmName; - - /** The digest length as calculated by the engine. */ - private final int digestLength; - - OpenSSLMessageDigestNative(String openSslName) { - try { - NativeLoader.loadAll(); - algorithmName = Objects.requireNonNull(openSslName); - context = nativeContext(); - PhantomReferenceCleanup.enqueueForCleanup(this, OpenSSLMessageDigestNative::free, context); - engineReset(); - digestLength = digestLength(context); - } - catch (IOException e) { - throw new IllegalStateException(e); - } + /** + * Return the digest length in bytes. + * + * @return the digest length in bytes. + */ + private static native int digestLength(ByteBuffer context); + + /** + * Removes a context allocated with {@linkplain #nativeContext()}. + * + * @param context the context to free. + */ + private static native void removeContext(ByteBuffer context); + + /** + * Get the list of MessageDigest algorithms supported by OpenSSL. + * + * @return an array of supported message digest algorithms from the OpenSSL library. + */ + private static native String[] listMessageDigests(); + + /** + * Returns the context size in bytes. This is used to allocate the {@link #context direct + * ByteBuffer}. + * + * @return a ByteBuffer containing the native message digest context. + */ + private final native ByteBuffer nativeContext(); + + /** + * Initialize the context. + * + * @param context the context as allocated in {@link #context}. + * @param algorithmName the OpenSSL algorithm name as returned by {@linkplain + * #listMessageDigests()}. + */ + private final native void nativeInit(ByteBuffer context, String algorithmName); + + /** + * Update the context with a single byte. + * + * @param context the context as allocated in {@link #context}. + * @param byteData the byte to update the context with. + */ + private final native void nativeUpdateWithByte(ByteBuffer context, byte byteData); + + /** + * Update the context with an array. + * + * @param context the context as allocated in {@link #context}. + * @param byteArray the array to update the context with. + * @param offset the start offset of the array data to update the context with. + * @param length the number of bytes to update the context with. + */ + private final native void nativeUpdateWithByteArray( + ByteBuffer context, byte[] byteArray, int offset, int length); + + /** + * Update the context with a direct byte buffer. + * + * @param context the context as allocated in {@link #context}. + * @param data the byte buffer to update the context with. + * @param offset the start offset of the buffer data to update the context with. + * @param length the number of bytes to update the context with. + */ + private final native void nativeUpdateWithByteBuffer( + ByteBuffer context, ByteBuffer data, int offset, int length); + + /** + * Do the final digest calculation and return it. + * + * @param context the context as allocated in {@link #context}. + * @param digest the target array to write the digest data to. + */ + private final native void nativeFinal(ByteBuffer context, byte[] digest); + + /** + * A native message digest context where the state of the current calculation is stored. Allocated + * with {@linkplain #nativeContext()}, freed by the {@linkplain PhantomReferenceCleanup} with + * {@linkplain #free(ByteBuffer)}. + */ + private final ByteBuffer context; + + /** The OpenSSL algorithm name as returned by {@linkplain #listMessageDigests()}. */ + private final String algorithmName; + + /** The digest length as calculated by the engine. */ + private final int digestLength; + + OpenSSLMessageDigestNative(String openSslName) { + try { + NativeLoader.loadAll(); + algorithmName = Objects.requireNonNull(openSslName); + context = nativeContext(); + PhantomReferenceCleanup.enqueueForCleanup(this, OpenSSLMessageDigestNative::free, context); + engineReset(); + digestLength = digestLength(context); + } catch (IOException e) { + throw new IllegalStateException(e); } - - /** Free the native context that came from {@linkplain #nativeContext()}. - * @param context the context allocated with {@linkplain #nativeContext()}. - * */ - protected static void free(ByteBuffer context) { - Objects.requireNonNull(context); - if (! context.isDirect()) { - throw new IllegalStateException("Illegal buffer passed in"); - } - removeContext(context); - } - - @Override - protected final int engineGetDigestLength() { - return digestLength; + } + + /** + * Free the native context that came from {@linkplain #nativeContext()}. + * + * @param context the context allocated with {@linkplain #nativeContext()}. + */ + protected static void free(ByteBuffer context) { + Objects.requireNonNull(context); + if (!context.isDirect()) { + throw new IllegalStateException("Illegal buffer passed in"); } - - /** Get the list of digest algorithms supported by the OpenSSL library. - * @return a Set of supported message digest algorithms. - * */ - protected static Set getMessageDigestList() { - String[] messageDigestAlgorithms = listMessageDigests(); - Set result = new HashSet<>(Arrays.asList(messageDigestAlgorithms)); - return result; + removeContext(context); + } + + @Override + protected final int engineGetDigestLength() { + return digestLength; + } + + /** + * Get the list of digest algorithms supported by the OpenSSL library. + * + * @return a Set of supported message digest algorithms. + */ + protected static Set getMessageDigestList() { + return new HashSet<>(Arrays.asList(listMessageDigests())); + } + + @Override + protected final void engineUpdate(final ByteBuffer input) { + if (!input.hasRemaining()) { + return; } - - @Override - protected final void engineUpdate(final ByteBuffer input) { - if (!input.hasRemaining()) { - return; - } - int remaining = input.remaining(); - int offset = input.position(); - if (input.isDirect()) { - nativeUpdateWithByteBuffer(context, input, offset, remaining); - input.position(input.position() + remaining); - } else if (input.hasArray()){ - // buffer is heap based and has an array - byte[] array = input.array(); - nativeUpdateWithByteArray(context, array, offset, remaining); - input.position(offset + remaining); - } else { - // neither direct nor array (read-only?) - byte[] array = new byte[remaining]; - input.get(array); - nativeUpdateWithByteArray(context, array, 0, array.length); - } - } - - @Override - protected final void engineUpdate(final byte inputByte) { - nativeUpdateWithByte(context, inputByte); - } - - @Override - protected final void engineUpdate(final byte[] input, final int offset, final int len) { - nativeUpdateWithByteArray(context, input, offset, len); - } - - @Override - protected final byte[] engineDigest() { - byte[] result = new byte[digestLength]; - nativeFinal(context, result); - engineReset(); - return result; - } - - @Override - protected final void engineReset() { - nativeInit(context, algorithmName); + int remaining = input.remaining(); + int offset = input.position(); + if (input.isDirect()) { + nativeUpdateWithByteBuffer(context, input, offset, remaining); + input.position(input.position() + remaining); + } else if (input.hasArray()) { + // buffer is heap based and has an array + byte[] array = input.array(); + nativeUpdateWithByteArray(context, array, offset, remaining); + input.position(offset + remaining); + } else { + // neither direct nor array (read-only?) + byte[] array = new byte[remaining]; + input.get(array); + nativeUpdateWithByteArray(context, array, 0, array.length); } + } + + @Override + protected final void engineUpdate(final byte inputByte) { + nativeUpdateWithByte(context, inputByte); + } + + @Override + protected final void engineUpdate(final byte[] input, final int offset, final int len) { + nativeUpdateWithByteArray(context, input, offset, len); + } + + @Override + protected final byte[] engineDigest() { + byte[] result = new byte[digestLength]; + nativeFinal(context, result); + engineReset(); + return result; + } + + @Override + protected final void engineReset() { + nativeInit(context, algorithmName); + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLSecureRandomNative.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLSecureRandomNative.java new file mode 100644 index 0000000..57b4742 --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLSecureRandomNative.java @@ -0,0 +1,28 @@ +package de.sfuhrm.openssl4j; + +import java.security.SecureRandomSpi; + +public class OpenSSLSecureRandomNative extends SecureRandomSpi { + + private static native void engineSetSeedNative(byte[] seed, int seedLen, double randomness); + private static native int engineNextBytesNative(byte[] rand, int randLen); + + @Override + protected void engineSetSeed(byte[] seed) { + engineSetSeedNative(seed, seed.length, seed.length); + } + + @Override + protected void engineNextBytes(byte[] bytes) { + engineNextBytesNative(bytes, bytes.length); + } + + @Override + protected byte[] engineGenerateSeed(int numBytes) { + byte[] seed = new byte[numBytes]; + + engineNextBytesNative(seed, numBytes); + + return seed; + } +} diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/PhantomReferenceCleanup.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/PhantomReferenceCleanup.java index c287d44..6b5803e 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/PhantomReferenceCleanup.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/PhantomReferenceCleanup.java @@ -10,64 +10,70 @@ import java.util.function.Consumer; /** - * Frees native AbstractNative objects. - * The ByteBuffer objects are allocated in {@linkplain OpenSSLMessageDigestNative#OpenSSLMessageDigestNative(String)} ()} - * and are not used any longer. + * Frees native AbstractNative objects (ByteBuffers). The objects are allocated in the constructors + * for OpenSSLMessageDigestNative, OpenSSLCipherNative, OpenSSLMacNative, and + * OpenSSLSecureRandomNative and are not used once construction is complete. + * * @author Stephan Fuhrmann */ class PhantomReferenceCleanup { - /** The reference queue of unused AbstractNative objects. */ - private static final ReferenceQueue BYTE_BUFFER_REFERENCE_QUEUE = new ReferenceQueue<>(); + /** The reference queue of unused AbstractNative objects. */ + private static final ReferenceQueue BYTE_BUFFER_REFERENCE_QUEUE = new ReferenceQueue<>(); - /** Is the thread running? */ - private static boolean running = false; + /** Is the thread running? */ + private static boolean running = false; - private static final Set nativePhantomReferenceList = Collections.synchronizedSet(new HashSet<>()); + private static final Set nativePhantomReferenceList = + Collections.synchronizedSet(new HashSet<>()); - private static class NativePhantomReference extends PhantomReference { - private final Consumer freeFunction; - private final ByteBuffer byteBuffer; - NativePhantomReference(Object abstractNative, Consumer freeFunction, ByteBuffer context) { - super(abstractNative, BYTE_BUFFER_REFERENCE_QUEUE); - this.freeFunction = freeFunction; - this.byteBuffer = context; - } - public void free() { - freeFunction.accept(byteBuffer); - } + private static class NativePhantomReference extends PhantomReference { + private final Consumer freeFunction; + private final ByteBuffer byteBuffer; + + NativePhantomReference( + Object abstractNative, Consumer freeFunction, ByteBuffer context) { + super(abstractNative, BYTE_BUFFER_REFERENCE_QUEUE); + this.freeFunction = freeFunction; + this.byteBuffer = context; } - /** Enqueues a AbstractNative for later cleanup. */ - static void enqueueForCleanup(Object ref, Consumer freeFunction, ByteBuffer context) { - NativePhantomReference phantomReference = new NativePhantomReference( - Objects.requireNonNull(ref), - Objects.requireNonNull(freeFunction), - Objects.requireNonNull(context)); - nativePhantomReferenceList.add(phantomReference); - startIfNeeded(); + public void free() { + freeFunction.accept(byteBuffer); } + } + + /** Enqueues a AbstractNative for later cleanup. */ + static void enqueueForCleanup(Object ref, Consumer freeFunction, ByteBuffer context) { + NativePhantomReference phantomReference = + new NativePhantomReference( + Objects.requireNonNull(ref), + Objects.requireNonNull(freeFunction), + Objects.requireNonNull(context)); + nativePhantomReferenceList.add(phantomReference); + startIfNeeded(); + } - /** Checks whether the queue thread is already - * running and starts it if not. - * */ - static synchronized void startIfNeeded() { - if (!running) { - running = true; - Runnable r = () -> { - try { - while (true) { - NativePhantomReference reference = (NativePhantomReference)BYTE_BUFFER_REFERENCE_QUEUE.remove(); - reference.free(); - nativePhantomReferenceList.remove(reference); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - }; - Thread t = new Thread(r, "OpenSSL-Cleanup"); - t.setDaemon(true); - t.start(); - } + /** Checks whether the queue thread is already running and starts it if not. */ + static synchronized void startIfNeeded() { + if (!running) { + running = true; + Runnable r = + () -> { + try { + while (true) { + NativePhantomReference reference = + (NativePhantomReference) BYTE_BUFFER_REFERENCE_QUEUE.remove(); + reference.free(); + nativePhantomReferenceList.remove(reference); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }; + Thread t = new Thread(r, "OpenSSL-Cleanup"); + t.setDaemon(true); + t.start(); } + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/PropertyAccessor.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/PropertyAccessor.java index da4dafb..01a8998 100644 --- a/openssl4j/src/main/java/de/sfuhrm/openssl4j/PropertyAccessor.java +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/PropertyAccessor.java @@ -5,28 +5,30 @@ import java.util.Properties; class PropertyAccessor { - private static Properties properties; + private static Properties properties; - /** Gets a property. - * @param name the property name. - * @param defaultValue the default value if the property was not set. - * */ - static String get(String name, String defaultValue) { - if (properties == null) { - properties = loadOpenssl4jProperties(); - } - return (String)properties.getOrDefault(name, defaultValue); + /** + * Gets a property. + * + * @param name the property name. + * @param defaultValue the default value if the property was not set. + */ + static String get(String name, String defaultValue) { + if (properties == null) { + properties = loadOpenssl4jProperties(); } + return (String) properties.getOrDefault(name, defaultValue); + } - private static Properties loadOpenssl4jProperties() { - Properties result = new Properties(); - try (InputStream inputStream = ObjectTransfer.class.getResourceAsStream("/META-INF/openssl4j.properties")) { - if (inputStream != null) { - result.load(inputStream); - } - } - catch (IOException e) { - } - return result; + private static Properties loadOpenssl4jProperties() { + Properties result = new Properties(); + try (InputStream inputStream = + ObjectTransfer.class.getResourceAsStream("/META-INF/openssl4j.properties")) { + if (inputStream != null) { + result.load(inputStream); + } + } catch (IOException e) { } + return result; + } } diff --git a/openssl4j/src/main/java/de/sfuhrm/openssl4j/SecureRandom.java b/openssl4j/src/main/java/de/sfuhrm/openssl4j/SecureRandom.java new file mode 100644 index 0000000..c750dab --- /dev/null +++ b/openssl4j/src/main/java/de/sfuhrm/openssl4j/SecureRandom.java @@ -0,0 +1,13 @@ +package de.sfuhrm.openssl4j; + +public class SecureRandom { + private SecureRandom() { + + } + + public static final class DEFAULT extends OpenSSLSecureRandomNative { + public DEFAULT() { + super(); + } + } +} diff --git a/openssl4j/src/main/java/module-info.java b/openssl4j/src/main/java/module-info.java new file mode 100644 index 0000000..76d1252 --- /dev/null +++ b/openssl4j/src/main/java/module-info.java @@ -0,0 +1,10 @@ +import de.sfuhrm.openssl4j.OpenSSL4JProvider; + +/** + * Provides the OpenSSL4j cryptographic provider implementation + * for use with the native OpenSSL dynamic library. + */ +module de.sfuhrm.openssl4j { + provides java.security.Provider with OpenSSL4JProvider; + exports de.sfuhrm.openssl4j to java.base; +} diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/BaseTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/BaseTest.java index 5a3ddde..3d2b1f0 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/BaseTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/BaseTest.java @@ -1,25 +1,25 @@ package de.sfuhrm.openssl4j; -import org.junit.jupiter.api.BeforeEach; - import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeEach; /** * Test base. + * * @author Stephan Fuhrmann */ public class BaseTest { - Formatter formatter; - Charset ascii; + Formatter formatter; + Charset ascii; - @BeforeEach - public void before() throws IOException { - NativeLoader.loadAll(); + @BeforeEach + public void before() throws IOException { + NativeLoader.loadAll(); - formatter = Formatter.getInstance(); - ascii = StandardCharsets.US_ASCII; - } + formatter = Formatter.getInstance(); + ascii = StandardCharsets.US_ASCII; + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/Formatter.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/Formatter.java index 8ac47b2..9faf41b 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/Formatter.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/Formatter.java @@ -2,62 +2,58 @@ /** * Formats digests to hexadecimal Strings. + * * @author Stephan Fuhrmann */ public final class Formatter { - /** Only singleton. */ - private Formatter() { + /** Only singleton. */ + private Formatter() {} - } - - /** Lazily initialized singleton. */ - private static Formatter instance; + /** Lazily initialized singleton. */ + private static Formatter instance; - /** Get a formatter instance. - * @return the single shared instance. - * */ - public static Formatter getInstance() { - if (instance == null) { - instance = new Formatter(); - } - return instance; + /** + * Get a formatter instance. + * + * @return the single shared instance. + */ + public static Formatter getInstance() { + if (instance == null) { + instance = new Formatter(); } - - /** Formats the given digest bytes to a hexadecimal - * String. - * @param digest the digest bytes to format. - * @return the digest bytes formatted as hexadecimal - * String. Every byte is formatted as two characters. - * */ - public String format(final byte[] digest) { - StringBuilder stringBuilder = new StringBuilder(); - for (byte b : digest) { - stringBuilder - .append( - digitValue(0x0f & (b >> 4))) - .append( - digitValue(0x0f & b)); - } - return stringBuilder.toString(); + return instance; + } + + /** + * Formats the given digest bytes to a hexadecimal String. + * + * @param digest the digest bytes to format. + * @return the digest bytes formatted as hexadecimal String. Every byte is formatted as two + * characters. + */ + public String format(final byte[] digest) { + StringBuilder stringBuilder = new StringBuilder(); + for (byte b : digest) { + stringBuilder.append(digitValue(0x0f & (b >> 4))).append(digitValue(0x0f & b)); } - - - - /** Formats a nibble to a char. - * @param b a nibble value between 0 and 15 (inclusive). - * @return the character value. - * @throws IllegalArgumentException if the character - * was not inside the hex chars. - * */ - private static char digitValue(final int b) { - if (b >= 0 && b <= 9) { - return (char)(b + '0'); - } else if (b >= 0xa && b <= 0xf) { - return (char)(b - 10 + 'a'); - } else { - throw new IllegalArgumentException( - "Digit not formattable: " + b); - } + return stringBuilder.toString(); + } + + /** + * Formats a nibble to a char. + * + * @param b a nibble value between 0 and 15 (inclusive). + * @return the character value. + * @throws IllegalArgumentException if the character was not inside the hex chars. + */ + private static char digitValue(final int b) { + if (b >= 0 && b <= 9) { + return (char) (b + '0'); + } else if (b >= 0xa && b <= 0xf) { + return (char) (b - 10 + 'a'); + } else { + throw new IllegalArgumentException("Digit not formattable: " + b); } + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/GcTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/GcTest.java index 7de0574..c5b560b 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/GcTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/GcTest.java @@ -5,16 +5,17 @@ /** * Manual test for {@linkplain PhantomReferenceCleanup}. + * * @author Stephan Fuhrmann */ public class GcTest { - @Test - @Disabled - public void gc() { - for (int i=0; i< 10000000; i++) { - MessageDigest.MD5 md5Native = new MessageDigest.MD5(); - System.gc(); - } + @Test + @Disabled + public void gc() { + for (int i = 0; i < 10000000; i++) { + MessageDigest.MD5 md5Native = new MessageDigest.MD5(); + System.gc(); } + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithKnownHashesTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithKnownHashesTest.java index 8defd80..46ea0e6 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithKnownHashesTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithKnownHashesTest.java @@ -1,12 +1,8 @@ package de.sfuhrm.openssl4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -14,57 +10,71 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** - * Test cases that compare the message digest of the - * implementations with well known expected outputs. + * Test cases that compare the message digest of the implementations with well known expected + * outputs. + * * @author Stephan Fuhrmann */ public class MessageDigestWithKnownHashesTest extends BaseTest { - private static final String[] REFERENCES = new String[] { - "BLAKE2b512", "", "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", - "BLAKE2b512", "The quick brown fox jumps over the lazy dog", "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", - "BLAKE2b512", "The quick brown fox jumps over the lazy dof", "ab6b007747d8068c02e25a6008db8a77c218d94f3b40d2291a7dc8a62090a744c082ea27af01521a102e42f480a31e9844053f456b4b41e8aa78bbe5c12957bb", - "BLAKE2b512", "", "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + private static final String[] REFERENCES = + new String[] { + "BLAKE2b512", "", + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + "BLAKE2b512", "The quick brown fox jumps over the lazy dog", + "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", + "BLAKE2b512", "The quick brown fox jumps over the lazy dof", + "ab6b007747d8068c02e25a6008db8a77c218d94f3b40d2291a7dc8a62090a744c082ea27af01521a102e42f480a31e9844053f456b4b41e8aa78bbe5c12957bb", + "BLAKE2b512", "", + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", "BLAKE2s256", "", "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", "RIPEMD160", "", "9c1185a5c5e9fc54612808977ee8f548b2258d31", - "RIPEMD160", "The quick brown fox jumps over the lazy dog", "37f332f68db77bd9d7edd4969571ad671cf9dd3b", - "RIPEMD160", "The quick brown fox jumps over the lazy cog", "132072df690933835eb8b6ad0b77e7b6f14acad7", + "RIPEMD160", "The quick brown fox jumps over the lazy dog", + "37f332f68db77bd9d7edd4969571ad671cf9dd3b", + "RIPEMD160", "The quick brown fox jumps over the lazy cog", + "132072df690933835eb8b6ad0b77e7b6f14acad7", "MD4", "", "31d6cfe0d16ae931b73c59d7e0c089c0", - "MD4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4", - "Whirlpool", "", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3", - "Whirlpool", "The quick brown fox jumps over the lazy eog", "C27BA124205F72E6847F3E19834F925CC666D0974167AF915BB462420ED40CC50900D85A1F923219D832357750492D5C143011A76988344C2635E69D06F2D38C" - }; + "MD4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "043f8582f241db351ce627e153e7f0e4", + "Whirlpool", "", + "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3", + "Whirlpool", "The quick brown fox jumps over the lazy eog", + "C27BA124205F72E6847F3E19834F925CC666D0974167AF915BB462420ED40CC50900D85A1F923219D832357750492D5C143011A76988344C2635E69D06F2D38C" + }; - private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { - List result = new ArrayList<>(); - Provider openSsl = new OpenSSL4JProvider(); + private static Stream provideTestArguments() + throws NoSuchAlgorithmException, IOException { + List result = new ArrayList<>(); + Provider openSsl = OpenSSL4JProvider.getInstance(); - for (int i=0; i < REFERENCES.length; i+= 3) { - String algorithm = REFERENCES[i]; - String clearText = REFERENCES[i + 1]; - String expected = REFERENCES[i + 2]; + for (int i = 0; i < REFERENCES.length; i += 3) { + String algorithm = REFERENCES[i]; + String clearText = REFERENCES[i + 1]; + String expected = REFERENCES[i + 2]; - result.add(Arguments.of( - algorithm, - MessageDigest.getInstance(algorithm, openSsl), - clearText.getBytes(StandardCharsets.US_ASCII), - expected - )); - } - - return result.stream(); + result.add( + Arguments.of( + algorithm, + MessageDigest.getInstance(algorithm, openSsl), + clearText.getBytes(StandardCharsets.US_ASCII), + expected)); } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void compareWithWellKnownHash(String digestName, MessageDigest testMD, byte[] clearText, String expectedDigest) { - testMD.update(clearText); - byte[] actual = testMD.digest(); + return result.stream(); + } - assertEquals(expectedDigest.toLowerCase(), formatter.format(actual)); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void compareWithWellKnownHash( + String digestName, MessageDigest testMD, byte[] clearText, String expectedDigest) { + testMD.update(clearText); + byte[] actual = testMD.digest(); + + assertEquals(expectedDigest.toLowerCase(), formatter.format(actual)); + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithReferenceMDTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithReferenceMDTest.java index c10dc65..ffee7f1 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithReferenceMDTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithReferenceMDTest.java @@ -1,14 +1,9 @@ package de.sfuhrm.openssl4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Provider; @@ -17,310 +12,389 @@ import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.*; /** - * Test cases that compare the message digest of the - * Sun provider (aka 'reference') with the implementations - * in this context (aka 'test'). + * Test cases that compare the message digest of the Sun provider (aka 'reference') with the + * implementations in this context (aka 'test'). + * * @author Stephan Fuhrmann */ -public class MessageDigestWithReferenceMDTest extends BaseTest { +public class MessageDigestWithReferenceMDTest extends BaseTest { - private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { - List messageDigestNames = Arrays.asList("MD5", "SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"); - List result = new ArrayList<>(); - Provider openSsl = new OpenSSL4JProvider(); - Provider sun = MessageDigest.getInstance("MD5").getProvider(); + private static Stream provideTestArguments() + throws NoSuchAlgorithmException, IOException { + List messageDigestNames = + Arrays.asList( + "MD5", + "SHA1", + "SHA-224", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA-512/224", + "SHA-512/256", + "SHA3-224", + "SHA3-256", + "SHA3-384", + "SHA3-512"); + List result = new ArrayList<>(); + Provider openSsl = OpenSSL4JProvider.getInstance(); + Provider sun = MessageDigest.getInstance("MD5").getProvider(); - for (String messageDigestName : messageDigestNames) { - result.add(Arguments.of( - messageDigestName, - MessageDigest.getInstance(messageDigestName, openSsl), - MessageDigest.getInstance(messageDigestName, sun))); - } - - return result.stream(); + for (String messageDigestName : messageDigestNames) { + result.add( + Arguments.of( + messageDigestName, + MessageDigest.getInstance(messageDigestName, openSsl), + MessageDigest.getInstance(messageDigestName, sun))); } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void compareGetters(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - assertEquals(referenceMD.getAlgorithm(), testMD.getAlgorithm()); - assertEquals(referenceMD.getDigestLength(), testMD.getDigestLength()); - } + return result.stream(); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void digestWithNoData(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - byte[] actualDigest = testMD.digest(); - byte[] expectedDigest = referenceMD.digest(); + @ParameterizedTest + @MethodSource("provideTestArguments") + public void compareGetters(String digestName, MessageDigest testMD, MessageDigest referenceMD) { + assertEquals(referenceMD.getAlgorithm(), testMD.getAlgorithm()); + assertEquals(referenceMD.getDigestLength(), testMD.getDigestLength()); + } - assertEquals(expectedDigest.length, actualDigest.length); - assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void digestWithNoData(String digestName, MessageDigest testMD, MessageDigest referenceMD) { + byte[] actualDigest = testMD.digest(); + byte[] expectedDigest = referenceMD.digest(); - private byte[] franzJagt() { - byte[] data = "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern".getBytes(ascii); - return data; - } + assertEquals(expectedDigest.length, actualDigest.length); + assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); + } - private void applyTo(Consumer consumer, MessageDigest testMD, MessageDigest referenceMD) { - consumer.accept(testMD); - consumer.accept(referenceMD); - byte[] actualDigest = testMD.digest(); - byte[] expectedDigest = referenceMD.digest(); - assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); - } + private byte[] franzJagt() { + byte[] data = "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern".getBytes(ascii); + return data; + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithFullArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - applyTo(md -> md.update(franzJagt()), testMD, referenceMD); - } + private void applyTo( + Consumer consumer, MessageDigest testMD, MessageDigest referenceMD) { + consumer.accept(testMD); + consumer.accept(referenceMD); + byte[] actualDigest = testMD.digest(); + byte[] expectedDigest = referenceMD.digest(); + assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithSingleBytes(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - applyTo(md -> { - for (byte val : franzJagt()) { - md.update(val); - } - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithFullArray( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + applyTo(md -> md.update(franzJagt()), testMD, referenceMD); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.wrap(franzJagt()); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithSingleBytes( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + applyTo( + md -> { + for (byte val : franzJagt()) { + md.update(val); + } + }, + testMD, + referenceMD); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithReadOnlyHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.wrap(franzJagt()); - bb = bb.asReadOnlyBuffer(); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithHeapByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.wrap(franzJagt()); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithReadOnlyMiddlePositionHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.wrap(franzJagt()); - bb = bb.asReadOnlyBuffer(); - bb.position(bb.remaining() / 2); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithReadOnlyHeapByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.wrap(franzJagt()); + bb = bb.asReadOnlyBuffer(); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithDirectByteBufferNoRemaining(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); - bb.put(franzJagt()); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithReadOnlyMiddlePositionHeapByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.wrap(franzJagt()); + bb = bb.asReadOnlyBuffer(); + bb.position(bb.remaining() / 2); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); - bb.put(franzJagt()); - bb.flip(); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithDirectByteBufferNoRemaining( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); + bb.put(franzJagt()); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithNonFullDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length * 2); - bb.put(franzJagt()); - bb.flip(); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithDirectByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); + bb.put(franzJagt()); + bb.flip(); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithMiddlePositionDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - final List list = new ArrayList<>(); - applyTo(md -> { - ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); - bb.put(franzJagt()); - bb.flip(); - bb.position(bb.remaining() / 2); - list.add(bb); - md.update(bb); - }, testMD, referenceMD); - ByteBuffer first = list.get(0); - ByteBuffer second = list.get(1); - assertEquals(first.position(), second.position()); - assertEquals(first.limit(), second.limit()); - assertEquals(first.capacity(), second.capacity()); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithNonFullDirectByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length * 2); + bb.put(franzJagt()); + bb.flip(); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithFragmentedArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - applyTo(md -> { - byte[] dataInner = franzJagt(); - byte[] data = new byte[dataInner.length * 2]; - int insertOffset = data.length / 4; - System.arraycopy(dataInner, 0, data, insertOffset, dataInner.length); - md.update(data, insertOffset, dataInner.length); - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithMiddlePositionDirectByteBuffer( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + final List list = new ArrayList<>(); + applyTo( + md -> { + ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); + bb.put(franzJagt()); + bb.flip(); + bb.position(bb.remaining() / 2); + list.add(bb); + md.update(bb); + }, + testMD, + referenceMD); + ByteBuffer first = list.get(0); + ByteBuffer second = list.get(1); + assertEquals(first.position(), second.position()); + assertEquals(first.limit(), second.limit()); + assertEquals(first.capacity(), second.capacity()); + } - static byte[] filledArray(int size) { - byte[] data = new byte[size]; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) i; - } - return data; - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithFragmentedArray( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + applyTo( + md -> { + byte[] dataInner = franzJagt(); + byte[] data = new byte[dataInner.length * 2]; + int insertOffset = data.length / 4; + System.arraycopy(dataInner, 0, data, insertOffset, dataInner.length); + md.update(data, insertOffset, dataInner.length); + }, + testMD, + referenceMD); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithLongArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - applyTo(md -> { - byte[] data = filledArray(1024 * 1024); - int rounds = 16; - for (int i=0; i < rounds; i++) { - md.update(data, 0, data.length); - } - }, testMD, referenceMD); + static byte[] filledArray(int size) { + byte[] data = new byte[size]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) i; } + return data; + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithLongDirectBB(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - applyTo(md -> { - byte[] data = filledArray(1024 * 1024); - int rounds = 16; - ByteBuffer direct = ByteBuffer.allocateDirect(data.length); - direct.put(data); - direct.flip(); - for (int i=0; i < rounds; i++) { - md.update(direct); - } - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithLongArray( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + applyTo( + md -> { + byte[] data = filledArray(1024 * 1024); + int rounds = 16; + for (int i = 0; i < rounds; i++) { + md.update(data, 0, data.length); + } + }, + testMD, + referenceMD); + } + + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithLongDirectBB( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + applyTo( + md -> { + byte[] data = filledArray(1024 * 1024); + int rounds = 16; + ByteBuffer direct = ByteBuffer.allocateDirect(data.length); + direct.put(data); + direct.flip(); + for (int i = 0; i < rounds; i++) { + md.update(direct); + } + }, + testMD, + referenceMD); + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithDirectBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - int size = 10240; - for (int i = 0; i < size; i++) { - final int position = i; - applyTo(md -> { - byte[] array = filledArray(size); - ByteBuffer direct = ByteBuffer.allocateDirect(array.length); - direct.put(array); - direct.flip(); - direct.position(position); - md.update(direct); - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithDirectBBWalkingPosition( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + int size = 10240; + for (int i = 0; i < size; i++) { + final int position = i; + applyTo( + md -> { + byte[] array = filledArray(size); + ByteBuffer direct = ByteBuffer.allocateDirect(array.length); + direct.put(array); + direct.flip(); + direct.position(position); + md.update(direct); + }, + testMD, + referenceMD); } + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithHeapBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - int size = 10240; - for (int i = 0; i < size; i++) { - final int position = i; - applyTo(md -> { - byte[] array = filledArray(size); - ByteBuffer direct = ByteBuffer.allocate(array.length); - direct.put(array); - direct.flip(); - direct.position(position); - md.update(direct); - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithHeapBBWalkingPosition( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + int size = 10240; + for (int i = 0; i < size; i++) { + final int position = i; + applyTo( + md -> { + byte[] array = filledArray(size); + ByteBuffer direct = ByteBuffer.allocate(array.length); + direct.put(array); + direct.flip(); + direct.position(position); + md.update(direct); + }, + testMD, + referenceMD); } + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithReadOnlyBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - int size = 10240; - for (int i = 0; i < size; i++) { - final int position = i; - applyTo(md -> { - byte[] array = filledArray(size); - ByteBuffer direct = ByteBuffer.allocate(array.length); - direct.put(array); - direct.flip(); - direct.position(position); - direct = direct.asReadOnlyBuffer(); - md.update(direct); - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithReadOnlyBBWalkingPosition( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + int size = 10240; + for (int i = 0; i < size; i++) { + final int position = i; + applyTo( + md -> { + byte[] array = filledArray(size); + ByteBuffer direct = ByteBuffer.allocate(array.length); + direct.put(array); + direct.flip(); + direct.position(position); + direct = direct.asReadOnlyBuffer(); + md.update(direct); + }, + testMD, + referenceMD); } + } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithArrayWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { - int size = 10240; - for (int i = 0; i < size; i++) { - final int position = i; - applyTo(md -> { - byte[] array = filledArray(size); - md.update(array, position, size - position); - }, testMD, referenceMD); - } + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithArrayWalkingPosition( + String digestName, MessageDigest testMD, MessageDigest referenceMD) { + int size = 10240; + for (int i = 0; i < size; i++) { + final int position = i; + applyTo( + md -> { + byte[] array = filledArray(size); + md.update(array, position, size - position); + }, + testMD, + referenceMD); } + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLCipherNativeTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLCipherNativeTest.java new file mode 100644 index 0000000..c54d4d3 --- /dev/null +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLCipherNativeTest.java @@ -0,0 +1,52 @@ +package de.sfuhrm.openssl4j; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Set; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test for {@linkplain OpenSSLCipherNative}. + * + * @author Stephan Fuhrmann + */ +public class OpenSSLCipherNativeTest { + + @BeforeEach + public void init() throws IOException { + NativeLoader.loadAll(); + } + + @Test + public void getCipherList() { + Set sslAlgos = OpenSSLCipherNative.getCipherList(); + Assertions.assertNotNull(sslAlgos); + Assertions.assertNotEquals(0, sslAlgos.size()); + Assertions.assertTrue(sslAlgos.contains("AES-256-OCB")); + } + + @Test + public void newInstance() throws IOException { + String LINE_SEPARATOR = System.getProperty("line.separator"); + try { + OpenSSLCipherNative openSSLCipherNative = new OpenSSLCipherNative("AES-256-OCB"); + Assertions.assertNotNull(openSSLCipherNative); + } catch (Exception e) { + StringBuilder builder = new StringBuilder(); + builder.append("ERROR CREATING OPEN SSL CIPHER NATIVE: "); + builder.append(e.getMessage()); + for (StackTraceElement stackTraceElement : e.getStackTrace()) { + builder.append(LINE_SEPARATOR); + builder.append(stackTraceElement.toString()); + } + BufferedWriter writer = + new BufferedWriter(new FileWriter("OpenSSLCipherNative_ERROR.txt", true)); + writer.append(builder.toString()); + writer.close(); + throw new OpenSSL4JException(builder.toString()); + } + } +} diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNativeTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNativeTest.java index 7e2e172..05ae7d0 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNativeTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNativeTest.java @@ -1,36 +1,34 @@ package de.sfuhrm.openssl4j; +import java.io.IOException; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.util.Set; - /** * Test for {@linkplain OpenSSLMessageDigestNative}. + * * @author Stephan Fuhrmann */ public class OpenSSLMessageDigestNativeTest { - @BeforeEach - public void init() throws IOException { - NativeLoader.loadAll(); - } + @BeforeEach + public void init() throws IOException { + NativeLoader.loadAll(); + } - @Test - public void getMessageDigestList() { - Set sslAlgos = OpenSSLMessageDigestNative.getMessageDigestList(); - Assertions.assertNotNull(sslAlgos); - Assertions.assertNotEquals(0, sslAlgos.size()); - Assertions.assertTrue(sslAlgos.contains("MD5")); - } + @Test + public void getMessageDigestList() { + Set sslAlgos = OpenSSLMessageDigestNative.getMessageDigestList(); + Assertions.assertNotNull(sslAlgos); + Assertions.assertNotEquals(0, sslAlgos.size()); + Assertions.assertTrue(sslAlgos.contains("MD5")); + } - @Test - public void freeWithNull() { - Assertions.assertThrows(NullPointerException.class, () -> - OpenSSLMessageDigestNative.free(null) - ); - } + @Test + public void freeWithNull() { + Assertions.assertThrows( + NullPointerException.class, () -> OpenSSLMessageDigestNative.free(null)); + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/SpeedTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/SpeedTest.java index 37b120f..cbaaacd 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/SpeedTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/SpeedTest.java @@ -1,11 +1,5 @@ package de.sfuhrm.openssl4j; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; @@ -20,108 +14,146 @@ import java.util.Locale; import java.util.Map; import java.util.stream.Stream; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Benchmark testing the speed of multiple algorithm implementations. + * * @author Stephan Fuhrmann */ @Disabled public class SpeedTest { - static final int TIMES = 100; - - private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { - List messageDigestNames = Arrays.asList("MD5", "SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"); - List bufferSizes = Arrays.asList(1000, 100000, 1000000); - List result = new ArrayList<>(); - Map providerMap = new HashMap<>(); - providerMap.put("OpenSSL", new OpenSSL4JProvider()); - providerMap.put("Sun", Security.getProvider("SUN")); - providerMap.put("BC", new BouncyCastleProvider()); - - for (Map.Entry providerEntry : providerMap.entrySet()) { - for (String messageDigestName : messageDigestNames) { - for (Integer bufferSize : bufferSizes) { - String name = providerEntry.getKey(); - result.add(Arguments.of( - name, - messageDigestName, - MessageDigest.getInstance(messageDigestName, providerEntry.getValue()), - messageDigestName, - bufferSize)); - } - } + static final int TIMES = 100; + + private static Stream provideTestArguments() + throws NoSuchAlgorithmException, IOException { + List messageDigestNames = + Arrays.asList( + "MD5", + "SHA1", + "SHA-224", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA-512/224", + "SHA-512/256", + "SHA3-224", + "SHA3-256", + "SHA3-384", + "SHA3-512"); + List bufferSizes = Arrays.asList(1000, 100000, 1000000); + List result = new ArrayList<>(); + Map providerMap = new HashMap<>(); + providerMap.put("OpenSSL", OpenSSL4JProvider.getInstance()); + providerMap.put("Sun", Security.getProvider("SUN")); + + for (Map.Entry providerEntry : providerMap.entrySet()) { + for (String messageDigestName : messageDigestNames) { + for (Integer bufferSize : bufferSizes) { + String name = providerEntry.getKey() + "-" + messageDigestName; + result.add( + Arguments.of( + name, + MessageDigest.getInstance(messageDigestName, providerEntry.getValue()), + messageDigestName, + bufferSize)); } - - return result.stream(); + } } - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithByte(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { - benchmark(provider, messageDigest, "SingleByte", TIMES, bufferSize, () -> { - for (int i = 0; i < bufferSize; i++) { - md.update((byte) 0); - } + return result.stream(); + } + + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithByte( + String benchmarkName, MessageDigest md, String messageDigestName, Integer bufferSize) { + benchmark( + benchmarkName, + "SingleByte", + TIMES, + bufferSize, + () -> { + for (int i = 0; i < bufferSize; i++) { + md.update((byte) 0); + } }); - } - - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithArray(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { - byte[] data = new byte[bufferSize]; - benchmark(provider, messageDigest, "ByteArray", TIMES, bufferSize, () -> md.update(data)); - } - - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithHeapBB(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { - ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize); - byteBuffer.limit(byteBuffer.capacity()); - benchmark(provider, messageDigest, "HeapBB", TIMES, bufferSize, () -> { - md.update(byteBuffer); - byteBuffer.flip(); + } + + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithArray( + String benchmarkName, MessageDigest md, String messageDigestName, Integer bufferSize) { + byte[] data = new byte[bufferSize]; + benchmark(benchmarkName, "ByteArray", TIMES, bufferSize, () -> md.update(data)); + } + + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithHeapBB( + String benchmarkName, MessageDigest md, String messageDigestName, Integer bufferSize) { + ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize); + byteBuffer.limit(byteBuffer.capacity()); + benchmark( + benchmarkName, + "HeapBB", + TIMES, + bufferSize, + () -> { + md.update(byteBuffer); + byteBuffer.flip(); }); - } - - @ParameterizedTest - @MethodSource("provideTestArguments") - public void updateWithDirectBB(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { - ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize); - byteBuffer.limit(byteBuffer.capacity()); - benchmark(provider, messageDigest, "DirectBB", TIMES, bufferSize, () -> { - md.update(byteBuffer); - byteBuffer.flip(); + } + + @ParameterizedTest + @MethodSource("provideTestArguments") + public void updateWithDirectBB( + String benchmarkName, MessageDigest md, String messageDigestName, Integer bufferSize) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize); + byteBuffer.limit(byteBuffer.capacity()); + benchmark( + benchmarkName, + "DirectBB", + TIMES, + bufferSize, + () -> { + md.update(byteBuffer); + byteBuffer.flip(); }); - } + } - static boolean first = true; - static void benchmark(String provider, String messageDigest, String testName, int times, int length, Runnable r) { - long start = System.currentTimeMillis(); - for (int i = 0; i < times; i++) { - r.run(); - } - long end = System.currentTimeMillis(); - long millis = end - start; + static boolean first = true; - double totalData = times * length; - double seconds = millis / 1000.; + static void benchmark(String benchmarkName, String testName, int times, int length, Runnable r) { + long start = System.currentTimeMillis(); + for (int i = 0; i < times; i++) { + r.run(); + } + long end = System.currentTimeMillis(); + long millis = end - start; - Formatter formatter = new Formatter(System.out, Locale.ENGLISH); + double totalData = times * length; + double seconds = millis / 1000.; - if (first) { - formatter.format("Provider;MD;Test;Times;Length;Seconds;Data;SpeedMBPS%n"); - first = false; - } + Formatter formatter = new Formatter(System.out, Locale.ENGLISH); - formatter.format("%s;%s;%s;%d;%d;%g;%g;%g%n", - provider, - messageDigest, - testName, - times, - length, - seconds, - totalData, - (totalData / (1024. * 1024.)) / seconds); + if (first) { + formatter.format("Bench;Test;Times;Length;Seconds;Data;SpeedMBPS%n"); + first = false; } + + formatter.format( + "%s;%s;%d;%d;%g;%g;%g%n", + benchmarkName, + testName, + times, + length, + seconds, + totalData, + (totalData / (1024. * 1024.)) / seconds); + } } diff --git a/openssl4j/src/test/java/de/sfuhrm/openssl4j/SunProviderListTest.java b/openssl4j/src/test/java/de/sfuhrm/openssl4j/SunProviderListTest.java index 98e68d5..7eb5235 100644 --- a/openssl4j/src/test/java/de/sfuhrm/openssl4j/SunProviderListTest.java +++ b/openssl4j/src/test/java/de/sfuhrm/openssl4j/SunProviderListTest.java @@ -1,30 +1,32 @@ package de.sfuhrm.openssl4j; -import org.junit.jupiter.api.Test; - import java.security.Provider; import java.security.Security; import java.util.Arrays; import java.util.Comparator; import java.util.TreeSet; +import org.junit.jupiter.api.Test; /** * Lists the registered Security providers. + * * @author Stephan Fuhrmann */ public class SunProviderListTest { - @Test - public void list() { - Provider[] providers = Security.getProviders(); - Arrays.sort(providers, Comparator.comparing(Provider::getName)); - for (Provider provider : providers) { - System.out.println(provider.getName()); - TreeSet sortedServices = new TreeSet<>(Comparator.comparing(o -> (o.getType() + o.getAlgorithm()))); - sortedServices.addAll(provider.getServices()); - for (Provider.Service service : sortedServices) { - System.out.println(service.getType()+" - "+service.getClassName()+" - " + service.getAlgorithm()); - } - } + @Test + public void list() { + Provider[] providers = Security.getProviders(); + Arrays.sort(providers, Comparator.comparing(Provider::getName)); + for (Provider provider : providers) { + System.out.println(provider.getName()); + TreeSet sortedServices = + new TreeSet<>(Comparator.comparing(o -> (o.getType() + o.getAlgorithm()))); + sortedServices.addAll(provider.getServices()); + for (Provider.Service service : sortedServices) { + System.out.println( + service.getType() + " - " + service.getClassName() + " - " + service.getAlgorithm()); + } } + } } diff --git a/pom.xml b/pom.xml index 7d9cef4..1a32cd4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,10 @@ - + 4.0.0 de.sfuhrm openssl4j-parent - 0.5.1-SNAPSHOT + 0.3.1-SNAPSHOT OpenSSL4J Parent OpenSSL4J Parent POM https://github.com/sfuhrm/openssl4j @@ -14,8 +15,8 @@ - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt + GNU Lesser General Public License, Version 3.0 + https://www.gnu.org/licenses/lgpl-3.0.en.html repo @@ -42,8 +43,8 @@ pom UTF-8 - 8 - 8 + 9 + 9 @@ -79,7 +80,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.1.0 + 1.6 0AC5A45E91FA93DA25380017B0D87B063EAD41F1 @@ -102,7 +103,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.3.0 + 3.2.0 @@ -119,7 +120,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.0 + 3.2.1 attach-sources @@ -132,7 +133,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.2.0 attach-javadocs @@ -152,14 +153,14 @@ org.junit.jupiter junit-jupiter-engine - 5.10.1 + 5.6.2 org.apache.maven.plugins maven-release-plugin - 3.0.1 + 3.0.0-M1 true release @@ -171,19 +172,19 @@ org.junit.jupiter junit-jupiter-api - 5.10.1 + 5.6.2 test org.junit.jupiter junit-jupiter-engine - 5.10.1 + 5.6.2 test org.junit.jupiter junit-jupiter-params - 5.10.1 + 5.6.2 test