Skip to content

Bouncy Castle Proprietary API Migration Examples

Chapters 3 and 4 cover migration of standard JCE and JSSE API usage where the primary change is swapping the provider name. This chapter addresses migration of code that uses Bouncy Castle's proprietary (non-standard) APIs directly, such as classes under org.bouncycastle.asn1.*, org.bouncycastle.cert.*, and org.bouncycastle.crypto.*. These cases require more significant code changes since there is no standard Java API equivalent.

Bouncy Castle Equivalent Functionality Gaps

wolfSSL provides functionally equivalent implementations of a subset of Bouncy Castle API use through the classes and related APIs mentioned below.

If the Bouncy Castle functionality equivalents you are using are missing from wolfSSL JNI/JSSE or wolfCrypt JNI/JCE, please contact wolfSSL support (support@wolfssl.com) to request the feature(s) be added.

X.509 Certificate Extensions

The following sections cover migration of Bouncy Castle code that parses X.509 certificate extensions using org.bouncycastle.asn1.* and org.bouncycastle.cert.* classes. wolfSSL handles ASN.1 extension parsing internally in native code and exposes the results through the WolfSSLCertificate class and its associated types. In each case, the multi-step ASN.1 decoding required by Bouncy Castle is replaced by direct method calls on WolfSSLCertificate.

Name Constraint Validation

A common use of the Bouncy Castle proprietary API is parsing X.509 Name Constraints extensions and validating certificate names against permitted and excluded subtrees. Bouncy Castle exposes this through its ASN.1 and PKI validation classes. wolfSSL provides equivalent functionality through the WolfSSLCertificate and WolfSSLNameConstraints classes.

Bouncy Castle Approach

Bouncy Castle Name Constraint validation is a multi-step process involving parsing raw extension bytes, constructing a validator object, populating it with subtrees, and then checking names against the validator.

Parsing the Name Constraints extension:

import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.NameConstraints;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;

/* Get raw extension bytes from a X509Certificate */
byte[] extensionValue =
    certificate.getExtensionValue(Extension.nameConstraints.getId());

/* Unwrap the OCTET STRING, parse the ASN.1 structure */
NameConstraints nameConstraints = NameConstraints.getInstance(
    JcaX509ExtensionUtils.parseExtensionValue(extensionValue));

Building and populating the validator:

import org.bouncycastle.asn1.x509.PKIXNameConstraintValidator;

PKIXNameConstraintValidator validator = new PKIXNameConstraintValidator();

/* Add permitted subtrees */
if (nameConstraints.getPermittedSubtrees() != null) {
    validator.intersectPermittedSubtree(
        nameConstraints.getPermittedSubtrees());
}

/* Add excluded subtrees */
if (nameConstraints.getExcludedSubtrees() != null) {
    for (int i = 0;
         i < nameConstraints.getExcludedSubtrees().length; i++) {
        validator.addExcludedSubtree(
            nameConstraints.getExcludedSubtrees()[i]);
    }
}

Checking a name against the constraints:

import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.NameConstraintValidatorException;

try {
    validator.checkPermitted(name);
    validator.checkExcluded(name);
    /* Name is valid */
} catch (NameConstraintValidatorException e) {
    /* Name violates constraints */
}

wolfSSL Approach

wolfSSL handles ASN.1 parsing and constraint management internally in native code. Rather than manually extracting the extension, parsing it, and building a validator, the WolfSSLCertificate class parses all extensions when the certificate is loaded, and WolfSSLNameConstraints provides a single method to check a name against both permitted and excluded subtrees.

Parsing the Name Constraints from a certificate:

import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLNameConstraints;

WolfSSLCertificate wolfCert = null;
try {
    wolfCert = new WolfSSLCertificate(certificate.getEncoded());
    WolfSSLNameConstraints nc = wolfCert.getNameConstraints();

    /* nc is null if the certificate has no Name Constraints extension */
    if (nc != null) {
        /* use nc for validation (see below) */
    }
} finally {
    if (wolfCert != null) {
        wolfCert.free();
    }
}

Checking a name against the constraints:

/* Checks both permitted and excluded subtrees in a single call.
 * Returns true if the name satisfies all constraints. */
boolean allowed = nc.checkName(name.getType(), name.getValue());

Extracting Name Constraint Subtree Values

Beyond validating names, applications often need to extract and display the permitted or excluded subtree values from a certificate's Name Constraints extension. With Bouncy Castle this requires manually decoding each GeneralSubtree entry, handling different GeneralName types (DNS names, IP addresses, etc.) individually. IP address constraints are particularly involved since they are encoded as raw ASN.1 octets containing both the address and subnet mask, which must be split and converted separately.

Bouncy Castle Approach

Getting the subtree list from a parsed NameConstraints:

import org.bouncycastle.asn1.x509.GeneralSubtree;

