JNI: add String and X500Principal constructors to WolfSSLX509Name

pull/366/head
Chris Conlon 2026-05-05 16:21:15 -06:00
parent 1bea4bec2d
commit e013b63b20
4 changed files with 1698 additions and 0 deletions

View File

@ -269,6 +269,38 @@
<Field name="x509NamePtr"/>
<Bug pattern="IS2_INCONSISTENT_SYNC"/>
</Match>
<Match>
<Class name="com.wolfssl.WolfSSLX509Name"/>
<Field name="active"/>
<Bug pattern="IS2_INCONSISTENT_SYNC"/>
</Match>
<!--
IS2_INCONSISTENT_SYNC: WolfSSLX509Name cached mirror fields.
Written by updateMirrorField() during DN-based construction
(before the object is published) and from synchronized
setXxx() methods after construction. Reads via synchronized
getXxx() methods. Safe under JMM publication semantics.
-->
<Match>
<Class name="com.wolfssl.WolfSSLX509Name"/>
<Or>
<Field name="countryName"/>
<Field name="stateOrProvinceName"/>
<Field name="streetAddress"/>
<Field name="localityName"/>
<Field name="surname"/>
<Field name="commonName"/>
<Field name="emailAddress"/>
<Field name="organizationName"/>
<Field name="organizationalUnitName"/>
<Field name="postalCode"/>
<Field name="userId"/>
<Field name="title"/>
<Field name="domainComponent"/>
<Field name="serialNumber"/>
</Or>
<Bug pattern="IS2_INCONSISTENT_SYNC"/>
</Match>
<!--
IS2_INCONSISTENT_SYNC: WolfSSLSession callback context

View File

