GordianCRMBuilder.java
/*
* GordianKnot: Security Suite
* Copyright 2012-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.keystore;
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.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.GordianAsyncFactory;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairFactory;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairGenerator;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.keystore.GordianKeyStoreEntry.GordianKeyStorePair;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMac;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignParams;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignature;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignatureSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianASN1Util;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianRandomSource;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cert.GordianCoreCertificate;
import io.github.tonywasher.joceanus.gordianknot.impl.core.digest.GordianCoreDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianIOException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianCRMEncryptor.GordianCRMResult;
import io.github.tonywasher.joceanus.gordianknot.impl.core.sign.GordianCoreSignatureFactory;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.BERSet;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cmp.PBMParameter;
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import org.bouncycastle.asn1.cms.EnvelopedData;
import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
import org.bouncycastle.asn1.crmf.CertReqMsg;
import org.bouncycastle.asn1.crmf.CertRequest;
import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
import org.bouncycastle.asn1.crmf.PKMACValue;
import org.bouncycastle.asn1.crmf.POPOPrivKey;
import org.bouncycastle.asn1.crmf.POPOSigningKey;
import org.bouncycastle.asn1.crmf.ProofOfPossession;
import org.bouncycastle.asn1.crmf.SubsequentMessage;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import java.io.IOException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* CRM Builder.
*/
public class GordianCRMBuilder {
/**
* AttrOID branch.
*/
public static final ASN1ObjectIdentifier ATTROID = GordianASN1Util.EXTOID.branch("4");
/**
* AttrOID branch.
*/
public static final ASN1ObjectIdentifier MACVALUEATTROID = ATTROID.branch("1");
/**
* The # of hash iterations .
*/
private static final int PBM_ITERATIONS = 10000;
/**
* The gateway.
*/
private final GordianBaseKeyStoreGateway theGateway;
/**
* Constructor.
*
* @param pGateway the gateway
*/
GordianCRMBuilder(final GordianBaseKeyStoreGateway pGateway) {
theGateway = pGateway;
}
/**
* Create a Certificate request.
*
* @param pKeyPair the keyStore entry
* @param pRequestId the reqId
* @return the certificate request message
* @throws GordianException on error
*/
public CertReqMsg createCertificateRequest(final GordianKeyStorePair pKeyPair,
final int pRequestId) throws GordianException {
/* Access the certificate */
final GordianCoreCertificate myCert = (GordianCoreCertificate) pKeyPair.getCertificateChain().get(0);
/* Create the Certificate request */
final CertRequest myCertReq = createCertRequest(myCert, pRequestId);
/* Create the ProofOfPossession */
final ProofOfPossession myProof = createKeyPairProofOfPossession(pKeyPair, myCert, myCertReq);
/* Create control if necessary */
AttributeTypeAndValue[] myAttrs = null;
final X500Name myName = myCert.getSubjectName();
final byte[] myMACSecret = theGateway.getMACSecret(myName);
if (myMACSecret != null) {
/* Create the PKMACValue Control */
final PKMACValue myMACValue = createPKMACValue(myMACSecret, myCertReq.getCertTemplate().getPublicKey());
myAttrs = new AttributeTypeAndValue[]{new AttributeTypeAndValue(MACVALUEATTROID, myMACValue)};
}
/* Create a CRMF */
return new CertReqMsg(myCertReq, myProof, myAttrs);
}
/**
* Create a Certificate request.
*
* @param pCertificate the local certificate
* @param pRequestId the reqId
* @return the certificate request
* @throws GordianException on error
*/
private static CertRequest createCertRequest(final GordianCoreCertificate pCertificate,
final int pRequestId) throws GordianException {
/* Protect against exceptions */
try {
/* Create the Certificate template Builder */
final CertTemplateBuilder myBuilder = new CertTemplateBuilder();
/* Record certificate name */
myBuilder.setSubject(pCertificate.getSubject().getName());
/* Record certificate publicKey */
final X509EncodedKeySpec myX509 = pCertificate.getX509KeySpec();
final SubjectPublicKeyInfo myPublicInfo = SubjectPublicKeyInfo.getInstance(myX509.getEncoded());
myBuilder.setPublicKey(myPublicInfo);
/* record extensions */
final ExtensionsGenerator myGenerator = new ExtensionsGenerator();
myGenerator.addExtension(Extension.keyUsage, true, pCertificate.getUsage().getKeyUsage());
myGenerator.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
myBuilder.setExtensions(myGenerator.generate());
/* Create the Certificate request */
return new CertRequest(pRequestId, myBuilder.build(), null);
} catch (IOException e) {
throw new GordianIOException("Failed to create Certificate request", e);
}
}
/**
* Create KeyPair Proof of Possession.
*
* @param pKeyPair the keyStore entry
* @param pCertificate the local certificate
* @param pCertRequest the certificate request
* @return the proof of possession
* @throws GordianException on error
*/
private ProofOfPossession createKeyPairProofOfPossession(final GordianKeyStorePair pKeyPair,
final GordianCoreCertificate pCertificate,
final CertRequest pCertRequest) throws GordianException {
/* Try to send a signed proof */
final GordianKeyPair myKeyPair = pKeyPair.getKeyPair();
final GordianKeyPairSpec mySpec = myKeyPair.getKeyPairSpec();
final GordianSignatureSpec mySignSpec = theGateway.getFactory().getAsyncFactory().getSignatureFactory().defaultForKeyPair(mySpec);
if (mySignSpec != null) {
return createKeyPairSignedProof(myKeyPair, mySignSpec, pCertRequest);
}
/* Send encrypted key via targeted encryption or request encrypted certificate */
final GordianCoreCertificate myTarget = theGateway.getTarget();
return myTarget != null
? createTargetedProofOfPossession(myKeyPair, pCertificate)
: new ProofOfPossession(ProofOfPossession.TYPE_KEY_ENCIPHERMENT, new POPOPrivKey(SubsequentMessage.encrCert));
}
/**
* Create Targeted Proof of Possession.
*
* @param pKeyPair the keyPair
* @param pCertificate the local certificate
* @return the proof of possession
* @throws GordianException on error
*/
private ProofOfPossession createTargetedProofOfPossession(final GordianKeyPair pKeyPair,
final GordianCoreCertificate pCertificate) throws GordianException {
/* Obtain the PKCS8Encoding of the private key */
final GordianKeyPairFactory myFactory = theGateway.getFactory().getAsyncFactory().getKeyPairFactory();
final GordianKeyPairSpec mySpec = pKeyPair.getKeyPairSpec();
final GordianKeyPairGenerator myGenerator = myFactory.getKeyPairGenerator(mySpec);
final PKCS8EncodedKeySpec myPKCS8Encoding = myGenerator.getPKCS8Encoding(pKeyPair);
/* Prepare for encryption */
final GordianCRMEncryptor myEncryptor = theGateway.getEncryptor();
final GordianCoreCertificate myTarget = theGateway.getTarget();
final GordianCRMResult myResult = myEncryptor.prepareForEncryption(myTarget);
/* Derive the keySet from the key */
final GordianKeySet myKeySet = myResult.getKeySet();
/* Create the encrypted data */
final EncryptedContentInfo myInfo = GordianCRMEncryptor.buildEncryptedContentInfo(myKeySet, myPKCS8Encoding, pCertificate);
/* Create the Proof of possession */
final EnvelopedData myEnvData = new EnvelopedData(null, new BERSet(myResult.getRecipient()), myInfo, (BERSet) null);
return new ProofOfPossession(ProofOfPossession.TYPE_KEY_ENCIPHERMENT, new XPOPOPrivKey(myEnvData));
}
/**
* Create a Signed KeyPair Proof of Possession.
*
* @param pKeyPair the keyPair
* @param pSignSpec the signatureSpec
* @param pCertRequest the certificate request
* @return the proof of possession
* @throws GordianException on error
*/
ProofOfPossession createKeyPairSignedProof(final GordianKeyPair pKeyPair,
final GordianSignatureSpec pSignSpec,
final CertRequest pCertRequest) throws GordianException {
/* Create the signer */
final GordianAsyncFactory myFactory = theGateway.getFactory().getAsyncFactory();
final GordianCoreSignatureFactory mySignFactory = (GordianCoreSignatureFactory) myFactory.getSignatureFactory();
final GordianSignature mySigner = mySignFactory.createSigner(pSignSpec);
final AlgorithmIdentifier myAlgId = mySignFactory.getIdentifierForSpecAndKeyPair(pSignSpec, pKeyPair);
/* Build the signed proof */
return createSignedProof(pKeyPair, myAlgId, mySigner, pCertRequest);
}
/**
* Create a Signed Proof of Possession.
*
* @param pKeyPair the keyPair
* @param pAlgId the algorithmId
* @param pSigner the signer
* @param pCertRequest the certificate request
* @return the proof of possession
* @throws GordianException on error
*/
private static ProofOfPossession createSignedProof(final GordianKeyPair pKeyPair,
final AlgorithmIdentifier pAlgId,
final GordianSignature pSigner,
final CertRequest pCertRequest) throws GordianException {
/* Protect against exceptions */
try {
/* Create the signature */
pSigner.initForSigning(GordianSignParams.keyPair(pKeyPair));
pSigner.update(pCertRequest.getEncoded());
final byte[] mySignature = pSigner.sign();
/* Build the signing key */
final POPOSigningKey myKey = new POPOSigningKey(null, pAlgId, new DERBitString(mySignature));
return new ProofOfPossession(myKey);
} catch (IOException e) {
throw new GordianIOException("Failed to create Signed Proof of Possession", e);
}
}
/**
* Extended POPOPrivKey to allow encryptedKey.
*/
static class XPOPOPrivKey extends POPOPrivKey {
/**
* The encrypted key.
*/
private final EnvelopedData theKey;
/**
* Constructor.
*
* @param pEncryptedKey the encryptedKey.
*/
XPOPOPrivKey(final EnvelopedData pEncryptedKey) {
super((PKMACValue) null);
theKey = pEncryptedKey;
}
@Override
public ASN1Primitive toASN1Primitive() {
return new DERTaggedObject(false, encryptedKey, theKey);
}
}
/**
* Create MACValue.
*
* @param pSecret the secret
* @param pData the data to calculate over
* @return the MACValue
* @throws GordianException on error
*/
public PKMACValue createPKMACValue(final byte[] pSecret,
final ASN1Object pData) throws GordianException {
final PBMParameter myParams = generatePBMParameters();
return calculatePKMacValue(theGateway.getFactory(), pSecret, pData, myParams);
}
/**
* Check PKMACValue.
*
* @param pSecret the secret
* @param pData the data to calculate over
* @param pMACValue the supplied MACValue
* @throws GordianException on error
*/
public void checkPKMACValue(final byte[] pSecret,
final ASN1Object pData,
final PKMACValue pMACValue) throws GordianException {
final PBMParameter myParams = PBMParameter.getInstance(pMACValue.getAlgId().getParameters());
final PKMACValue myMACValue = calculatePKMacValue(theGateway.getFactory(), pSecret, pData, myParams);
if (!pMACValue.equals(myMACValue)) {
throw new GordianDataException("Invalid PKMacValue");
}
}
/**
* Create PBMParameters.
*
* @return the PBMParameters
*/
private PBMParameter generatePBMParameters() {
/* Create the salt value */
final byte[] mySalt = new byte[GordianLength.LEN_256.getByteLength()];
final GordianRandomSource mySource = theGateway.getFactory().getRandomSource();
mySource.getRandom().nextBytes(mySalt);
/* Access algorithm ids */
final AlgorithmIdentifier myHashId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);
final AlgorithmIdentifier myMacId = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA256, DERNull.INSTANCE);
/* Create the PBMParameter */
return new PBMParameter(mySalt, myHashId, PBM_ITERATIONS, myMacId);
}
/**
* Calculate PKMacValue.
*
* @param pFactory the factory
* @param pSecret the secret value
* @param pObject the object
* @param pParams the PBM Parameters
* @return the PKMacValue
* @throws GordianException on error
*/
private static PKMACValue calculatePKMacValue(final GordianBaseFactory pFactory,
final byte[] pSecret,
final ASN1Object pObject,
final PBMParameter pParams) throws GordianException {
/* Protect against exceptions */
try {
/* Create the digest */
final GordianCoreDigestFactory myDigests = (GordianCoreDigestFactory) pFactory.getDigestFactory();
final GordianDigestSpec myDigestSpec = myDigests.getDigestSpecForIdentifier(pParams.getOwf());
final GordianDigest myDigest = myDigests.createDigest(myDigestSpec);
/* Run through the first iteration */
myDigest.update(pSecret);
myDigest.update(pParams.getSalt().getOctets());
byte[] myKey = new byte[myDigest.getDigestSize()];
myKey = myDigest.finish(myKey);
/* Loop through the remaining iterations */
final int numIterations = pParams.getIterationCount().intValueExact() - 1;
for (int i = 0; i < numIterations; i++) {
myKey = myDigest.finish(myKey);
}
/* Create the mac */
final GordianMacFactory myMacs = pFactory.getMacFactory();
final GordianMacSpec myMacSpec = (GordianMacSpec) pFactory.getKeySpecForIdentifier(pParams.getMac());
final GordianMac myMac = myMacs.createMac(myMacSpec);
myMac.initKeyBytes(myKey);
/* Create the result */
myMac.update(pObject.toASN1Primitive().getEncoded());
final byte[] myResult = myMac.finish();
return new PKMACValue(pParams, new DERBitString(myResult));
/* Handle exceptions */
} catch (IOException e) {
throw new GordianIOException("Failed to calculate PKMACValue", e);
}
}
/**
* Calculate AckValue.
*
* @param pCertificate the certificate
* @return the AckValue
* @throws GordianException on error
*/
public byte[] calculateAckValue(final GordianCoreCertificate pCertificate) throws GordianException {
/* Access the MACSecret */
final X500Name mySubject = pCertificate.getSubjectName();
final byte[] myMACSecret = theGateway.getMACSecret(mySubject);
/* Create the digest */
final GordianBaseFactory myFactory = theGateway.getFactory();
final GordianDigestFactory myDigests = myFactory.getDigestFactory();
final GordianDigest myDigest = myDigests.createDigest(GordianDigestSpecBuilder.sha2(GordianLength.LEN_256));
/* Calculate the digest */
if (myMACSecret != null) {
myDigest.update(myMACSecret);
}
myDigest.update(pCertificate.getEncoded());
return myDigest.finish();
}
}