/* After parsing NameConstraints (see previous section) */
GeneralSubtree[] permitted = nameConstraints.getPermittedSubtrees();
GeneralSubtree[] excluded = nameConstraints.getExcludedSubtrees();

Extracting the value from a GeneralSubtree entry:

For most GeneralName types (DNS, email, URI, etc.), the value can be obtained by calling toString() on the name. IP address constraints require additional decoding since the value is stored as a DER-encoded octet string containing both the IP address and subnet mask concatenated together:

import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.DEROctetString;
import java.net.InetAddress;

GeneralName name = subtree.getBase();

if (name.getTagNo() == GeneralName.iPAddress) {
    /* IP constraints are encoded as raw octets: [address | mask] */
    byte[] octets = DEROctetString.getInstance(
        name.getName().toASN1Primitive()).getOctets();

    /* Split octets in half: first half is IP, second half is subnet mask */
    byte[] ipBytes = Arrays.copyOf(octets, octets.length / 2);
    byte[] maskBytes = Arrays.copyOfRange(
        octets, octets.length / 2, octets.length);

    String ip = InetAddress.getByAddress(ipBytes).getHostAddress();
    String mask = InetAddress.getByAddress(maskBytes).getHostAddress();
    /* Result example: "192.168.1.0/255.255.255.0" */

} else {
    /* DNS names, email, URIs, etc. */
    String value = name.getName().toString();
}

wolfSSL Approach

wolfSSL handles the ASN.1 decoding internally, including IP address and subnet mask formatting. WolfSSLNameConstraints returns subtree entries as WolfSSLGeneralName objects whose getValue() method returns a pre-formatted string regardless of the GeneralName type.

Getting subtree values:

import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLNameConstraints;
import com.wolfssl.WolfSSLGeneralName;
import java.util.List;

WolfSSLCertificate wolfCert = null;
try {
    wolfCert = new WolfSSLCertificate(certificate.getEncoded());
    WolfSSLNameConstraints nc = wolfCert.getNameConstraints();

    if (nc != null) {
        List<WolfSSLGeneralName> permitted = nc.getPermittedSubtrees();
        List<WolfSSLGeneralName> excluded = nc.getExcludedSubtrees();
    }
} finally {
    if (wolfCert != null) {
        wolfCert.free();
    }
}

Extracting the value from a subtree entry:

/* getValue() returns a formatted string for all GeneralName types,
 * including IP address constraints with subnet mask. No manual
 * ASN.1 decoding needed. */
String value = generalName.getValue();

Authority Information Access (AIA) Extension Parsing

The AIA extension in X.509 certificates contains URIs for OCSP responders and CA Issuer locations (used to download intermediate certificates for chain building). With Bouncy Castle, parsing AIA requires unwrapping the extension bytes, casting through several ASN.1 types, iterating over AccessDescription entries, and filtering by access method OID. wolfSSL parses the AIA extension internally and provides direct access to the URI lists.

Bouncy Castle Approach

Parsing the AIA extension:

import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;

byte[] aiaValue =
    certificate.getExtensionValue(Extension.authorityInfoAccess.getId());

ASN1Primitive extensionValue =
    JcaX509ExtensionUtils.parseExtensionValue(aiaValue);

AuthorityInformationAccess aia =
    AuthorityInformationAccess.getInstance(extensionValue);

Extracting CA Issuer URIs:

for (AccessDescription ad : aia.getAccessDescriptions()) {
    if (ad.getAccessMethod().equals(
            X509ObjectIdentifiers.id_ad_caIssuers)) {
        GeneralName location = ad.getAccessLocation();
        String url = location.getName().toString();
        /* url contains a CA Issuer URI, e.g. "http://ca.example.com/ca.pem" */
    }
}

Extracting OCSP responder URIs:

for (AccessDescription ad : aia.getAccessDescriptions()) {
    if (ad.getAccessMethod().equals(
            X509ObjectIdentifiers.id_ad_ocsp)) {
        GeneralName location = ad.getAccessLocation();
        String url = location.getName().toString();
        /* url contains an OCSP responder URI */
    }
}

Downloading intermediate certificates from AIA CA Issuer URIs:

A common use of the AIA extension is downloading intermediate CA certificates for chain building. With Bouncy Castle, the application must iterate over AccessDescription entries and filter by OID to obtain the URIs:

for (AccessDescription ad : aia.getAccessDescriptions()) {
    GeneralName accessLocation = ad.getAccessLocation();

    if (accessLocation != null &&
        ad.getAccessMethod().equals(
            X509ObjectIdentifiers.id_ad_caIssuers)) {

        String url = accessLocation.getName().toString();
        /* download certificate from url for chain building */
    }
}

wolfSSL Approach

WolfSSLCertificate parses the AIA extension when the certificate is loaded and provides separate methods for retrieving CA Issuer URIs and OCSP responder URIs. No manual ASN.1 parsing or OID filtering is needed.