@ -20,7 +20,14 @@
*/
package com.wolfssl;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.security.auth.x500.X500Principal;
/**
* WolfSSLX509Name class, wraps native WOLFSSL_X509_NAME functionality.
@ -49,6 +56,9 @@ public class WolfSSLX509Name {
private String organizationalUnitName = null;
private String postalCode = null;
private String userId = null;
private String title = null;
private String domainComponent = null;
private String serialNumber = null;
/* Encoding types, matched to native define values */
private static final int MBSTRING_UTF8 = 0x100;
@ -66,6 +76,119 @@ public class WolfSSLX509Name {
*/
public WolfSSLX509Name() throws WolfSSLException {
initNative();
}
/**
* Create new WolfSSLX509Name populated from a distinguished name string.
*
* Two string formats are accepted:
* - RFC 2253 / RFC 4514: "CN=foo,O=bar,C=US"
* - OpenSSL-style oneline: "/C=US/O=bar/CN=foo"
*
* The format is auto detected. A string with first non-whitespace
* character '/' is parsed as OpenSSL-style oneline. Everything else is
* parsed as RFC 2253 / RFC 4514. Both ',' and ';' are accepted as RDN
* separators in RFC 2253 input. Backslash escapes (single character or
* two-hex-digit byte form) and double-quoted values are supported in
* RFC 2253. Hex-encoded values ("#hexdigits") are not supported and will
* cause this constructor to throw a WolfSSLException.
*
* Callers with a javax.naming.ldap.LdapName instance can pass
* myLdapName.toString() since LdapName's string form is RFC 2253.
* javax.naming is not available on Android, which is why this class
* does not provide a typed LdapName constructor.
*
* Relative Distinguished Names (RDNs) are added in the order used to build
* the X.509 subject, not necessarily in the textual order of the input.
* For RFC 2253 / RFC 4514 input, RDNs are added in reverse textual order
* (least significant first). For example "CN=foo,O=bar,C=US" is added as
* C, O, CN. OpenSSL-style oneline input is already written in that
* insertion order.
*
* Multi value RDNs (ex: "CN=a+OU=b") are not currently supported and will
* cause this constructor to throw a WolfSSLException (not supported in
* native wolfSSL).
*
* @param dn distinguished name string in RFC 2253 or OpenSSL oneline format
*
* @throws WolfSSLException if dn is null, empty, cannot be parsed, contains
* a multi value RDN, or contains an attribute type not recognized
* by native wolfSSL.
*/
@SuppressWarnings("this-escape")
public WolfSSLX509Name(String dn) throws WolfSSLException {
String trimmed = null;
if (dn == null) {
throw new WolfSSLException("WolfSSLX509Name dn is null");
}
trimmed = dn.trim();
if (trimmed.isEmpty()) {
throw new WolfSSLException("WolfSSLX509Name dn is empty");
}
initNative();
try {
if (trimmed.charAt(0) == '/') {
populateFromOneline(trimmed);
} else {
populateFromRfc2253(trimmed);
}
} catch (WolfSSLException | RuntimeException e) {
free();
throw e;
}
}
/**
* Create new WolfSSLX509Name populated from an X500Principal.
*
* Internally calls X500Principal.getName(RFC2253) and parses the string.
* This could be used with X509Certificate.getSubjectX500Principal().
*
* Multi value Relative Distinguished Names (RDNs) are not currently
* supported and will cause this constructor to throw a WolfSSLException.
*
* @param principal X500Principal to populate from
*
* @throws WolfSSLException if principal is null, contains a multi value
* RDN, or contains an attribute type not recognized by wolfSSL.
*/
@SuppressWarnings("this-escape")
public WolfSSLX509Name(X500Principal principal)
throws WolfSSLException {
String dn = null;
if (principal == null) {
throw new WolfSSLException("principal is null");
}
dn = principal.getName(X500Principal.RFC2253);
initNative();
try {
populateFromRfc2253(dn);
} catch (WolfSSLException | RuntimeException e) {
free();
throw e;
}
}
/**
* Allocate the native WOLFSSL_X509_NAME and mark this object active.
* Shared initialization for all constructors.
*
* @throws WolfSSLException if native API call fails.
*/
private void initNative() throws WolfSSLException {
x509NamePtr = X509_NAME_new();
if (x509NamePtr == 0) {
throw new WolfSSLException("Failed to create WolfSSLX509Name");
@ -80,6 +203,577 @@ public class WolfSSLX509Name {
}
}
/**
* Parse an OpenSSL "oneline" DN string into a list of [type, value] pairs,
* in encoded order (country first).
*
* The oneline format is "/Type1=Value1/Type2=Value2/..." with no defined
* escaping for '/' or '=' inside values. Inputs containing backslash
* escapes are rejected. Leading and trailing whitespace around both the
* attribute type and value is trimmed.
*
* @param dn oneline DN string to parse
*
* @throws WolfSSLException on malformed input (empty RDN, missing '=',
* empty attribute type, or backslash escape sequences)
*/
private static List<String[]> parseOnelineDn(String dn)
throws WolfSSLException {
int eq;
String type = null;
String value = null;
String[] parts = null;
List<String[]> rdns = new ArrayList<String[]>();
if (dn.length() < 2 || dn.charAt(0) != '/') {
throw new WolfSSLException(
"Oneline DN must start with '/' followed by RDNs");
}
if (dn.indexOf('\\') >= 0) {
throw new WolfSSLException(
"Backslash escapes in oneline DN are not supported, " +
"use RFC 2253 form (e.g. \"CN=foo,O=bar\") or pass an " +
"X500Principal instead");
}
parts = dn.substring(1).split("/", -1);
int partOffset = 1;
for (String part : parts) {
if (part.isEmpty()) {
throw new WolfSSLException(
"Invalid oneline DN: empty RDN at position " + partOffset);
}
eq = part.indexOf('=');
if (eq < 0) {
throw new WolfSSLException(
"Invalid oneline DN: missing '=' at position " +
partOffset + " (in segment \"" + part + "\")");
}
type = part.substring(0, eq).trim();
value = part.substring(eq + 1).trim();
if (type.isEmpty()) {
throw new WolfSSLException(
"Invalid oneline DN: empty attribute type at " +
"position " + partOffset);
}
rdns.add(new String[] { type, value });
partOffset += part.length() + 1;
}
if (rdns.isEmpty()) {
throw new WolfSSLException("Invalid oneline DN: no RDNs found");
}
return rdns;
}
/**
* Populate this name from a parsed oneline DN. The oneline format stores
* RDNs in encoded order (country first), which matches our append-to-end
* behavior in addEntryByTxt.
*
* @param dn oneline DN string to parse and populate from
* @throws WolfSSLException if dn is malformed or contains an attribute
* type not recognized by native wolfSSL.
*/
private void populateFromOneline(String dn)
throws WolfSSLException {
for (String[] tv : parseOnelineDn(dn)) {
addAttribute(tv[0], tv[1]);
}
}
/**
* Populate this name from an RFC 2253 / RFC 4514 distinguished name
* string. Parses the input, then iterates RDNs in reverse textual order
* (least significant first), so "CN=foo,O=bar,C=US" is added as C, O, CN.
*
* @param dn RFC 2253 / RFC 4514 distinguished name string
* @throws WolfSSLException if dn is malformed, contains a multi value RDN,
* contains a hex-encoded value, or contains an attribute type not
* recognized by native wolfSSL.
*/
private void populateFromRfc2253(String dn) throws WolfSSLException {
for (String[] tv : parseRfc2253Dn(dn)) {
addAttribute(tv[0], tv[1]);
}
}
/**
* Parse an RFC 2253 / RFC 4514 distinguished name string into a list of
* [type, value] pairs, in encoded order (least significant RDN first,
* matching conventional X.509 subject encoding order such as C, O, OU,
* CN).
*
* Supported syntax:
* - ',' and ';' as RDN separators
* - Whitespace tolerated around '=' and RDN separators
* - Attribute type as descr ([A-Za-z][A-Za-z0-9-]*) or numericoid
* ([0-9]+(\.[0-9]+)*)
* - Single-character escapes: \, \; \+ \" \\ \= \&lt; \&gt; \# \space
* - Hex-byte escapes: \xx, accumulated and decoded as UTF-8 (so \C3\A9
* produces 'e' acute)
* - Double-quoted values (defined in RFC 2253, omitted from RFC 4514
* but still accepted by major parsers including LdapName and
* X500Principal)
*
* Rejected (throws WolfSSLException):
* - '#hexpairs' values (BER-encoded form)
* - '+' multi-valued RDNs
* - Malformed input (missing '=', unterminated quote, trailing
* separator, etc.)
*
* @param dn RFC 2253 / RFC 4514 distinguished name string
* @return list of [type, value] pairs in least-significant-first order
* @throws WolfSSLException on malformed or unsupported input
*/
private static List<String[]> parseRfc2253Dn(String dn)
throws WolfSSLException {
List<String[]> rdns = new ArrayList<String[]>();
int len = dn.length();
int pos = 0;
while (true) {
pos = skipDnWhitespace(dn, pos);
if (pos >= len) {
if (rdns.isEmpty()) {
throw new WolfSSLException(
"Invalid DN: no RDNs found at position " + pos);
}
throw new WolfSSLException(
"Invalid DN: trailing RDN separator at position " + pos);
}
/* Parse attribute type */
int typeStart = pos;
char c = dn.charAt(pos);
if (c >= '0' && c <= '9') {
while (pos < len) {
char dc = dn.charAt(pos);
if ((dc >= '0' && dc <= '9') || dc == '.') {
pos++;
} else {
break;
}
}
} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
while (pos < len) {
char tc = dn.charAt(pos);
if ((tc >= 'A' && tc <= 'Z') || (tc >= 'a' && tc <= 'z') ||
(tc >= '0' && tc <= '9') || tc == '-') {
pos++;
} else {
break;
}
}
} else {
throw new WolfSSLException(
"Invalid DN: unexpected character '" + c +
"' at position " + pos);
}
String type = dn.substring(typeStart, pos);
/* Skip whitespace, expect '=' */
pos = skipDnWhitespace(dn, pos);
if (pos >= len || dn.charAt(pos) != '=') {
throw new WolfSSLException(
"Invalid DN: missing '=' after type '" + type + "'");
}
pos++;
pos = skipDnWhitespace(dn, pos);
/* Parse value */
int[] posRef = new int[] { pos };
String value = parseRfc2253Value(dn, posRef);
pos = posRef[0];
rdns.add(new String[] { type, value });
/* Separator */
pos = skipDnWhitespace(dn, pos);
if (pos >= len) {
break;
}
char sep = dn.charAt(pos);
if (sep == '+') {
throw new WolfSSLException(
"Multi-valued RDNs are not supported");
}
if (sep != ',' && sep != ';') {
throw new WolfSSLException(
"Invalid DN: expected ',' or ';' at position " + pos);
}
pos++;
}
if (rdns.isEmpty()) {
throw new WolfSSLException(
"Invalid DN: no RDNs found at position " + pos);
}
/* Reverse to insertion order matching the conventional X.509
* subject encoding order (least significant first, e.g. C, O, OU,
* CN for input "CN=foo,O=bar,OU=baz,C=US"). */
Collections.reverse(rdns);
return rdns;
}
/**
* Parse a single RFC 2253 attribute value starting at posRef[0]. Updates
* posRef[0] to the index after the value (before any separator).
*
* Handles quoted and unquoted forms, single-char and hex-byte escapes.
* Builds the value as a byte stream so multi-byte UTF-8 escapes (e.g.
* \C3\A9) decode correctly.
*/
private static String parseRfc2253Value(String dn, int[] posRef)
throws WolfSSLException {
int pos = posRef[0];
int len = dn.length();
/* Empty value at end of input ("CN=") is allowed for parity with the
* explicit setX("") path and the mid-DN empty case ("CN=,O=foo").
* Native may or may not produce useful output for an empty value, but
* the wrapper accepts it consistently across all entry points. */
if (pos >= len) {
posRef[0] = pos;
return "";
}
char first = dn.charAt(pos);
if (first == '#') {
throw new WolfSSLException(
"Hex-encoded RFC 2253 attribute values (#hexpairs form) " +
"are not supported");
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
/* Number of unescaped trailing whitespace bytes (' ' or '\t')
* appended at the current end of `bytes`. Reset to 0 by any
* non-whitespace byte and by any escape (so escaped trailing
* whitespace per RFC 2253, e.g. "CN=foo\ ", is preserved).
* Used to trim only unescaped trailing whitespace at the end. */
int trimmableTail = 0;
boolean quoted = (first == '"');
int quoteStart = pos;
if (quoted) {
pos++;
}
while (pos < len) {
char ch = dn.charAt(pos);
if (quoted) {
if (ch == '"') {
pos++;
posRef[0] = pos;
return new String(bytes.toByteArray(),
StandardCharsets.UTF_8);
}
} else {
if (ch == ',' || ch == ';' || ch == '+') {
break;
}
}
if (ch == '\\') {
if (pos + 1 >= len) {
throw new WolfSSLException(
"Invalid DN: trailing '\\' escape at position " + pos);
}
char next = dn.charAt(pos + 1);
if (isHex(next)) {
if (pos + 2 >= len || !isHex(dn.charAt(pos + 2))) {
throw new WolfSSLException(
"Invalid DN: incomplete hex escape at position " +
pos);
}
bytes.write((hexValue(next) << 4) |
hexValue(dn.charAt(pos + 2)));
pos += 3;
} else {
/* Single-character escape, encode as UTF-8 */
appendCodePointUtf8(bytes, next);
pos += 2;
}
/* Any escape (single-char or hex byte) is intentional content,
* even if the resulting byte is a whitespace one. Reset so we
* don't trim. */
trimmableTail = 0;
} else {
/* Plain code point, encode as UTF-8. Handle surrogate pairs by
* reading the full code point. */
int cp = dn.codePointAt(pos);
appendCodePointUtf8(bytes, cp);
pos += Character.charCount(cp);
if (!quoted && (ch == ' ' || ch == '\t')) {
trimmableTail++;
} else {
trimmableTail = 0;
}
}
}
if (quoted) {
throw new WolfSSLException(
"Invalid DN: unterminated quoted value starting at " +
"position " + quoteStart);
}
posRef[0] = pos;
/* Trim unescaped trailing whitespace. Escaped trailing whitespace
* ("CN=foo\ " or "CN=foo\20") is preserved because trimmableTail was
* reset to 0 at each escape. */
byte[] all = bytes.toByteArray();
return new String(all, 0, all.length - trimmableTail,
StandardCharsets.UTF_8);
}
private static int skipDnWhitespace(String dn, int pos) {
while (pos < dn.length() && (dn.charAt(pos) == ' ' ||
dn.charAt(pos) == '\t')) {
pos++;
}
return pos;
}
private static boolean isHex(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
private static int hexValue(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return c - 'A' + 10;
}
private static void appendCodePointUtf8(ByteArrayOutputStream out, int cp) {
byte[] b = new String(Character.toChars(cp))
.getBytes(StandardCharsets.UTF_8);
out.write(b, 0, b.length);
}
/**
* Add a single attribute to this name, normalizing the type before the
* native call and updating the cached Java side mirror.
*
* Normalization is necessary because wolfSSL's OBJ_txt2nid() lookup
* is case sensitive, while X500Principal.getName(RFC2253) commonly
* returns uppercase forms (ex: "STREET", "EMAILADDRESS") that don't
* match wolfSSL's canonical form.
*
* @param type attribute type string, for example "CN" or "commonName"
* @param value attribute value string
*
* @throws WolfSSLException if native JNI error has occurred, or input
* attribute type is not recognized by native wolfSSL.
*/
private void addAttribute(String type, String value)
throws WolfSSLException {
confirmObjectIsActive();
String nativeType = canonicalAttributeName(type);
addEntryByTxt(nativeType, value);
updateMirrorField(nativeType, value);
}
/**
* Map a DN attribute type to the canonical form recognized by native
* wolfSSL_OBJ_txt2nid(). Handles the common short/long aliases case
* insensitively. Dotted OIDs are mapped to their canonical keyword for
* well known X.500 attributes. Unrecognized OIDs and unrecognized keyword
* types are passed through. For pass through types the downstream native
* call will succeed if the casing matches, or fail cleanly via
* addEntryByTxt() if not. Null and empty inputs are returned unchanged.
* Callers downstream reject them via addEntryByTxt().
*
* @param attrType attribute type string, for example "CN", "commonName",
* or "2.5.4.3"
*
* @return canonical attribute type string recognized by wolfSSL, for
* example "commonName" for "CN" / "commonName" / "2.5.4.3".
* Unrecognized inputs are passed through.
*/
private static String canonicalAttributeName(String attrType) {
String key = null;
if (attrType == null || attrType.isEmpty()) {
return attrType;
}
/* Dotted OIDs start with an ASCII digit. Translate well known X.500
* attribute OIDs to their canonical keyword so equivalent DN forms
* (ex: "CN=foo" vs "2.5.4.3=foo") produce identical state. Unknown
* OIDs are passed through. wolfSSL_OBJ_txt2nid() handles dotted OID
* lookup natively. */
char first = attrType.charAt(0);
if (first >= '0' && first <= '9') {
switch (attrType) {
case "2.5.4.3": return "commonName";
case "2.5.4.4": return "surname";
case "2.5.4.5": return "serialNumber";
case "2.5.4.6": return "countryName";
case "2.5.4.7": return "localityName";
case "2.5.4.8": return "stateOrProvinceName";
case "2.5.4.9": return "streetAddress";
case "2.5.4.10": return "organizationName";
case "2.5.4.11": return "organizationalUnitName";
case "2.5.4.12": return "title";
case "2.5.4.17": return "postalCode";
case "0.9.2342.19200300.100.1.1": return "userId";
case "0.9.2342.19200300.100.1.25": return "domainComponent";
case "1.2.840.113549.1.9.1": return "emailAddress";
default: return attrType;
}
}
key = attrType.toUpperCase(Locale.ROOT);
switch (key) {
case "C":
case "COUNTRYNAME":
return "countryName";
case "ST":
case "STATEORPROVINCENAME":
return "stateOrProvinceName";
case "STREET":
case "STREETADDRESS":
return "streetAddress";
case "L":
case "LOCALITYNAME":
return "localityName";
case "SN":
case "SURNAME":
return "surname";
case "CN":
case "COMMONNAME":
return "commonName";
case "EMAILADDRESS":
return "emailAddress";
case "O":
case "ORGANIZATIONNAME":
return "organizationName";
case "OU":
case "ORGANIZATIONALUNITNAME":
return "organizationalUnitName";
case "POSTALCODE":
return "postalCode";
case "UID":
case "USERID":
return "userId";
case "T":
case "TITLE":
return "title";
case "DC":
case "DOMAINCOMPONENT":
return "domainComponent";
case "SERIALNUMBER":
return "serialNumber";
default:
return attrType;
}
}
/**
* Update the cached Java-side mirror field if attrType matches one of the
* known short/long names. Unknown attribute types are silently skipped
* since they were already pushed through the native call. Comparison is
* case-insensitive.
*
* @param attrType attribute type string
* @param value attribute value string
*/
private void updateMirrorField(String attrType, String value) {
String key = null;
if (attrType == null) {
return;
}
key = attrType.toUpperCase(Locale.ROOT);
switch (key) {
case "C":
case "COUNTRYNAME":
this.countryName = value;
break;
case "ST":
case "STATEORPROVINCENAME":
this.stateOrProvinceName = value;
break;
case "STREET":
case "STREETADDRESS":
this.streetAddress = value;
break;
case "L":
case "LOCALITYNAME":
this.localityName = value;
break;
case "SN":
case "SURNAME":
this.surname = value;
break;
case "CN":
case "COMMONNAME":
this.commonName = value;
break;
case "EMAILADDRESS":
this.emailAddress = value;
break;
case "O":
case "ORGANIZATIONNAME":
this.organizationName = value;
break;
case "OU":
case "ORGANIZATIONALUNITNAME":
this.organizationalUnitName = value;
break;
case "POSTALCODE":
this.postalCode = value;
break;
case "UID":
case "USERID":
this.userId = value;
break;
case "T":
case "TITLE":
this.title = value;
break;
case "DC":
case "DOMAINCOMPONENT":
this.domainComponent = value;
break;
case "SERIALNUMBER":
this.serialNumber = value;
break;
default:
break;
}
}
/**
* Verifies that the current WolfSSLX509Name object is active.
*
@ -133,6 +827,10 @@ public class WolfSSLX509Name {
throw new WolfSSLException(
"field or entry is null in addEntryByTxt()");
}
if (field.isEmpty()) {
throw new WolfSSLException(
"field is empty in addEntryByTxt()");
}
synchronized (x509NameLock) {
entryBytes = entry.getBytes(StandardCharsets.UTF_8);
@ -389,6 +1087,72 @@ public class WolfSSLX509Name {
this.userId = id;
}
/**
* Set title for this name object.
*
* @param name String containing title to be set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
* @throws WolfSSLException if native JNI error has occurred, or input
* argument is invalid.
*/
public synchronized void setTitle(String name)
throws IllegalStateException, WolfSSLException {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr,
() -> "entered setTitle(" + name + ")");
addEntryByTxt("title", name);
this.title = name;
}
/**
* Set domain component for this name object.
*
* @param dc String containing domain component to be set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
* @throws WolfSSLException if native JNI error has occurred, or input
* argument is invalid.
*/
public synchronized void setDomainComponent(String dc)
throws IllegalStateException, WolfSSLException {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr,
() -> "entered setDomainComponent(" + dc + ")");
addEntryByTxt("domainComponent", dc);
this.domainComponent = dc;
}
/**
* Set serial number for this name object.
*
* @param sn String containing serial number to be set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
* @throws WolfSSLException if native JNI error has occurred, or input
* argument is invalid.
*/
public synchronized void setSerialNumber(String sn)
throws IllegalStateException, WolfSSLException {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr,
() -> "entered setSerialNumber(" + sn + ")");
addEntryByTxt("serialNumber", sn);
this.serialNumber = sn;
}
/**
* Get country name set in this object.
*
@ -587,6 +1351,59 @@ public class WolfSSLX509Name {
return this.userId;
}
/**
* Get title set in this object.
*
* @return title string, or null if not yet set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
*/
public synchronized String getTitle() {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr, () -> "entered getTitle()");
return this.title;
}
/**
* Get domain component set in this object.
*
* @return domain component string, or null if not yet set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
*/
public synchronized String getDomainComponent() {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr,
() -> "entered getDomainComponent()");
return this.domainComponent;
}
/**
* Get serial number set in this object.
*
* @return serial number string, or null if not yet set
*
* @throws IllegalStateException if WolfSSLX509Name has been freed.
*/
public synchronized String getSerialNumber() {
confirmObjectIsActive();
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509NamePtr,
() -> "entered getSerialNumber()");
return this.serialNumber;
}
@Override
public String toString() {

View File

@ -37,6 +37,7 @@ import com.wolfssl.WolfSSLException;
WolfCryptECCTest.class,
WolfSSLCertificateTest.class,
WolfSSLCertRequestTest.class,
WolfSSLX509NameTest.class,
WolfSSLCertManagerTest.class,
WolfSSLNameConstraintsTest.class,
WolfSSLCRLTest.class

View File

@ -0,0 +1,848 @@
/* WolfSSLX509NameTest.java
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
package com.wolfssl.test;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import javax.security.auth.x500.X500Principal;
import com.wolfssl.WolfSSL;
import com.wolfssl.WolfSSLException;
import com.wolfssl.WolfSSLX509Name;
public class WolfSSLX509NameTest {
/* True if native wolfSSL recognizes the "title" attribute (added in
* v5.8.2). When false, tests that exercise title / domainComponent /
* serialNumber are skipped via Assume. */
private static boolean extendedAttrsSupported = false;
@Rule
public TestRule testWatcher = TimedTestWatcher.create();
@BeforeClass
public static void loadLibrary() {
System.out.println("WolfSSLX509Name Class");
try {
WolfSSL.loadLibrary();
} catch (UnsatisfiedLinkError ule) {
fail("failed to load native JNI library");
}
/* Check if wolfSSL knows the extended attributes added in v5.8.2
* (title, domainComponent, serialNumber). The check uses setTitle
* since title was the first added in that set. */
try {
WolfSSLX509Name probe = new WolfSSLX509Name();
try {
probe.setTitle("probe");
extendedAttrsSupported = true;
} finally {
probe.free();
}
} catch (Exception e) {
extendedAttrsSupported = false;
}
}
@Test
public void test_String_RFC2253_AllKnownAttributes()
throws WolfSSLException {
/* Exercises every cached mirror field that wolfSSL has
* supported since before v5.8.2. The extended attributes (title,
* domainComponent, serialNumber) are covered separately by
* test_String_RFC2253_ExtendedAttributes since they require
* wolfSSL >= 5.8.2. */
String dn = "UID=tester,CN=wolfssl.com," +
"EMAILADDRESS=support@wolfssl.com,SN=Smith," +
"OU=Engineering,O=wolfSSL Inc.,POSTALCODE=59715," +
"STREET=12345 Test St,L=Bozeman,ST=Montana,C=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
assertEquals("US", name.getCountryName());
assertEquals("Montana", name.getStateOrProvinceName());
assertEquals("Bozeman", name.getLocalityName());
assertEquals("12345 Test St", name.getStreetAddress());
assertEquals("59715", name.getPostalCode());
assertEquals("wolfSSL Inc.", name.getOrganizationName());
assertEquals("Engineering", name.getOrganizationalUnitName());
assertEquals("Smith", name.getSurname());
assertEquals("support@wolfssl.com", name.getEmailAddress());
assertEquals("wolfssl.com", name.getCommonName());
assertEquals("tester", name.getUserId());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_ExtendedAttributes()
throws WolfSSLException {
/* title, domainComponent, and serialNumber were added to wolfSSL
* OBJ table in v5.8.2. Skip on older builds. The input also tests
* case-sensitive aliases that wolfSSL natively doesn't recognize
* without canonical mapping (lowercase "dc", short forms "T" /
* "SERIALNUMBER". */
Assume.assumeTrue(
"wolfSSL does not recognize 'title' (require >= 5.8.2)",
extendedAttrsSupported);
WolfSSLX509Name name = new WolfSSLX509Name(
"T=CTO,dc=example,SERIALNUMBER=42,CN=foo");
try {
assertEquals("CTO", name.getTitle());
assertEquals("example", name.getDomainComponent());
assertEquals("42", name.getSerialNumber());
assertEquals("foo", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_SerialNumberOid()
throws WolfSSLException {
/* 2.5.4.5 is the standard X.500 OID for serialNumber. The
* canonicalAttributeName() OID switch must map it to "serialNumber"
* so the mirror populates the same as if the user had passed
* "SERIALNUMBER=foo". Gated like the other extended-attribute tests
* since wolfSSL added serialNumber to its OBJ table in v5.8.2. */
Assume.assumeTrue(
"wolfSSL does not recognize 'serialNumber' (require >= 5.8.2)",
extendedAttrsSupported);
WolfSSLX509Name name =
new WolfSSLX509Name("2.5.4.5=ABC123,CN=foo");
try {
assertEquals("ABC123", name.getSerialNumber());
assertEquals("foo", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_LongAttributeNames()
throws WolfSSLException {
String dn = "commonName=wolfssl.com," +
"organizationName=wolfSSL,countryName=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
assertEquals("US", name.getCountryName());
assertEquals("wolfSSL", name.getOrganizationName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_SemicolonSeparator()
throws WolfSSLException {
/* RFC 2253 also accepts ';' as an RDN separator. */
String dn = "CN=wolfssl.com;O=wolfSSL Inc.;C=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
assertEquals("US", name.getCountryName());
assertEquals("wolfSSL Inc.", name.getOrganizationName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_OidAttributeTypes()
throws WolfSSLException {
/* 2.5.4.3 = commonName,
* 2.5.4.10 = organizationName,
* 2.5.4.6 = countryName. */
String dn = "2.5.4.3=wolfssl.com,2.5.4.10=wolfSSL,2.5.4.6=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
/* Well known X.500 OIDs are translated to their canonical
* keyword, so mirror fields populate the same as if the user
* had passed "CN=...,O=...,C=...". */
assertEquals("US", name.getCountryName());
assertEquals("wolfSSL", name.getOrganizationName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_UnknownOidPassesThrough() {
/* An OID not in our well known table. wolfSSL doesn't recognize
* "1.2.3.4.5.6.7" so the native call rejects it. This documents
* that unknown OIDs reach native and aren't silently swallowed. */
try {
new WolfSSLX509Name("1.2.3.4.5.6.7=foo,CN=bar");
fail("expected WolfSSLException for unknown OID");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_QuotedValueWithComma()
throws WolfSSLException {
/* RFC 2253 allows quoting a value that contains a comma. */
String dn = "CN=\"Foo, Inc.\",O=Bar,C=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
assertEquals("Foo, Inc.", name.getCommonName());
assertEquals("Bar", name.getOrganizationName());
assertEquals("US", name.getCountryName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_EscapedComma()
throws WolfSSLException {
/* Backslash-escaped comma in value. */
String dn = "CN=Foo\\, Inc.,O=Bar,C=US";
WolfSSLX509Name name = new WolfSSLX509Name(dn);
try {
assertEquals("Foo, Inc.", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_LeadingTrailingWhitespace()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name(" CN=wolfssl.com,C=US ");
try {
assertEquals("US", name.getCountryName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_SingleComponent()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name("CN=foo");
try {
assertEquals("foo", name.getCommonName());
assertNull(name.getCountryName());
} finally {
name.free();
}
}
@Test
public void test_String_Oneline_Basic() throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name(
"/C=US/ST=Montana/L=Bozeman/O=wolfSSL Inc." +
"/OU=Engineering/CN=wolfssl.com");
try {
assertEquals("US", name.getCountryName());
assertEquals("Montana", name.getStateOrProvinceName());
assertEquals("Bozeman", name.getLocalityName());
assertEquals("wolfSSL Inc.", name.getOrganizationName());
assertEquals("Engineering", name.getOrganizationalUnitName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_Oneline_SingleComponent()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name("/CN=foo");
try {
assertEquals("foo", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_Oneline_LongAttributeNames()
throws WolfSSLException {
WolfSSLX509Name name =
new WolfSSLX509Name("/countryName=US/commonName=foo");
try {
assertEquals("US", name.getCountryName());
assertEquals("foo", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_Oneline_LeadingTrailingWhitespace()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name(" /C=US/CN=foo ");
try {
assertEquals("US", name.getCountryName());
assertEquals("foo", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_Oneline_BareSlashThrows() {
try {
new WolfSSLX509Name("/");
fail("expected WolfSSLException for bare '/'");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_MissingEqualsThrows() {
try {
new WolfSSLX509Name("/CN=foo/bar/O=baz");
fail("expected WolfSSLException for RDN missing '='");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_EmptyTypeThrows() {
try {
new WolfSSLX509Name("/=foo/CN=bar");
fail("expected WolfSSLException for empty attribute type");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_DoubleSlashThrows() {
/* "//CN=foo" (empty first RDN) */
try {
new WolfSSLX509Name("//CN=foo");
fail("expected WolfSSLException for empty RDN");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_TrailingSlashThrows() {
/* split with limit -1 keeps trailing empty (empty RDN) */
try {
new WolfSSLX509Name("/CN=foo/");
fail("expected WolfSSLException for trailing '/'");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_BackslashEscapeThrows() {
/* OpenSSL's oneline can emit "\/" for slashes in values. We
* reject rather than mis-parse, and the message points users
* to RFC 2253 / X500Principal alternatives. */
try {
new WolfSSLX509Name("/O=ACME\\/West/CN=foo");
fail("expected WolfSSLException for backslash escape");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_Oneline_UnknownAttributeThrows() {
try {
new WolfSSLX509Name("/FOO=bar/CN=baz");
fail("expected WolfSSLException for unknown attribute");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_NullThrows() {
try {
new WolfSSLX509Name((String) null);
fail("expected WolfSSLException for null String DN");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_EmptyThrows() {
try {
new WolfSSLX509Name("");
fail("expected WolfSSLException for empty String DN");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_WhitespaceOnlyThrows() {
try {
new WolfSSLX509Name(" ");
fail("expected WolfSSLException for whitespace-only DN");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_InvalidSyntaxThrows() {
try {
new WolfSSLX509Name("not a valid dn");
fail("expected WolfSSLException for invalid DN syntax");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_MultiValuedRdnThrows() {
try {
new WolfSSLX509Name("CN=foo+OU=bar,O=baz,C=US");
fail("expected WolfSSLException for multi-valued RDN");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_UnknownAttributeThrows() {
/* "FOO" is not a recognized short/long name and is not a dotted
* OID, so wolfSSL_OBJ_txt2nid() returns WC_NID_undef and the native
* add_entry_by_txt() call fails. */
try {
new WolfSSLX509Name("FOO=bar,CN=baz");
fail("expected WolfSSLException for unknown attribute");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_HexEncodedValueThrows() {
/* RFC 2253 #hexpairs form are rejected: bytes are BER-encoded
* (tag + length + value), not UTF-8 text. "#1303616263" =
* PrintableString "abc" in BER. */
try {
new WolfSSLX509Name("CN=#1303616263,O=foo,C=US");
fail("expected WolfSSLException for hex-encoded BER value");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_HexByteEscape()
throws WolfSSLException {
/* "\C3\A9" = UTF-8 encoding of 'e' acute (U+00E9). The parser
* accumulates hex-byte escapes as a byte sequence and decodes the
* whole value as UTF-8, so multi-byte sequences round-trip.
*
* Use a unicode escape for the literal rather than the raw 'e'
* acute character, so this test compiles correctly on platforms
* where javac defaults to a non-UTF-8 source encoding (e.g. on
* Windows where javac defaults to the platform charset). */
WolfSSLX509Name name = new WolfSSLX509Name("CN=Caf\\C3\\A9,C=US");
try {
assertEquals("Caf\u00e9", name.getCommonName());
assertEquals("US", name.getCountryName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_BackslashLiteral()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name("CN=foo\\\\bar,C=US");
try {
assertEquals("foo\\bar", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_EscapedTrailingSpacePreserved()
throws WolfSSLException {
/* RFC 2253 requires trailing spaces in unquoted values to be escaped
* (single-char "\ " or hex "\20"). The parser must preserve the
* escaped space and only trim unescaped trailing whitespace. */
WolfSSLX509Name viaCharEscape = new WolfSSLX509Name("CN=foo\\ ,O=bar");
try {
assertEquals("foo ", viaCharEscape.getCommonName());
} finally {
viaCharEscape.free();
}
WolfSSLX509Name viaHexEscape = new WolfSSLX509Name("CN=foo\\20,O=bar");
try {
assertEquals("foo ", viaHexEscape.getCommonName());
} finally {
viaHexEscape.free();
}
}
@Test
public void test_String_RFC2253_MixedEscapedAndUnescapedTrailingSpace()
throws WolfSSLException {
/* "foo\ " = escaped space then unescaped space. The escaped space
* is preserved, the unescaped one is trimmed. */
WolfSSLX509Name name = new WolfSSLX509Name("CN=foo\\ ,O=bar");
try {
assertEquals("foo ", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_UnescapedTrailingWhitespaceTrimmed()
throws WolfSSLException {
/* Plain trailing whitespace in an unquoted value is trimmed. */
WolfSSLX509Name name = new WolfSSLX509Name("CN=foo ,O=bar");
try {
assertEquals("foo", name.getCommonName());
assertEquals("bar", name.getOrganizationName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_QuotedWithEmbeddedQuote()
throws WolfSSLException {
WolfSSLX509Name name =
new WolfSSLX509Name("CN=\"he said \\\"hi\\\"\",C=US");
try {
assertEquals("he said \"hi\"", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_WhitespaceAroundEquals()
throws WolfSSLException {
WolfSSLX509Name name = new WolfSSLX509Name("CN = foo, C = US");
try {
assertEquals("foo", name.getCommonName());
assertEquals("US", name.getCountryName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_TrailingSeparatorThrows() {
try {
new WolfSSLX509Name("CN=foo,C=US,");
fail("expected WolfSSLException for trailing separator");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_MissingEqualsThrows() {
try {
new WolfSSLX509Name("CN foo,C=US");
fail("expected WolfSSLException for missing '='");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_UnterminatedQuoteThrows() {
try {
new WolfSSLX509Name("CN=\"unterminated,C=US");
fail("expected WolfSSLException for unterminated quote");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_TrailingBackslashThrows() {
/* Single '\' at end of value with nothing to escape. */
try {
new WolfSSLX509Name("CN=foo\\");
fail("expected WolfSSLException for trailing backslash");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_IncompleteHexEscapeAtEndThrows() {
/* "\C" at end of input: only one hex digit, no second to make a
* complete \xx byte escape. */
try {
new WolfSSLX509Name("CN=foo\\C");
fail("expected WolfSSLException for incomplete hex escape");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_IncompleteHexEscapeBadDigitThrows() {
/* "\Cz": first nibble is hex, second isn't. Parser must reject
* rather than treat as single-char escape. */
try {
new WolfSSLX509Name("CN=foo\\Cz");
fail("expected WolfSSLException for non-hex second digit");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_UnexpectedCharAtTypeStartThrows() {
/* '=' is not a valid first char for an attribute type. */
try {
new WolfSSLX509Name("=foo,CN=bar");
fail("expected WolfSSLException for unexpected character");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_BadRdnSeparatorThrows() {
/* After a quoted value the next non-whitespace char must be ','
* or ';'. Anything else (here '|') hits the parser's separator
* check rather than being silently absorbed into the value. */
try {
new WolfSSLX509Name("CN=\"foo\"|O=bar");
fail("expected WolfSSLException for bad RDN separator");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_String_RFC2253_RdnInsertionOrderIsReversed()
throws WolfSSLException {
/* Parser walks textual L-to-R then Collections.reverse()s before
* insertion (least-significant-first / X.509 encoding order).
* Mirrors are "last write wins": after reversal the calls happen
* as CN=last then CN=first, leaving "first" in the mirror. If
* reversal weren't happening, the mirror would hold "last". */
WolfSSLX509Name name = new WolfSSLX509Name("CN=first,CN=last");
try {
assertEquals("first", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_String_RFC2253_EmptyValueAtEofMatchesSetX()
throws WolfSSLException {
/* "CN=" at end of input and "CN=,O=foo" both produce an RDN with an
* empty value, matching the explicit setX("") path. Native may or may
* not produce a useful cert from an empty value, but the wrapper
* accepts it consistently across all three entry points (no-arg +
* setX, parser at EOF, parser mid-DN). */
WolfSSLX509Name viaSet = new WolfSSLX509Name();
try {
viaSet.setCommonName("");
} catch (WolfSSLException e) {
/* If setX("") doesn't go through cleanly, the parser path for
* "CN=" must fail the same way. Verify and exit. */
viaSet.free();
try {
new WolfSSLX509Name("CN=");
fail("setX(\"\") threw but parser path didn't");
} catch (WolfSSLException e2) {
/* expected: paths conform */
}
return;
}
viaSet.free();
/* setX("") succeeded, so the parser must too. */
WolfSSLX509Name viaParser = new WolfSSLX509Name("CN=");
try {
assertEquals("", viaParser.getCommonName());
} finally {
viaParser.free();
}
}
@Test
public void test_String_RFC2253_SurrogatePairValue()
throws WolfSSLException {
/* U+1F600 (grinning face emoji) encodes as a UTF-16 surrogate
* pair in a Java String and four UTF-8 bytes. The parser uses
* codePointAt + charCount so the pair flows through to the byte
* stream correctly. charAt-only logic would split or mangle it. */
String emoji = "\uD83D\uDE00";
WolfSSLX509Name name =
new WolfSSLX509Name("CN=" + emoji + ",C=US");
try {
assertEquals(emoji, name.getCommonName());
assertEquals("US", name.getCountryName());
} finally {
name.free();
}
}
@Test
public void test_X500Principal_PopulatesMirrorFields()
throws WolfSSLException {
X500Principal principal = new X500Principal(
"CN=wolfssl.com,O=wolfSSL Inc.,C=US");
WolfSSLX509Name name = new WolfSSLX509Name(principal);
try {
assertEquals("US", name.getCountryName());
assertEquals("wolfSSL Inc.", name.getOrganizationName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_X500Principal_RoundTrip() throws WolfSSLException {
/* Build a principal from RFC 2253 string, feed it to our
* constructor, verify mirrors match. This is the canonical use case
* for round tripping cert subjects. */
String original = "CN=wolfssl.com,OU=Engineering," +
"O=wolfSSL Inc.,L=Bozeman,ST=Montana,C=US";
X500Principal principal = new X500Principal(original);
WolfSSLX509Name name = new WolfSSLX509Name(principal);
try {
assertEquals("US", name.getCountryName());
assertEquals("Montana", name.getStateOrProvinceName());
assertEquals("Bozeman", name.getLocalityName());
assertEquals("wolfSSL Inc.", name.getOrganizationName());
assertEquals("Engineering", name.getOrganizationalUnitName());
assertEquals("wolfssl.com", name.getCommonName());
} finally {
name.free();
}
}
@Test
public void test_X500Principal_NullThrows() {
try {
new WolfSSLX509Name((X500Principal) null);
fail("expected WolfSSLException for null X500Principal");
} catch (WolfSSLException e) {
/* expected */
}
}
@Test
public void test_X500Principal_UnknownAttributeThrows() {
/* X500Principal accepts any attribute as a string. Our constructor
* should fail when wolfSSL doesn't recognize it. */
X500Principal principal = new X500Principal("1.2.3.4.5.6.7=foo,CN=bar");
try {
new WolfSSLX509Name(principal);
fail("expected WolfSSLException for unknown OID");
} catch (WolfSSLException e) {
/* expected */
}
}
}