package org.bouncycastle.cms.jcajce; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; 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.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.util.ArrayList; import java.util.List; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; 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.RecipientEncryptedKey; import org.bouncycastle.asn1.cms.RecipientKeyIdentifier; import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cms.CMSAlgorithm; import org.bouncycastle.cms.CMSEnvelopedGenerator; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.KeyAgreeRecipientInfoGenerator; import org.bouncycastle.jcajce.DefaultJcaJceHelper; import org.bouncycastle.jcajce.NamedJcaJceHelper; import org.bouncycastle.jcajce.ProviderJcaJceHelper; import org.bouncycastle.jce.spec.MQVPrivateKeySpec; import org.bouncycastle.jce.spec.MQVPublicKeySpec; import org.bouncycastle.operator.GenericKey; public class JceKeyAgreeRecipientInfoGenerator extends KeyAgreeRecipientInfoGenerator { private List recipientIDs = new ArrayList(); private List recipientKeys = new ArrayList(); private PublicKey senderPublicKey; private PrivateKey senderPrivateKey; private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceHelper()); private SecureRandom random; private KeyPair ephemeralKP; public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID) throws CMSException { super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID); this.senderPublicKey = senderPublicKey; this.senderPrivateKey = senderPrivateKey; } public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider) { this.helper = new EnvelopedDataHelper(new ProviderJcaJceHelper(provider)); return this; } public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName) { this.helper = new EnvelopedDataHelper(new NamedJcaJceHelper(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 { init(keyAgreeAlgorithm.getAlgorithm()); PrivateKey senderPrivateKey = this.senderPrivateKey; ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm(); if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF)) { senderPrivateKey = new MQVPrivateKeySpec( senderPrivateKey, ephemeralKP.getPrivate(), ephemeralKP.getPublic()); } ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector(); for (int i = 0; i != recipientIDs.size(); i++) { PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i); KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i); if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF)) { recipientPublicKey = new MQVPublicKeySpec(recipientPublicKey, recipientPublicKey); } try { // Use key agreement to choose a wrap key for this recipient KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID); keyAgreement.init(senderPrivateKey, random); keyAgreement.doPhase(recipientPublicKey, true); SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncryptionAlgorithm.getAlgorithm().getId()); // Wrap the content encryption key with the agreement key Cipher keyEncryptionCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm()); keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random); byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(CMSUtils.getJceKey(contentEncryptionKey)); ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes); recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey)); } catch (GeneralSecurityException e) { throw new CMSException("cannot perform agreement step: " + e.getMessage(), e); } } return new DERSequence(recipientEncryptedKeys); } protected ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg) throws CMSException { init(keyAgreeAlg.getAlgorithm()); if (ephemeralKP != null) { return new MQVuserKeyingMaterial( createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded())), null); } return null; } private void init(ASN1ObjectIdentifier keyAgreementOID) throws CMSException { if (random == null) { random = new SecureRandom(); } if (keyAgreementOID.equals(CMSAlgorithm.ECMQV_SHA1KDF)) { if (ephemeralKP == null) { try { ECParameterSpec ecParamSpec = ((ECPublicKey)senderPublicKey).getParams(); KeyPairGenerator ephemKPG = helper.createKeyPairGenerator(keyAgreementOID); ephemKPG.initialize(ecParamSpec, random); ephemeralKP = ephemKPG.generateKeyPair(); } catch (InvalidAlgorithmParameterException e) { throw new CMSException( "cannot determine MQV ephemeral key pair parameters from public key: " + e); } } } } }