Extracting CA Issuer URIs:

import com.wolfssl.WolfSSLCertificate;

WolfSSLCertificate wolfCert = null;
try {
    wolfCert = new WolfSSLCertificate(certificate.getEncoded());

    String[] caIssuers = wolfCert.getCaIssuerUris();
    if (caIssuers != null) {
        for (String url : caIssuers) {
            /* url contains a CA Issuer URI, can be used to
             * download intermediate certs for chain building */
        }
    }
} finally {
    if (wolfCert != null) {
        wolfCert.free();
    }
}

Extracting OCSP responder URIs:

String[] ocspUris = wolfCert.getOcspUris();
if (ocspUris != null) {
    for (String url : ocspUris) {
        /* url contains an OCSP responder URI */
    }
}

Checking for AIA overflow:

The native wolfSSL library has an internal limit on the number of AIA URIs it can parse from a single certificate. Applications handling certificates with unusually large numbers of AIA entries should check for overflow:

int overflow = wolfCert.getAiaOverflow();
if (overflow == 1) {
    /* AIA URI list was truncated, certificate has more URIs
     * than the internal buffer can hold */
}

Subject Alternative Name (SAN) Parsing

Applications commonly need to extract Subject Alternative Name values from client certificates for identity mapping, authorization decisions, or display. With Bouncy Castle, SAN parsing (especially for non-String types like otherName) requires manual ASN.1 decoding using ASN1InputStream, ASN1Sequence, and ASN1TaggedObject. This is particularly problematic with certificates issued by Microsoft Active Directory, which encodes User Principal Names (UPNs) as otherName entries with a Microsoft-specific OID.

wolfSSL provides WolfSSLAltName objects that handle ASN.1 decoding internally, with explicit support for Microsoft AD UPN entries.

Bouncy Castle Approach

The standard JCE method X509Certificate.getSubjectAlternativeNames() returns SAN entries as a Collection<List<?>> where each entry is a list containing an integer type and a value. For simple string types (DNS, email, URI) the value is a String. However, for otherName entries (type 0), the value is returned as a raw byte[] requiring manual ASN.1 decoding:

Extracting string-based SAN types (DNS, email, URI):

import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;

Collection<List<?>> sans = certificate.getSubjectAlternativeNames();
if (sans != null) {
    for (List<?> san : sans) {
        int type = (Integer) san.get(0);
        if (type == 1 || type == 2 || type == 6) {
            /* rfc822Name (1), dNSName (2), URI (6) */
            String value = (String) san.get(1);
        }
    }
}

Decoding otherName entries (e.g., Microsoft AD UPN):

When the SAN value is an otherName (type 0), the JCE API returns the raw DER-encoded bytes. Bouncy Castle ASN.1 classes are typically used to decode these:

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;

for (List<?> san : sans) {
    int type = (Integer) san.get(0);

    if (type == 0 && san.get(1) instanceof byte[]) {
        /* otherName: requires ASN.1 decoding */
        ASN1InputStream decoder =
            new ASN1InputStream((byte[]) san.get(1));
        ASN1Encodable encoded = decoder.readObject();

        /* Navigate the ASN.1 structure:
         * SEQUENCE { OID, [0] EXPLICIT value } */
        ASN1Encodable value =
            ((ASN1Sequence) encoded).getObjectAt(1);
        String name =
            ((ASN1TaggedObject) value).getBaseObject().toString();
        decoder.close();
    }
}

This approach is fragile, the ASN.1 structure must be navigated by position, casting can fail for unexpected encodings, and different certificate issuers (particularly Microsoft AD) may encode values in subtly different ways.

wolfSSL Approach

WolfSSLCertificate.getSubjectAltNamesArray() returns WolfSSLAltName objects that provide type-safe access to all SAN types. The WolfSSLAltName class handles ASN.1 decoding internally and includes explicit support for Microsoft AD UPN detection.

Iterating over SAN entries by type:

import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLAltName;

WolfSSLCertificate wolfCert = null;
try {
    wolfCert = new WolfSSLCertificate(certificate.getEncoded());
    WolfSSLAltName[] sans = wolfCert.getSubjectAltNamesArray();

    if (sans != null) {
        for (WolfSSLAltName san : sans) {
            switch (san.getType()) {
                case WolfSSLAltName.TYPE_DNS_NAME:
                case WolfSSLAltName.TYPE_RFC822_NAME:
                case WolfSSLAltName.TYPE_URI:
                    String value = san.getStringValue();
                    break;
                case WolfSSLAltName.TYPE_IP_ADDRESS:
                    String ip = san.getIPAddressString();
                    break;
                case WolfSSLAltName.TYPE_OTHER_NAME:
                    String otherValue =
                        san.getOtherNameValueAsString();
                    break;
            }
        }
    }
} finally {
    if (wolfCert != null) {
        wolfCert.free();
    }
}

