GordianCertUtils.java
/*
* GordianKnot: Security Suite
* Copyright 2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.core.cert;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUsage;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUse;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigest;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianIOException;
import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.TBSCertificate;
import java.io.IOException;
import java.math.BigInteger;
/**
* Certificate utilities.
*/
public final class GordianCertUtils {
/**
* Private constructor.
*/
private GordianCertUtils() {
}
/**
* Create New Serial#.
*
* @return the new serial#
*/
static BigInteger newSerialNo() {
final long myNow = System.currentTimeMillis();
return BigInteger.valueOf(myNow);
}
/**
* Determine usage.
*
* @param pExtensions the extensions.
* @return the usage
*/
public static GordianKeyPairUsage determineUsage(final Extensions pExtensions) {
/* Access details */
final KeyUsage myUsage = KeyUsage.fromExtensions(pExtensions);
final BasicConstraints myConstraint = BasicConstraints.fromExtensions(pExtensions);
final GordianKeyPairUsage myResult = new GordianKeyPairUsage();
/* Check for CERTIFICATE */
final boolean isCA = myConstraint != null && myConstraint.isCA();
if (isCA && checkUsage(myUsage, KeyUsage.keyCertSign)) {
myResult.addUse(GordianKeyPairUse.CERTIFICATE);
}
/* Check for signer. */
if (checkUsage(myUsage, KeyUsage.digitalSignature)) {
myResult.addUse(GordianKeyPairUse.SIGNATURE);
}
/* Check for nonRepudiation. */
if (checkUsage(myUsage, KeyUsage.nonRepudiation)) {
myResult.addUse(GordianKeyPairUse.NONREPUDIATION);
}
/* Check for keyAgreement. */
if (checkUsage(myUsage, KeyUsage.keyAgreement)) {
myResult.addUse(GordianKeyPairUse.AGREEMENT);
}
/* Check for keyEncryption. */
if (checkUsage(myUsage, KeyUsage.keyEncipherment)) {
myResult.addUse(GordianKeyPairUse.KEYENCRYPT);
}
/* Check for dataEncryption. */
if (checkUsage(myUsage, KeyUsage.dataEncipherment)) {
myResult.addUse(GordianKeyPairUse.DATAENCRYPT);
}
/* Check for encipherOnly. */
if (checkUsage(myUsage, KeyUsage.encipherOnly)) {
myResult.addUse(GordianKeyPairUse.ENCRYPTONLY);
}
/* Check for decipherOnly. */
if (checkUsage(myUsage, KeyUsage.decipherOnly)) {
myResult.addUse(GordianKeyPairUse.DECRYPTONLY);
}
/* Return the result */
return myResult;
}
/**
* Determine subjectId.
*
* @param pCertificate the certificate.
* @return the id
*/
static byte[] determineSubjectId(final TBSCertificate pCertificate) {
/* Access details */
final Extensions myExtensions = pCertificate.getExtensions();
final Extension mySubjectId = myExtensions.getExtension(Extension.subjectKeyIdentifier);
if (mySubjectId != null) {
return mySubjectId.getExtnValue().getOctets();
}
final ASN1BitString myId = pCertificate.getSubjectUniqueId();
return myId == null ? null : myId.getOctets();
}
/**
* Determine issuerId.
*
* @param pCertificate the certificate.
* @return the id
*/
static byte[] determineIssuerId(final TBSCertificate pCertificate) {
/* Access details */
final Extensions myExtensions = pCertificate.getExtensions();
final Extension myIssuerId = myExtensions.getExtension(Extension.authorityKeyIdentifier);
if (myIssuerId != null) {
return myIssuerId.getExtnValue().getOctets();
}
final ASN1BitString myId = pCertificate.getIssuerUniqueId();
return myId == null ? null : myId.getOctets();
}
/**
* Check for usage.
*
* @param pUsage the usage control
* @param pRequired the required usage
* @return true/false
*/
private static boolean checkUsage(final KeyUsage pUsage,
final int pRequired) {
return pUsage == null || pUsage.hasUsages(pRequired);
}
/**
* Create extensions for tbsCertificate.
*
* @param pCAStatus the CA status
* @param pUsage the keyPair usage
* @param pSubjectId the subjectId
* @param pIssuerId the issuerId (or null)
* @return the extensions
* @throws GordianException on error
*/
static Extensions createExtensions(final GordianCAStatus pCAStatus,
final GordianKeyPairUsage pUsage,
final byte[] pSubjectId,
final byte[] pIssuerId) throws GordianException {
/* Protect against exceptions */
try {
/* Create extensions for the certificate */
final ExtensionsGenerator myGenerator = new ExtensionsGenerator();
myGenerator.addExtension(Extension.keyUsage, true, pUsage.getKeyUsage());
myGenerator.addExtension(Extension.subjectKeyIdentifier, true, pSubjectId);
if (pIssuerId != null) {
myGenerator.addExtension(Extension.authorityKeyIdentifier, true, pIssuerId);
}
pCAStatus.createExtensions(myGenerator);
return myGenerator.generate();
} catch (IOException e) {
throw new GordianIOException("Failed to create extensions", e);
}
}
/**
* Create extensions.
*
* @param pUsage the usage
* @return the extensions
* @throws GordianException on error
*/
static Extensions createExtensions(final GordianKeyPairUsage pUsage) throws GordianException {
/* Protect against exceptions */
try {
final ExtensionsGenerator myGenerator = new ExtensionsGenerator();
myGenerator.addExtension(Extension.keyUsage, true, pUsage.getKeyUsage());
return myGenerator.generate();
} catch (IOException e) {
throw new GordianIOException("Failed to create extensions", e);
}
}
/**
* Create the keyId.
*
* @param pFactory the factory
* @param pEncodedPublicKey the publicKey
* @return the keyId
* @throws GordianException on error
*/
static byte[] createKeyId(final GordianFactory pFactory,
final byte[] pEncodedPublicKey) throws GordianException {
/* Build the hash */
final GordianDigestSpec mySpec = GordianDigestSpecBuilder.sha3(GordianLength.LEN_256);
final GordianDigestFactory myDigests = pFactory.getDigestFactory();
final GordianDigest myDigest = myDigests.createDigest(mySpec);
myDigest.update(pEncodedPublicKey);
/* Create the keyId */
return myDigest.finish();
}
}