blob: 5e14f1bfa845fcc1d75733f526558d15ef75c135 [file] [log] [blame]
package org.bouncycastle.cms.jcajce;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
import org.bouncycastle.asn1.cms.OriginatorPublicKey;
import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
import org.bouncycastle.asn1.cryptopro.Gost2814789EncryptedKey;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KeyAgreeRecipientInfoGenerator;
import org.bouncycastle.jcajce.spec.GOST28147WrapParameterSpec;
import org.bouncycastle.jcajce.spec.MQVParameterSpec;
import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
import org.bouncycastle.operator.DefaultSecretKeySizeProvider;
import org.bouncycastle.operator.GenericKey;
import org.bouncycastle.operator.SecretKeySizeProvider;
import org.bouncycastle.util.Arrays;
public class JceKeyAgreeRecipientInfoGenerator
extends KeyAgreeRecipientInfoGenerator
{
private SecretKeySizeProvider keySizeProvider = new DefaultSecretKeySizeProvider();
private List recipientIDs = new ArrayList();
private List recipientKeys = new ArrayList();
private PublicKey senderPublicKey;
private PrivateKey senderPrivateKey;
private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
private SecureRandom random;
private KeyPair ephemeralKP;
private byte[] userKeyingMaterial;
public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID)
{
super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID);
this.senderPublicKey = senderPublicKey;
this.senderPrivateKey = senderPrivateKey;
}
public JceKeyAgreeRecipientInfoGenerator setUserKeyingMaterial(byte[] userKeyingMaterial)
{
this.userKeyingMaterial = Arrays.clone(userKeyingMaterial);
return this;
}
public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider)
{
this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
return this;
}
public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName)
{
this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
return this;
}
public JceKeyAgreeRecipientInfoGenerator setSecureRandom(SecureRandom random)
{
this.random = random;
return this;
}
/**
* Add a recipient based on the passed in certificate's public key and its issuer and serial number.
*
* @param recipientCert recipient's certificate
* @return the current instance.
* @throws CertificateEncodingException if the necessary data cannot be extracted from the certificate.
*/
public JceKeyAgreeRecipientInfoGenerator addRecipient(X509Certificate recipientCert)
throws CertificateEncodingException
{
recipientIDs.add(new KeyAgreeRecipientIdentifier(CMSUtils.getIssuerAndSerialNumber(recipientCert)));
recipientKeys.add(recipientCert.getPublicKey());
return this;
}
/**
* Add a recipient identified by the passed in subjectKeyID and the for the passed in public key.
*
* @param subjectKeyID identifier actual recipient will use to match the private key.
* @param publicKey the public key for encrypting the secret key.
* @return the current instance.
* @throws CertificateEncodingException
*/
public JceKeyAgreeRecipientInfoGenerator addRecipient(byte[] subjectKeyID, PublicKey publicKey)
throws CertificateEncodingException
{
recipientIDs.add(new KeyAgreeRecipientIdentifier(new RecipientKeyIdentifier(subjectKeyID)));
recipientKeys.add(publicKey);
return this;
}
public ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncryptionAlgorithm, GenericKey contentEncryptionKey)
throws CMSException
{
if (recipientIDs.isEmpty())
{
throw new CMSException("No recipients associated with generator - use addRecipient()");
}
init(keyAgreeAlgorithm.getAlgorithm());
PrivateKey senderPrivateKey = this.senderPrivateKey;
ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm();
ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector();
for (int i = 0; i != recipientIDs.size(); i++)
{
PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i);
KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i);
try
{
AlgorithmParameterSpec agreementParamSpec;
ASN1ObjectIdentifier keyEncAlg = keyEncryptionAlgorithm.getAlgorithm();
if (CMSUtils.isMQV(keyAgreementOID))
{
agreementParamSpec = new MQVParameterSpec(ephemeralKP, recipientPublicKey, userKeyingMaterial);
}
else if (CMSUtils.isEC(keyAgreementOID))
{
byte[] ukmKeyingMaterial = ecc_cms_Generator.generateKDFMaterial(keyEncryptionAlgorithm, keySizeProvider.getKeySize(keyEncAlg), userKeyingMaterial);
agreementParamSpec = new UserKeyingMaterialSpec(ukmKeyingMaterial);
}
else if (CMSUtils.isRFC2631(keyAgreementOID))
{
if (userKeyingMaterial != null)
{
agreementParamSpec = new UserKeyingMaterialSpec(userKeyingMaterial);
}
else
{
if (keyAgreementOID.equals(PKCSObjectIdentifiers.id_alg_SSDH))
{
throw new CMSException("User keying material must be set for static keys.");
}
agreementParamSpec = null;
}
}
else if (CMSUtils.isGOST(keyAgreementOID))
{
if (userKeyingMaterial != null)
{
agreementParamSpec = new UserKeyingMaterialSpec(userKeyingMaterial);
}
else
{
throw new CMSException("User keying material must be set for static keys.");
}
}
else
{
throw new CMSException("Unknown key agreement algorithm: " + keyAgreementOID);
}
// Use key agreement to choose a wrap key for this recipient
KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID);
keyAgreement.init(senderPrivateKey, agreementParamSpec, random);
keyAgreement.doPhase(recipientPublicKey, true);
SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncAlg.getId());
// Wrap the content encryption key with the agreement key
Cipher keyEncryptionCipher = helper.createCipher(keyEncAlg);
ASN1OctetString encryptedKey;
if (keyEncAlg.equals(CryptoProObjectIdentifiers.id_Gost28147_89_None_KeyWrap)
|| keyEncAlg.equals(CryptoProObjectIdentifiers.id_Gost28147_89_CryptoPro_KeyWrap))
{
keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, new GOST28147WrapParameterSpec(CryptoProObjectIdentifiers.id_Gost28147_89_CryptoPro_A_ParamSet, userKeyingMaterial));
byte[] encKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey));
Gost2814789EncryptedKey encKey = new Gost2814789EncryptedKey(
Arrays.copyOfRange(encKeyBytes, 0, encKeyBytes.length - 4),
Arrays.copyOfRange(encKeyBytes, encKeyBytes.length - 4, encKeyBytes.length));
encryptedKey = new DEROctetString(encKey.getEncoded(ASN1Encoding.DER));
}
else
{
keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random);
byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey));
encryptedKey = new DEROctetString(encryptedKeyBytes);
}
recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey));
}
catch (GeneralSecurityException e)
{
throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
}
catch (IOException e)
{
throw new CMSException("unable to encode wrapped key: " + e.getMessage(), e);
}
}
return new DERSequence(recipientEncryptedKeys);
}
protected byte[] getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg)
throws CMSException
{
init(keyAgreeAlg.getAlgorithm());
if (ephemeralKP != null)
{
OriginatorPublicKey originatorPublicKey = createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded()));
try
{
if (userKeyingMaterial != null)
{
return new MQVuserKeyingMaterial(originatorPublicKey, new DEROctetString(userKeyingMaterial)).getEncoded();
}
else
{
return new MQVuserKeyingMaterial(originatorPublicKey, null).getEncoded();
}
}
catch (IOException e)
{
throw new CMSException("unable to encode user keying material: " + e.getMessage(), e);
}
}
return userKeyingMaterial;
}
private void init(ASN1ObjectIdentifier keyAgreementOID)
throws CMSException
{
if (random == null)
{
random = new SecureRandom();
}
if (CMSUtils.isMQV(keyAgreementOID))
{
if (ephemeralKP == null)
{
try
{
SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded());
AlgorithmParameters ecAlgParams = helper.createAlgorithmParameters(keyAgreementOID);
ecAlgParams.init(pubInfo.getAlgorithm().getParameters().toASN1Primitive().getEncoded());
KeyPairGenerator ephemKPG = helper.createKeyPairGenerator(keyAgreementOID);
ephemKPG.initialize(ecAlgParams.getParameterSpec(AlgorithmParameterSpec.class), random);
ephemeralKP = ephemKPG.generateKeyPair();
}
catch (Exception e)
{
throw new CMSException(
"cannot determine MQV ephemeral key pair parameters from public key: " + e, e);
}
}
}
}
private static KeyMaterialGenerator ecc_cms_Generator = new RFC5753KeyMaterialGenerator();
}