Detecting and extracting Microsoft AD UPN entries:

WolfSSLAltName provides isMicrosoftUPN() which checks for the Microsoft UPN OID (1.3.6.1.4.1.311.20.2.3) and getOtherNameValueAsString() which decodes the ASN.1 UTF8String value:

for (WolfSSLAltName san : sans) {
    if (san.isMicrosoftUPN()) {
        String upn = san.getOtherNameValueAsString();
        /* upn contains the User Principal Name, e.g. "user@domain.com" */
    }
}

For other otherName types, the OID and raw value can be inspected directly:

if (san.getType() == WolfSSLAltName.TYPE_OTHER_NAME) {
    String oid = san.getOtherNameOID();
    String value = san.getOtherNameValueAsString();
    byte[] rawDer = san.getOtherNameValue();
}

RSA Encryption, Decryption, and Signing

Applications using the Bouncy Castle crypto API for RSA operations (RSAEngine, PKCS1Encoding, RSADigestSigner, PublicKeyFactory, PrivateKeyFactory) can migrate to standard JCE APIs backed by wolfJCE. The Bouncy Castle API provides its own key representations (AsymmetricKeyParameter) and low-level cipher/signer objects, while the JCE equivalents use standard java.security and javax.crypto interfaces.

Bouncy Castle Approach

RSA encryption with PKCS#1 v1.5 padding:

import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PublicKeyFactory;

/* Convert raw public key bytes to BC key parameter type */
AsymmetricKeyParameter pubKeyParam = PublicKeyFactory.createKey(publicKeyBytes);

/* Initialize RSA engine with PKCS#1 padding for encryption */
PKCS1Encoding rsaEngine = new PKCS1Encoding(new RSAEngine());
rsaEngine.init(true, pubKeyParam);
byte[] ciphertext = rsaEngine.processBlock(plaintext, 0, plaintext.length);

RSA decryption:

import org.bouncycastle.crypto.util.PrivateKeyFactory;

/* Convert standard PrivateKey to BC key parameter type */
AsymmetricKeyParameter privKeyParam =
    PrivateKeyFactory.createKey(privateKey.getEncoded());

PKCS1Encoding rsaEngine = new PKCS1Encoding(new RSAEngine());
rsaEngine.init(false, privKeyParam);
byte[] plaintext = rsaEngine.processBlock(ciphertext, 0, ciphertext.length);

RSA signing with SHA-512:

import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.crypto.digests.SHA512Digest;

RSADigestSigner signer = new RSADigestSigner(new SHA512Digest());
signer.init(true, PrivateKeyFactory.createKey(privateKey.getEncoded()));
signer.update(data, 0, data.length);
byte[] signature = signer.generateSignature();

RSA signature verification:

import org.bouncycastle.crypto.digests.SHA256Digest;

/* Use SHA256Digest or SHA512Digest depending on the algorithm */
RSADigestSigner verifier = new RSADigestSigner(new SHA256Digest());
verifier.init(false, PublicKeyFactory.createKey(publicKey.getEncoded()));
verifier.update(data, 0, data.length);
boolean valid = verifier.verifySignature(signature);

wolfJCE Approach

All of these operations map directly to standard JCE classes. No proprietary key conversions or engine wrappers are needed - standard java.security.PublicKey and java.security.PrivateKey objects are used directly.

RSA encryption with PKCS#1 v1.5 padding:

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;

/* Convert raw public key bytes to standard PublicKey */
KeyFactory kf = KeyFactory.getInstance("RSA", "wolfJCE");
PublicKey pubKey = kf.generatePublic(new X509EncodedKeySpec(publicKeyBytes));

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "wolfJCE");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] ciphertext = cipher.doFinal(plaintext);

RSA decryption:

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "wolfJCE");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plaintext = cipher.doFinal(ciphertext);

RSA signing with SHA-512:

import java.security.Signature;

Signature sig = Signature.getInstance("SHA512withRSA", "wolfJCE");
sig.initSign(privateKey);
sig.update(data);
byte[] signature = sig.sign();

RSA signature verification:

/* Use "SHA256withRSA" or "SHA512withRSA" depending on the algorithm */
Signature sig = Signature.getInstance("SHA256withRSA", "wolfJCE");
sig.initVerify(publicKey);
sig.update(data);
boolean valid = sig.verify(signature);

Hex String Encoding and Decoding

Bouncy Castle provides hex encoding utilities in org.bouncycastle.util.encoders.Hex that are commonly used for logging, debugging, and formatting cryptographic values such as key fingerprints, hash digests, and certificate serial numbers. wolfSSL provides equivalent methods in the com.wolfssl.WolfCrypt class.

Bouncy Castle Approach

import org.bouncycastle.util.encoders.Hex;

/* Byte array to hex string */
String hexStr = Hex.toHexString(data);

/* Hex string to byte array */
byte[] decoded = Hex.decode(hexStr);

