diff --git a/pom.xml b/pom.xml index 184d92d..4af5470 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.gmssl GmSSLJNI - 3.1.1 + 3.1.2 GmSSL-Java jar GmSSL Java SDK diff --git a/src/main/c/CMakeLists.txt b/src/main/c/CMakeLists.txt index a16c4b3..2b0ab46 100644 --- a/src/main/c/CMakeLists.txt +++ b/src/main/c/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.11) + project(gmssljni) find_program(GMSSL_EXECUTABLE NAMES gmssl) @@ -21,6 +22,7 @@ if(WIN32) set_target_properties(gmssljni-native PROPERTIES OUTPUT_NAME lib$ENV{libName}) elseif(APPLE) message(STATUS "->Now is Apple systems.") + message(STATUS "CMAKE_OSX_ARCHITECTURES is set to: ${CMAKE_OSX_ARCHITECTURES}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$ENV{libSubFolder}) add_library(gmssljni-native SHARED gmssljni.c) target_link_libraries(gmssljni-native -L"${GMSSL_PARENT_DIR}/lib") diff --git a/src/main/c/gmssljni.c b/src/main/c/gmssljni.c index fe1cc72..c8fa7e4 100644 --- a/src/main/c/gmssljni.c +++ b/src/main/c/gmssljni.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -3880,3 +3881,371 @@ JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_cert_1verify_1by_1ca_1cert( return ret; } +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_new + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1new( + JNIEnv *env, jclass this, + jstring country, jstring state, jstring locality, + jstring org, jstring org_unit, jstring common_name, + jlong sm2_key, jstring signer_id) +{ + jbyteArray ret = NULL; + const char *country_str = NULL; + const char *state_str = NULL; + const char *locality_str = NULL; + const char *org_str = NULL; + const char *org_unit_str = NULL; + const char *common_name_str = NULL; + const char *signer_id_str = NULL; + size_t signer_id_len = 0; + + uint8_t name[256]; + size_t namelen = 0; + uint8_t attrs[512]; + size_t attrs_len = 0; + uint8_t req[1024]; + uint8_t *p = req; + size_t reqlen = 0; + + if (!sm2_key) { + error_print(); + return NULL; + } + if (!common_name) { + error_print(); + return NULL; + } + + // Get string values (some can be NULL) + if (country) { + if (!(country_str = (*env)->GetStringUTFChars(env, country, NULL))) { + error_print(); + goto end; + } + } + if (state) { + if (!(state_str = (*env)->GetStringUTFChars(env, state, NULL))) { + error_print(); + goto end; + } + } + if (locality) { + if (!(locality_str = (*env)->GetStringUTFChars(env, locality, NULL))) { + error_print(); + goto end; + } + } + if (org) { + if (!(org_str = (*env)->GetStringUTFChars(env, org, NULL))) { + error_print(); + goto end; + } + } + if (org_unit) { + if (!(org_unit_str = (*env)->GetStringUTFChars(env, org_unit, NULL))) { + error_print(); + goto end; + } + } + if (!(common_name_str = (*env)->GetStringUTFChars(env, common_name, NULL))) { + error_print(); + goto end; + } + + // Get signer_id, use default if not provided + if (signer_id) { + if (!(signer_id_str = (*env)->GetStringUTFChars(env, signer_id, NULL))) { + error_print(); + goto end; + } + signer_id_len = strlen(signer_id_str); + } else { + signer_id_str = SM2_DEFAULT_ID; + signer_id_len = strlen(SM2_DEFAULT_ID); + } + + // Build subject name + if (x509_name_set(name, &namelen, sizeof(name), + country_str, state_str, locality_str, + org_str, org_unit_str, common_name_str) != 1) { + error_print(); + goto end; + } + + // Generate and sign the certificate request + if (x509_req_sign_to_der( + X509_version_v1, + name, namelen, + (SM2_KEY *)sm2_key, + attrs, attrs_len, + OID_sm2sign_with_sm3, + (SM2_KEY *)sm2_key, signer_id_str, signer_id_len, + &p, &reqlen) != 1) { + error_print(); + goto end; + } + + // Create return byte array + if (!(ret = (*env)->NewByteArray(env, reqlen))) { + error_print(); + goto end; + } + (*env)->SetByteArrayRegion(env, ret, 0, reqlen, (jbyte *)req); + +end: + if (country_str && country) (*env)->ReleaseStringUTFChars(env, country, country_str); + if (state_str && state) (*env)->ReleaseStringUTFChars(env, state, state_str); + if (locality_str && locality) (*env)->ReleaseStringUTFChars(env, locality, locality_str); + if (org_str && org) (*env)->ReleaseStringUTFChars(env, org, org_str); + if (org_unit_str && org_unit) (*env)->ReleaseStringUTFChars(env, org_unit, org_unit_str); + if (common_name_str) (*env)->ReleaseStringUTFChars(env, common_name, common_name_str); + if (signer_id_str && signer_id) (*env)->ReleaseStringUTFChars(env, signer_id, signer_id_str); + return ret; +} + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_to_pem + * Signature: ([BLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1to_1pem( + JNIEnv *env, jclass this, + jbyteArray req, jstring file) +{ + jint ret = -1; + jbyte *reqbuf = NULL; + jsize reqlen; + const char *file_str = NULL; + FILE *fp = NULL; + + if (!req || !file) { + error_print(); + return -1; + } + + if (!(reqbuf = (*env)->GetByteArrayElements(env, req, NULL))) { + error_print(); + return -1; + } + reqlen = (*env)->GetArrayLength(env, req); + + if (!(file_str = (*env)->GetStringUTFChars(env, file, NULL))) { + error_print(); + goto end; + } + if (!(fp = fopen(file_str, "wb"))) { + error_print(); + goto end; + } + if (x509_req_to_pem((uint8_t *)reqbuf, (size_t)reqlen, fp) != 1) { + error_print(); + goto end; + } + ret = 1; +end: + (*env)->ReleaseByteArrayElements(env, req, reqbuf, JNI_ABORT); + if (file_str) (*env)->ReleaseStringUTFChars(env, file, file_str); + if (fp) fclose(fp); + return ret; +} + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_from_pem + * Signature: (Ljava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1from_1pem( + JNIEnv *env, jclass this, + jstring file) +{ + jbyteArray ret = NULL; + const char *file_str = NULL; + FILE *fp = NULL; + uint8_t req[1024]; + size_t reqlen; + + if (!file) { + error_print(); + return NULL; + } + + if (!(file_str = (*env)->GetStringUTFChars(env, file, NULL))) { + error_print(); + return NULL; + } + if (!(fp = fopen(file_str, "rb"))) { + error_print(); + goto end; + } + if (x509_req_from_pem(req, &reqlen, sizeof(req), fp) != 1) { + error_print(); + goto end; + } + + if (!(ret = (*env)->NewByteArray(env, reqlen))) { + error_print(); + goto end; + } + (*env)->SetByteArrayRegion(env, ret, 0, reqlen, (jbyte *)req); + +end: + if (file_str) (*env)->ReleaseStringUTFChars(env, file, file_str); + if (fp) fclose(fp); + return ret; +} + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_get_subject + * Signature: ([B)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1get_1subject( + JNIEnv *env, jclass this, + jbyteArray req) +{ + jobjectArray ret = NULL; + jobjectArray arr = NULL; + jbyte *reqbuf = NULL; + jsize reqlen; + const uint8_t *subject; + size_t subject_len; + int cnt; + + if (!req) { + error_print(); + return NULL; + } + + if (!(reqbuf = (*env)->GetByteArrayElements(env, req, NULL))) { + error_print(); + return NULL; + } + reqlen = (*env)->GetArrayLength(env, req); + + if (x509_req_get_details((uint8_t *)reqbuf, reqlen, + NULL, &subject, &subject_len, NULL, NULL, NULL, NULL, NULL, NULL) != 1) { + error_print(); + goto end; + } + + if (gmssl_name_cnt(subject, subject_len, &cnt) != 1) { + error_print(); + goto end; + } + if (!(arr = (*env)->NewObjectArray(env, cnt, (*env)->FindClass(env, "java/lang/String"), 0))) { + error_print(); + goto end; + } + if (gmssl_parse_name(env, arr, subject, subject_len) != 1) { + error_print(); + } + ret = arr; + arr = NULL; + +end: + if (reqbuf) (*env)->ReleaseByteArrayElements(env, req, reqbuf, JNI_ABORT); + return ret; +} + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_get_subject_public_key + * Signature: ([B)J + */ +JNIEXPORT jlong JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1get_1subject_1public_1key( + JNIEnv *env, jclass this, + jbyteArray req) +{ + jlong ret = 0; + jbyte *reqbuf = NULL; + jsize reqlen; + SM2_KEY *sm2_pub = NULL; + + if (!req) { + error_print(); + return 0; + } + + if (!(reqbuf = (*env)->GetByteArrayElements(env, req, NULL))) { + error_print(); + return 0; + } + reqlen = (*env)->GetArrayLength(env, req); + + if (!(sm2_pub = (SM2_KEY *)malloc(sizeof(SM2_KEY)))) { + error_print(); + goto end; + } + memset(sm2_pub, 0, sizeof(SM2_KEY)); + + if (x509_req_get_details((uint8_t *)reqbuf, reqlen, + NULL, NULL, NULL, sm2_pub, NULL, NULL, NULL, NULL, NULL) != 1) { + error_print(); + goto end; + } + + ret = (jlong)sm2_pub; + sm2_pub = NULL; + +end: + (*env)->ReleaseByteArrayElements(env, req, reqbuf, JNI_ABORT); + if (sm2_pub) { + gmssl_secure_clear(sm2_pub, sizeof(SM2_KEY)); + free(sm2_pub); + } + return ret; +} + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_verify + * Signature: ([BLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1verify( + JNIEnv *env, jclass this, + jbyteArray req, jstring signer_id) +{ + jint ret = -1; + jbyte *reqbuf = NULL; + jsize reqlen; + const char *signer_id_str = NULL; + size_t signer_id_len = 0; + + if (!req) { + error_print(); + return -1; + } + + if (!(reqbuf = (*env)->GetByteArrayElements(env, req, NULL))) { + error_print(); + return -1; + } + reqlen = (*env)->GetArrayLength(env, req); + + if (signer_id) { + if (!(signer_id_str = (*env)->GetStringUTFChars(env, signer_id, NULL))) { + error_print(); + goto end; + } + signer_id_len = strlen(signer_id_str); + } else { + signer_id_str = SM2_DEFAULT_ID; + signer_id_len = strlen(SM2_DEFAULT_ID); + } + + if (x509_req_verify((uint8_t *)reqbuf, reqlen, signer_id_str, signer_id_len) != 1) { + error_print(); + ret = 0; + goto end; + } + ret = 1; + +end: + (*env)->ReleaseByteArrayElements(env, req, reqbuf, JNI_ABORT); + if (signer_id_str && signer_id) (*env)->ReleaseStringUTFChars(env, signer_id, signer_id_str); + return ret; +} + diff --git a/src/main/c/gmssljni.h b/src/main/c/gmssljni.h index f9dbe2a..ae57e00 100644 --- a/src/main/c/gmssljni.h +++ b/src/main/c/gmssljni.h @@ -927,6 +927,54 @@ JNIEXPORT jlong JNICALL Java_org_gmssl_GmSSLJNI_cert_1get_1subject_1public_1key JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_cert_1verify_1by_1ca_1cert (JNIEnv *, jclass, jbyteArray, jbyteArray, jstring); +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_new + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1new + (JNIEnv *, jclass, jstring, jstring, jstring, jstring, jstring, jstring, jlong, jstring); + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_to_pem + * Signature: ([BLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1to_1pem + (JNIEnv *, jclass, jbyteArray, jstring); + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_from_pem + * Signature: (Ljava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1from_1pem + (JNIEnv *, jclass, jstring); + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_get_subject + * Signature: ([B)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1get_1subject + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_get_subject_public_key + * Signature: ([B)J + */ +JNIEXPORT jlong JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1get_1subject_1public_1key + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: org_gmssl_GmSSLJNI + * Method: x509_req_verify + * Signature: ([BLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_gmssl_GmSSLJNI_x509_1req_1verify + (JNIEnv *, jclass, jbyteArray, jstring); + #ifdef __cplusplus } #endif diff --git a/src/main/java/org/gmssl/GmSSLJNI.java b/src/main/java/org/gmssl/GmSSLJNI.java index 64e6dbb..4bbc8df 100644 --- a/src/main/java/org/gmssl/GmSSLJNI.java +++ b/src/main/java/org/gmssl/GmSSLJNI.java @@ -149,6 +149,17 @@ public class GmSSLJNI { public final static native long cert_get_subject_public_key(byte[] cert); public final static native int cert_verify_by_ca_cert(byte[] cert, byte[] cacert, String ca_sm2_id); + // Certificate Request (CSR) functions + public final static native byte[] x509_req_new( + String country, String state, String locality, + String org, String orgUnit, String commonName, + long sm2Key, String signerId); + public final static native int x509_req_to_pem(byte[] req, String file); + public final static native byte[] x509_req_from_pem(String file); + public final static native String[] x509_req_get_subject(byte[] req); + public final static native long x509_req_get_subject_public_key(byte[] req); + public final static native int x509_req_verify(byte[] req, String signerId); + public static void print_bytes(String label, byte[] data) { int i; System.out.printf("%s: ", label); diff --git a/src/main/java/org/gmssl/Sm2CertificateRequest.java b/src/main/java/org/gmssl/Sm2CertificateRequest.java new file mode 100644 index 0000000..190bc2c --- /dev/null +++ b/src/main/java/org/gmssl/Sm2CertificateRequest.java @@ -0,0 +1,264 @@ +/* + * Copyright 2014-2023 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.gmssl; + +/** + * SM2 Certificate Request (CSR) class. + * + * This class provides functionality to generate and manage X.509 Certificate + * Signing Requests (CSR) using SM2 algorithm, equivalent to the `gmssl reqgen` command. + * + * Example usage: + *
+ * // Generate a new key pair
+ * Sm2Key key = new Sm2Key();
+ * key.generateKey();
+ * 
+ * // Create a certificate request
+ * Sm2CertificateRequest csr = new Sm2CertificateRequest();
+ * csr.setSubject("CN", "Beijing", "Beijing", "MyOrg", "MyUnit", "www.example.com");
+ * byte[] reqData = csr.generate(key);
+ * 
+ * // Export to PEM file
+ * csr.toPem("request.pem");
+ * 
+ */ +public class Sm2CertificateRequest { + + public static final String DEFAULT_ID = GmSSLJNI.SM2_DEFAULT_ID; + + private byte[] req = null; + + // Subject fields + private String country = null; + private String state = null; + private String locality = null; + private String organization = null; + private String organizationalUnit = null; + private String commonName = null; + + public Sm2CertificateRequest() { + } + + /** + * Set the subject distinguished name fields. + * + * @param country Country code (C), e.g., "CN" + * @param state State or province name (ST), e.g., "Beijing" + * @param locality Locality name (L), e.g., "Beijing" + * @param organization Organization name (O), e.g., "MyCompany" + * @param organizationalUnit Organizational unit name (OU), e.g., "IT Department" + * @param commonName Common name (CN), e.g., "www.example.com" - REQUIRED + */ + public void setSubject(String country, String state, String locality, + String organization, String organizationalUnit, String commonName) { + if (commonName == null || commonName.isEmpty()) { + throw new GmSSLException("Common name (CN) is required"); + } + this.country = country; + this.state = state; + this.locality = locality; + this.organization = organization; + this.organizationalUnit = organizationalUnit; + this.commonName = commonName; + } + + /** + * Set only the common name (CN) for simple use cases. + * + * @param commonName Common name (CN), e.g., "www.example.com" + */ + public void setCommonName(String commonName) { + if (commonName == null || commonName.isEmpty()) { + throw new GmSSLException("Common name (CN) is required"); + } + this.commonName = commonName; + } + + /** + * Generate a certificate request using the provided SM2 key pair. + * Uses the default SM2 signer ID. + * + * @param key SM2 key pair (must contain private key) + * @return The DER-encoded certificate request + */ + public byte[] generate(Sm2Key key) { + return generate(key, DEFAULT_ID); + } + + /** + * Generate a certificate request using the provided SM2 key pair and signer ID. + * + * @param key SM2 key pair (must contain private key) + * @param signerId SM2 signer ID for signature + * @return The DER-encoded certificate request + */ + public byte[] generate(Sm2Key key, String signerId) { + if (this.commonName == null) { + throw new GmSSLException("Common name (CN) must be set before generating CSR"); + } + if (key == null) { + throw new GmSSLException("SM2 key is required"); + } + + long sm2Key = key.getPrivateKey(); + this.req = GmSSLJNI.x509_req_new( + this.country, this.state, this.locality, + this.organization, this.organizationalUnit, this.commonName, + sm2Key, signerId); + + if (this.req == null) { + throw new GmSSLException("Failed to generate certificate request"); + } + return this.req; + } + + /** + * Get the DER-encoded certificate request data. + * + * @return The DER-encoded certificate request, or null if not generated + */ + public byte[] getRequest() { + return this.req; + } + + /** + * Export the certificate request to a PEM file. + * + * @param file Path to the output PEM file + */ + public void toPem(String file) { + if (this.req == null) { + throw new GmSSLException("No certificate request to export"); + } + if (file == null || file.isEmpty()) { + throw new GmSSLException("File path is required"); + } + if (GmSSLJNI.x509_req_to_pem(this.req, file) != 1) { + throw new GmSSLException("Failed to export certificate request to PEM"); + } + } + + /** + * Import a certificate request from a PEM file. + * + * @param file Path to the PEM file + */ + public void fromPem(String file) { + if (file == null || file.isEmpty()) { + throw new GmSSLException("File path is required"); + } + this.req = GmSSLJNI.x509_req_from_pem(file); + if (this.req == null) { + throw new GmSSLException("Failed to import certificate request from PEM"); + } + } + + /** + * Get the subject distinguished name from the certificate request. + * + * @return Array of subject name components (e.g., ["C:CN", "ST:Beijing", "CN:www.example.com"]) + */ + public String[] getSubject() { + if (this.req == null) { + throw new GmSSLException("No certificate request loaded"); + } + String[] subject = GmSSLJNI.x509_req_get_subject(this.req); + if (subject == null) { + throw new GmSSLException("Failed to get subject from certificate request"); + } + return subject; + } + + /** + * Get the subject public key from the certificate request. + * + * @return SM2 public key + */ + public Sm2Key getSubjectPublicKey() { + if (this.req == null) { + throw new GmSSLException("No certificate request loaded"); + } + long sm2Pub = GmSSLJNI.x509_req_get_subject_public_key(this.req); + if (sm2Pub == 0) { + throw new GmSSLException("Failed to get subject public key from certificate request"); + } + return new Sm2Key(sm2Pub, false); + } + + /** + * Verify the signature of the certificate request. + * Uses the default SM2 signer ID. + * + * @return true if verification succeeds, false otherwise + */ + public boolean verify() { + return verify(DEFAULT_ID); + } + + /** + * Verify the signature of the certificate request with specified signer ID. + * + * @param signerId SM2 signer ID used for verification + * @return true if verification succeeds, false otherwise + */ + public boolean verify(String signerId) { + if (this.req == null) { + throw new GmSSLException("No certificate request loaded"); + } + int ret = GmSSLJNI.x509_req_verify(this.req, signerId); + if (ret < 0) { + throw new GmSSLException("Failed to verify certificate request"); + } + return ret == 1; + } + + /** + * Static method to generate a certificate request directly. + * + * @param country Country code (C) + * @param state State or province name (ST) + * @param locality Locality name (L) + * @param organization Organization name (O) + * @param organizationalUnit Organizational unit name (OU) + * @param commonName Common name (CN) - REQUIRED + * @param key SM2 key pair + * @param signerId SM2 signer ID + * @return The DER-encoded certificate request + */ + public static byte[] generateRequest( + String country, String state, String locality, + String organization, String organizationalUnit, String commonName, + Sm2Key key, String signerId) { + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + csr.setSubject(country, state, locality, organization, organizationalUnit, commonName); + return csr.generate(key, signerId); + } + + /** + * Static method to generate a certificate request with default signer ID. + * + * @param country Country code (C) + * @param state State or province name (ST) + * @param locality Locality name (L) + * @param organization Organization name (O) + * @param organizationalUnit Organizational unit name (OU) + * @param commonName Common name (CN) - REQUIRED + * @param key SM2 key pair + * @return The DER-encoded certificate request + */ + public static byte[] generateRequest( + String country, String state, String locality, + String organization, String organizationalUnit, String commonName, + Sm2Key key) { + return generateRequest(country, state, locality, organization, organizationalUnit, + commonName, key, DEFAULT_ID); + } +} diff --git a/src/test/java/org/gmssl/Sm2CertificateRequestTest.java b/src/test/java/org/gmssl/Sm2CertificateRequestTest.java new file mode 100644 index 0000000..1a0ab00 --- /dev/null +++ b/src/test/java/org/gmssl/Sm2CertificateRequestTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2014-2023 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.gmssl; + +import org.junit.Assert; +import org.junit.Test; +import java.io.File; + +public class Sm2CertificateRequestTest { + + @Test + public void testGenerateAndVerifyCsr() { + // 1. 生成 SM2 密钥对 + Sm2Key key = new Sm2Key(); + key.generateKey(); + System.out.println("Generated SM2 key pair"); + + // 2. 创建证书请求 + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + csr.setSubject("CN", "Beijing", "Beijing", "Test Organization", "IT Department", "www.test.com"); + + // 3. 生成 CSR + byte[] reqData = csr.generate(key); + Assert.assertNotNull("CSR data should not be null", reqData); + Assert.assertTrue("CSR data should have content", reqData.length > 0); + System.out.println("Generated CSR, length: " + reqData.length + " bytes"); + + // 4. 验证 CSR 签名 + boolean verified = csr.verify(); + Assert.assertTrue("CSR signature verification should succeed", verified); + System.out.println("CSR signature verified: " + verified); + + // 5. 获取并打印主题信息 + String[] subject = csr.getSubject(); + Assert.assertNotNull("Subject should not be null", subject); + Assert.assertTrue("Subject should have entries", subject.length > 0); + System.out.println("CSR Subject:"); + for (String s : subject) { + System.out.println(" " + s); + } + + // 6. 获取公钥并验证 + Sm2Key pubKey = csr.getSubjectPublicKey(); + Assert.assertNotNull("Public key should not be null", pubKey); + System.out.println("Extracted public key from CSR"); + } + + @Test + public void testCsrPemExportImport() { + // 1. 生成密钥和 CSR + Sm2Key key = new Sm2Key(); + key.generateKey(); + + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + csr.setSubject("CN", "Shanghai", "Shanghai", "Another Org", "Dev", "api.example.com"); + byte[] reqData = csr.generate(key); + + // 2. 导出到 PEM 文件 + String pemFile = "test_request.pem"; + csr.toPem(pemFile); + System.out.println("Exported CSR to: " + pemFile); + + // 验证文件存在 + File file = new File(pemFile); + Assert.assertTrue("PEM file should exist", file.exists()); + + // 3. 从 PEM 文件导入 + Sm2CertificateRequest csr2 = new Sm2CertificateRequest(); + csr2.fromPem(pemFile); + System.out.println("Imported CSR from: " + pemFile); + + // 4. 验证导入的 CSR + boolean verified = csr2.verify(); + Assert.assertTrue("Imported CSR verification should succeed", verified); + System.out.println("Imported CSR verified: " + verified); + + // 5. 比较主题信息 + String[] subject1 = csr.getSubject(); + String[] subject2 = csr2.getSubject(); + Assert.assertArrayEquals("Subjects should match", subject1, subject2); + System.out.println("Subject comparison passed"); + + // 清理测试文件 + file.delete(); + } + + @Test + public void testStaticGenerateMethod() { + // 使用静态方法生成 CSR + Sm2Key key = new Sm2Key(); + key.generateKey(); + + byte[] reqData = Sm2CertificateRequest.generateRequest( + "CN", "Shenzhen", "Shenzhen", + "Static Test Org", "QA", "static.test.com", + key); + + Assert.assertNotNull("Static generated CSR should not be null", reqData); + Assert.assertTrue("Static generated CSR should have content", reqData.length > 0); + System.out.println("Static method generated CSR, length: " + reqData.length + " bytes"); + + // 验证生成的 CSR + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + // 需要先保存再读取来验证 + String pemFile = "test_static_request.pem"; + GmSSLJNI.x509_req_to_pem(reqData, pemFile); + csr.fromPem(pemFile); + + boolean verified = csr.verify(); + Assert.assertTrue("Static generated CSR verification should succeed", verified); + System.out.println("Static generated CSR verified: " + verified); + + // 清理 + new File(pemFile).delete(); + } + + @Test + public void testMinimalCsr() { + // 测试只设置 CommonName 的最小 CSR + Sm2Key key = new Sm2Key(); + key.generateKey(); + + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + csr.setCommonName("minimal.test.com"); + byte[] reqData = csr.generate(key); + + Assert.assertNotNull("Minimal CSR should not be null", reqData); + boolean verified = csr.verify(); + Assert.assertTrue("Minimal CSR verification should succeed", verified); + System.out.println("Minimal CSR (CN only) generated and verified"); + } + + @Test + public void testCustomSignerId() { + // 测试自定义 signer ID + Sm2Key key = new Sm2Key(); + key.generateKey(); + + String customSignerId = "custom@signer.id"; + + Sm2CertificateRequest csr = new Sm2CertificateRequest(); + csr.setSubject("CN", "Hangzhou", "Hangzhou", "Custom ID Org", "Security", "custom.id.test.com"); + byte[] reqData = csr.generate(key, customSignerId); + + Assert.assertNotNull("CSR with custom signer ID should not be null", reqData); + + // 使用相同的 signer ID 验证 + boolean verified = csr.verify(customSignerId); + Assert.assertTrue("CSR with custom signer ID verification should succeed", verified); + System.out.println("CSR with custom signer ID generated and verified"); + + // 使用默认 signer ID 验证应该失败 + boolean verifiedWithDefault = csr.verify(); + Assert.assertFalse("CSR verification with wrong signer ID should fail", verifiedWithDefault); + System.out.println("CSR verification with wrong signer ID correctly failed"); + } +}