@@ -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");
+ }
+}