wolfSSL Approach

import com.wolfssl.wolfcrypt.WolfCrypt;

/* Byte array to hex string */
String hexStr = WolfCrypt.toHexString(data);

/* Hex string to byte array */
byte[] decoded = WolfCrypt.hexStringToByteArray(hexStr);

PEM to DER Conversion

Bouncy Castle PEMParser is commonly used to read PEM-encoded keys and certificates, extract the underlying ASN.1 structure, and obtain the DER encoding. This involves parsing the PEM object, casting to the appropriate Bouncy Castle type (PEMKeyPair, X509CertificateHolder, SubjectPublicKeyInfo, etc.), and then calling getEncoded().

wolfCrypt provides direct PEM-to-DER conversion methods in the WolfCrypt class that handle the PEM parsing and DER extraction in a single call.

Private Key

Bouncy Castle:

import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMKeyPair;

PEMParser parser = new PEMParser(new FileReader("key.pem"));
PEMKeyPair keyPair = (PEMKeyPair) parser.readObject();
byte[] der = keyPair.getPrivateKeyInfo().getEncoded();

wolfCrypt:

import com.wolfssl.wolfcrypt.WolfCrypt;
import java.nio.file.Files;
import java.nio.file.Paths;

byte[] pem = Files.readAllBytes(Paths.get("key.pem"));
byte[] der = WolfCrypt.keyPemToDer(pem, null);

Encrypted Private Key (PKCS#8)

Bouncy Castle requires building a decryptor provider and decrypting through the PKCS#8 layer before obtaining the DER encoding. wolfCrypt accepts the password directly.

Bouncy Castle:

import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;

PEMParser parser = new PEMParser(new FileReader("key.pem"));
PKCS8EncryptedPrivateKeyInfo encInfo =
    (PKCS8EncryptedPrivateKeyInfo) parser.readObject();

JceOpenSSLPKCS8DecryptorProviderBuilder builder =
    new JceOpenSSLPKCS8DecryptorProviderBuilder();
InputDecryptorProvider decryptor =
    builder.build("password".toCharArray());

PrivateKeyInfo keyInfo = encInfo.decryptPrivateKeyInfo(decryptor);
byte[] der = keyInfo.getEncoded();

wolfCrypt:

import com.wolfssl.wolfcrypt.WolfCrypt;

byte[] pem = Files.readAllBytes(Paths.get("key.pem"));
byte[] der = WolfCrypt.keyPemToDer(pem, "password");

Certificate

Bouncy Castle:

import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.cert.X509CertificateHolder;

PEMParser parser = new PEMParser(new FileReader("cert.pem"));
X509CertificateHolder holder =
    (X509CertificateHolder) parser.readObject();
byte[] der = holder.getEncoded();

wolfCrypt:

import com.wolfssl.wolfcrypt.WolfCrypt;

byte[] pem = Files.readAllBytes(Paths.get("cert.pem"));
byte[] der = WolfCrypt.certPemToDer(pem);

Public Key

Bouncy Castle:

import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;

PEMParser parser = new PEMParser(new FileReader("pubkey.pem"));
SubjectPublicKeyInfo pubKeyInfo =
    (SubjectPublicKeyInfo) parser.readObject();
byte[] der = pubKeyInfo.getEncoded();

wolfCrypt:

import com.wolfssl.wolfcrypt.WolfCrypt;

byte[] pem = Files.readAllBytes(Paths.get("pubkey.pem"));
byte[] der = WolfCrypt.pubKeyPemToDer(pem);

CRL Generation

Bouncy Castle provides CRL (Certificate Revocation List) generation through its X509v2CRLBuilder and JcaX509v2CRLBuilder classes in the org.bouncycastle.cert package. These require constructing a builder, adding revoked entries, configuring extensions, building with a ContentSigner, and then converting the result using JcaX509CRLConverter. wolfSSL JNI provides equivalent functionality through the WolfSSLCRL class, which wraps native wolfSSL CRL generation and offers a more direct API.

Bouncy Castle Approach

CRL generation in Bouncy Castle involves creating a JcaX509v2CRLBuilder (or the lower-level X509v2CRLBuilder), setting validity dates, adding revoked certificate entries with optional reason codes, signing via a JcaContentSignerBuilder, and converting the resulting X509CRLHolder to a standard java.security.cert.X509CRL object.

Creating and signing a CRL:

import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.asn1.x509.CRLReason;

import java.security.PrivateKey;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.math.BigInteger;
import java.util.Date;

/* Create CRL builder from issuer certificate */
Date now = new Date();
JcaX509v2CRLBuilder crlBuilder = new JcaX509v2CRLBuilder(issuerCert, now);

/* Set next update date */
Date nextUpdate = new Date(now.getTime() + (30L * 24 * 60 * 60 * 1000));
crlBuilder.setNextUpdate(nextUpdate);

/* Add revoked certificate entries */
crlBuilder.addCRLEntry(
    revokedCert.getSerialNumber(),   /* serial number */
    now,                              /* revocation date */
    CRLReason.keyCompromise);         /* revocation reason */

/* Sign the CRL */
X509CRLHolder crlHolder = crlBuilder.build(
    new JcaContentSignerBuilder("SHA256withRSA")
        .setProvider("BC")
        .build(issuerPrivateKey));

/* Convert to standard JCA X509CRL */
X509CRL crl = new JcaX509CRLConverter()
    .setProvider("BC")
    .getCRL(crlHolder);

/* Get DER encoding */
byte[] crlDer = crl.getEncoded();

wolfSSL Approach

The WolfSSLCRL class provides CRL generation through direct method calls without requiring separate builder, signer, and converter objects. The CRL is created, configured, signed, and exported using a single object.

Creating and signing a CRL:

import com.wolfssl.WolfSSLCRL;
import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLX509Name;
import com.wolfssl.WolfSSL;

import java.security.PrivateKey;
import java.util.Date;

/* Create a new empty CRL */
WolfSSLCRL crl = new WolfSSLCRL();

/* Set CRL version (1 = v2) */
crl.setVersion(1);

/* Set issuer name from a WolfSSLX509Name */
WolfSSLCertificate issuerWolfCert =
    new WolfSSLCertificate(issuerCert.getEncoded());
try {
    WolfSSLX509Name issuerName = issuerWolfCert.getSubjectName();
    crl.setIssuerName(issuerName);
} finally {
    issuerWolfCert.free();
}

/* Set validity dates */
Date now = new Date();
Date nextUpdate = new Date(now.getTime() + (30L * 24 * 60 * 60 * 1000));
crl.setLastUpdate(now);
crl.setNextUpdate(nextUpdate);

/* Add revoked certificate entries (by serial number) */
crl.addRevoked(revokedCert.getSerialNumber().toByteArray(), now);

/* Or add revoked entries directly from a DER-encoded certificate */
crl.addRevokedCert(revokedCert.getEncoded(), now);

try {
    /* Or add from a WolfSSLCertificate object */
    WolfSSLCertificate revokedWolfCert =
        new WolfSSLCertificate(revokedCert.getEncoded());
    try {
        crl.addRevokedCert(revokedWolfCert, now);
    } finally {
        revokedWolfCert.free();
    }

    /* Sign the CRL with RSA or EC private key */
    crl.sign(issuerPrivateKey, "SHA256");

    /* Get DER or PEM encoding */
    byte[] crlDer = crl.getDer();
    byte[] crlPem = crl.getPem();

    /* Or write directly to file (ASN1/DER or PEM format) */
    crl.writeToFile("/path/to/output.crl", WolfSSL.SSL_FILETYPE_ASN1);
    crl.writeToFile("/path/to/output.pem", WolfSSL.SSL_FILETYPE_PEM);
} finally {
    /* Free native resources when done */
    crl.free();
}

API Comparison Summary

The following table summarizes the mapping between Bouncy Castle CRL generation classes and the wolfSSL JNI equivalent methods:

Operation Bouncy Castle wolfSSL (WolfSSLCRL)
Create CRL builder new JcaX509v2CRLBuilder(issuer, date) new WolfSSLCRL()
Set version Set implicitly (v2 builder) setVersion(int)
Set issuer Set via constructor setIssuerName(WolfSSLX509Name)
Set lastUpdate/thisUpdate Set via constructor (thisUpdate) setLastUpdate(Date)
Set next update setNextUpdate(Date) setNextUpdate(Date)
Add revoked entry addCRLEntry(BigInteger, Date, int) addRevoked(byte[], Date)
Add revoked cert N/A (serial number only) addRevokedCert(byte[], Date) or addRevokedCert(WolfSSLCertificate, Date)
Sign CRL build(ContentSigner) sign(PrivateKey, String)
Get DER encoding X509CRLHolder.getEncoded() getDer()
Get PEM encoding Manual conversion needed getPem()
Write to file Manual I/O needed writeToFile(String, int)
Free resources Garbage collected free()

Note: wolfSSL CRL generation requires native wolfSSL to be compiled with CRL generation support enabled. Applications can check for this at runtime using WolfSSL.CrlGenerationEnabled().

X.509 Certificate Generation

Applications that generate and sign X.509v3 certificate chains using the Bouncy Castle X509v3CertificateBuilder API (or its JCA wrapper JcaX509v3CertificateBuilder) can migrate to the WolfSSLCertificate class in wolfSSL JNI. Bouncy Castle certificate generation involves constructing a builder, adding extensions one at a time through X509ExtensionUtils helpers, signing with a JcaContentSignerBuilder, and converting the result with JcaX509CertificateConverter. wolfSSL provides direct setter methods on the WolfSSLCertificate object for each extension.

Bouncy Castle Approach

Bouncy Castle certificate generation requires assembling several objects: a certificate builder, extension utilities for computing key identifiers, and a content signer for the final signature.

Creating and signing a certificate with standard extensions:

import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.security.auth.x500.X500Principal;

/* Set certificate validity period */
Date notBefore = new Date();
Date notAfter = new Date(notBefore.getTime() + (365L * 24 * 60 * 60 * 1000));

/* Create certificate builder */
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
    issuerCert,                                      /* issuer */
    BigInteger.valueOf(1),                           /* serial */
    notBefore,                                       /* validity start */
    notAfter,                                        /* validity end */
    new X500Principal("CN=Example"),                 /* subject */
    subjectKeyPair.getPublic());                     /* subject public key */

/* Add extensions */
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();

certBuilder.addExtension(Extension.subjectKeyIdentifier, false,
    extUtils.createSubjectKeyIdentifier(subjectKeyPair.getPublic()));

certBuilder.addExtension(Extension.authorityKeyIdentifier, false,
    extUtils.createAuthorityKeyIdentifier(issuerCert));

certBuilder.addExtension(Extension.basicConstraints, true,
    new BasicConstraints(false));

certBuilder.addExtension(Extension.keyUsage, true,
    new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));

certBuilder.addExtension(Extension.extendedKeyUsage, false,
    new ExtendedKeyUsage(new KeyPurposeId[] {
        KeyPurposeId.id_kp_serverAuth,
        KeyPurposeId.id_kp_clientAuth
    }));

certBuilder.addExtension(Extension.subjectAlternativeName, false,
    new GeneralNames(new GeneralName(GeneralName.iPAddress, "192.168.1.1")));

/* Add CRL Distribution Points */
DistributionPoint dp = new DistributionPoint(
    new DistributionPointName(new GeneralNames(
        new GeneralName(GeneralName.uniformResourceIdentifier,
            "http://crl.example.com/ca.crl"))),
    null, null);
certBuilder.addExtension(Extension.cRLDistributionPoints, false,
    new CRLDistPoint(new DistributionPoint[] { dp }));

/* Sign and convert */
X509Certificate cert = new JcaX509CertificateConverter()
    .setProvider("BC")
    .getCertificate(certBuilder.build(
        new JcaContentSignerBuilder("SHA256withRSA")
            .setProvider("BC")
            .build(issuerPrivateKey)));

wolfSSL Approach

WolfSSLCertificate provides setter methods for each extension and handles ASN.1 encoding internally. The certificate is created as an empty object, configured through method calls, and signed in place.

Creating and signing a certificate with standard extensions:

import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLX509Name;
import com.wolfssl.WolfSSL;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Date;

/* Load issuer certificate for setting issuer name and AKID */
WolfSSLCertificate issuerWolfCert = null;
WolfSSLCertificate cert = null;
try {
    issuerWolfCert =
        new WolfSSLCertificate(issuerCert.getEncoded());

    /* Create empty certificate for generation */
    cert = new WolfSSLCertificate();
    /* Set subject and issuer names */
    WolfSSLX509Name subject = new WolfSSLX509Name();
    subject.setCommonName("Example");
    cert.setSubjectName(subject);
    cert.setIssuerName(issuerWolfCert);

    /* Set serial number and validity period */
    cert.setSerialNumber(BigInteger.valueOf(1));
    Date notBefore = new Date();
    Date notAfter =
        new Date(notBefore.getTime() + (365L * 24 * 60 * 60 * 1000));
    cert.setNotBefore(notBefore);
    cert.setNotAfter(notAfter);

    /* Set subject public key */
    cert.setPublicKey(subjectKeyPair.getPublic());

    /* Subject Key Identifier (from the certificate's own public key) */
    cert.setSubjectKeyIdEx();

    /* Authority Key Identifier (from the issuer certificate) */
    cert.setAuthorityKeyIdEx(issuerWolfCert);

    /* Basic Constraints (non-CA) */
    cert.addExtension(WolfSSL.NID_basic_constraints, false, false);

    /* Key Usage */
    cert.addExtension(WolfSSL.NID_key_usage,
        "digitalSignature,keyEncipherment", true);

    /* Extended Key Usage */
    cert.addExtension(WolfSSL.NID_ext_key_usage,
        "serverAuth,clientAuth", false);

    /* Subject Alternative Name (IP address) */
    cert.addAltNameIP("192.168.1.1");

    /* CRL Distribution Point */
    cert.addCrlDistPoint("http://crl.example.com/ca.crl", false);

    /* Netscape Certificate Type (if needed) */
    cert.setNsCertType(WolfSSL.SSL_CLIENT | WolfSSL.SSL_SERVER);

    /* Sign with issuer private key */
    cert.signCert(issuerPrivateKey, "SHA256");

    /* Get DER or PEM encoding of the signed certificate */
    byte[] certDer = cert.getDer();
    byte[] certPem = cert.getPem();

} finally {
    if (cert != null) {
        cert.free();
    }
    if (issuerWolfCert != null) {
        issuerWolfCert.free();
    }
}

API Comparison Summary

Operation Bouncy Castle wolfSSL (WolfSSLCertificate)
Create builder new JcaX509v3CertificateBuilder(...) new WolfSSLCertificate()
Set subject name Set via constructor setSubjectName(WolfSSLX509Name)
Set issuer name Set via constructor setIssuerName(WolfSSLCertificate) or setIssuerName(X509Certificate)
Set serial number Set via constructor setSerialNumber(BigInteger)
Set validity dates Set via constructor setNotBefore(Date) / setNotAfter(Date)
Set public key Set via constructor setPublicKey(PublicKey)
Subject Key Identifier extUtils.createSubjectKeyIdentifier(...) setSubjectKeyIdEx() or setSubjectKeyId(byte[])
Authority Key Identifier extUtils.createAuthorityKeyIdentifier(...) setAuthorityKeyIdEx(WolfSSLCertificate) or setAuthorityKeyId(byte[])
Basic Constraints new BasicConstraints(boolean) addExtension(NID_basic_constraints, boolean, boolean)
Key Usage new KeyUsage(int) addExtension(NID_key_usage, String, boolean)
Extended Key Usage new ExtendedKeyUsage(KeyPurposeId[]) addExtension(NID_ext_key_usage, String, boolean)
SAN (IP address) new GeneralName(iPAddress, ...) addAltNameIP(String)
SAN (other types) new GeneralName(type, ...) addAltName(String, int)
CRL Distribution Points new CRLDistPoint(DistributionPoint[]) addCrlDistPoint(String, boolean) or setCrlDistPoints(byte[])
Netscape Cert Type addExtension(netscapeCertType, ...) setNsCertType(int)
Sign certificate JcaContentSignerBuilder + build() signCert(PrivateKey, String)
Convert to JCA type JcaX509CertificateConverter.getCertificate() N/A (native wrapper)
Get DER encoding X509CertificateHolder.getEncoded() getDer()
Get PEM encoding Manual conversion needed getPem()
Free resources Garbage collected free()

Certificate Verification with CertManager

Bouncy Castle provides X509CertificateHolder and related classes for certificate verification and chain validation. wolfSSL provides the WolfSSLCertManager class, which wraps the native wolfSSL certificate manager and supports loading trusted CA certificates, verifying certificates against those CAs, and checking OCSP responses.

Bouncy Castle Approach

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;

import java.security.cert.CertificateFactory;
import java.security.cert.CertPathValidator;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.KeyStore;

/* Load trusted CA into a KeyStore */
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", caCert);

/* Build PKIX parameters from trust anchors */
PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(false);

/* Validate certificate chain */
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
CertPathValidator validator = CertPathValidator.getInstance("PKIX", "BC");
validator.validate(cf.generateCertPath(certChain), params);

wolfSSL Approach

WolfSSLCertManager loads trusted CAs and verifies certificates in a single method call without requiring CertPath or PKIXParameters construction.

import com.wolfssl.WolfSSLCertManager;
import com.wolfssl.WolfSSL;

WolfSSLCertManager cm = new WolfSSLCertManager();
try {
    /* Load trusted CA from file */
    cm.CertManagerLoadCA("/path/to/ca-cert.pem", null);

    /* Or load from byte array */
    cm.CertManagerLoadCABuffer(caDer, caDer.length, WolfSSL.SSL_FILETYPE_ASN1);

    /* Or load all CAs from a KeyStore */
    cm.CertManagerLoadCAKeyStore(trustStore);

    /* Verify a certificate against loaded CAs */
    int ret = cm.CertManagerVerifyBuffer(certDer, certDer.length,
        WolfSSL.SSL_FILETYPE_ASN1);
    if (ret == WolfSSL.SSL_SUCCESS) {
        /* Certificate verified successfully */
    }

    /* Check OCSP response for revocation status */
    cm.CertManagerCheckOCSPResponse(ocspResponse, certDer, issuerDer);

    /* Unload CAs when done */
    cm.CertManagerUnloadCAs();

} finally {
    cm.free();
}

RSA Miller-Rabin Primality Test Configuration

Bouncy Castle allows configuring the number of Miller-Rabin primality test rounds used during RSA key generation via a security property:

Security.setProperty("org.bouncycastle.rsa.max_mr_tests", "0");

This property has no equivalent in wolfJCE. wolfCrypt's RSA implementation uses a fixed number of Miller-Rabin rounds that cannot be adjusted at runtime. When using wolfCrypt FIPS, the primality testing parameters are part of the validated module and must not be modified in order to remain compliant with FIPS 140-3 requirements.

Any code that sets this Bouncy Castle property should simply be removed during migration.