Skip to content

Commit 869bbff

Browse files
committed
added implementation of the direct method for doing POP on a KEM key using challenge/response based on RFC 9810 EnvelopedData.
1 parent e29f5c6 commit 869bbff

File tree

11 files changed

+598
-49
lines changed

11 files changed

+598
-49
lines changed

core/src/main/java/org/bouncycastle/asn1/ASN1EncodableVector.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public void add(ASN1Encodable element)
4848
this.elementCount = minCapacity;
4949
}
5050

51+
public void addOptional(ASN1Encodable element)
52+
{
53+
if (element != null)
54+
{
55+
this.add(element);
56+
}
57+
}
58+
5159
public void addAll(ASN1Encodable[] others)
5260
{
5361
if (null == others)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
public class CMPChallengeFailedException
4+
extends CMPException
5+
{
6+
public CMPChallengeFailedException(String msg)
7+
{
8+
super(msg);
9+
}
10+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
import java.io.IOException;
4+
import java.io.OutputStream;
5+
import java.util.Collection;
6+
7+
import org.bouncycastle.asn1.cmp.Challenge;
8+
import org.bouncycastle.asn1.cmp.PKIHeader;
9+
import org.bouncycastle.asn1.cms.ContentInfo;
10+
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
11+
import org.bouncycastle.cms.CMSEnvelopedData;
12+
import org.bouncycastle.cms.CMSException;
13+
import org.bouncycastle.cms.Recipient;
14+
import org.bouncycastle.cms.RecipientInformation;
15+
import org.bouncycastle.operator.DigestCalculator;
16+
import org.bouncycastle.util.Arrays;
17+
18+
public class ChallengeContent
19+
{
20+
private final Challenge challenge;
21+
private final DigestCalculator owfCalc;
22+
23+
ChallengeContent(Challenge challenge, DigestCalculator owfCalc)
24+
{
25+
this.challenge = challenge;
26+
this.owfCalc = owfCalc;
27+
}
28+
29+
public byte[] extractChallenge(PKIHeader sourceMessageHdr, Recipient recipient)
30+
throws CMPException
31+
{
32+
// TODO: add check for recipientName
33+
try
34+
{
35+
CMSEnvelopedData cmsEnvelopedData = new CMSEnvelopedData(new ContentInfo(PKCSObjectIdentifiers.envelopedData, challenge.getEncryptedRand()));
36+
37+
Collection c = cmsEnvelopedData.getRecipientInfos().getRecipients();
38+
39+
RecipientInformation recInfo = (RecipientInformation)c.iterator().next();
40+
41+
byte[] recData = recInfo.getContent(recipient);
42+
43+
Challenge.Rand rand = Challenge.Rand.getInstance(recData);
44+
45+
if (!Arrays.constantTimeAreEqual(rand.getSender().getEncoded(), sourceMessageHdr.getSender().getEncoded()))
46+
{
47+
throw new CMPChallengeFailedException("incorrect sender found");
48+
}
49+
50+
OutputStream digOut = owfCalc.getOutputStream();
51+
52+
digOut.write(rand.getInt().getEncoded());
53+
54+
digOut.close();
55+
56+
if (!Arrays.constantTimeAreEqual(challenge.getWitness(), owfCalc.getDigest()))
57+
{
58+
throw new CMPChallengeFailedException("corrupted challenge found");
59+
}
60+
61+
return rand.getInt().getValue().toByteArray();
62+
}
63+
catch (CMSException e)
64+
{
65+
throw new CMPException(e.getMessage(), e);
66+
}
67+
catch (IOException e)
68+
{
69+
throw new CMPException(e.getMessage(), e);
70+
}
71+
}
72+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
import org.bouncycastle.asn1.ASN1Sequence;
4+
import org.bouncycastle.asn1.cmp.Challenge;
5+
import org.bouncycastle.asn1.cmp.PKIBody;
6+
import org.bouncycastle.asn1.cmp.POPODecKeyChallContent;
7+
import org.bouncycastle.operator.DigestCalculator;
8+
import org.bouncycastle.operator.DigestCalculatorProvider;
9+
import org.bouncycastle.operator.OperatorCreationException;
10+
11+
/**
12+
* POPODecKeyChallContent ::= SEQUENCE OF Challenge
13+
* -- One Challenge per encryption key certification request (in the
14+
* -- same order as these requests appear in CertReqMessages).
15+
*/
16+
public class POPODecryptionKeyChallengeContent
17+
{
18+
private final ASN1Sequence content;
19+
private final DigestCalculatorProvider owfCalcProvider;
20+
21+
POPODecryptionKeyChallengeContent(POPODecKeyChallContent challenges, DigestCalculatorProvider owfCalcProvider)
22+
{
23+
this.content = ASN1Sequence.getInstance(challenges.toASN1Primitive());
24+
this.owfCalcProvider = owfCalcProvider;
25+
}
26+
27+
public ChallengeContent[] toChallengeArray()
28+
throws CMPException
29+
{
30+
ChallengeContent[] result = new ChallengeContent[content.size()];
31+
DigestCalculator owfCalc = null;
32+
33+
for (int i = 0; i != result.length; i++)
34+
{
35+
Challenge c = Challenge.getInstance(content.getObjectAt(i));
36+
if (c.getOwf() != null)
37+
{
38+
try
39+
{
40+
owfCalc = owfCalcProvider.get(c.getOwf());
41+
}
42+
catch (OperatorCreationException e)
43+
{
44+
throw new CMPException(e.getMessage(), e);
45+
}
46+
}
47+
result[i] = new ChallengeContent(Challenge.getInstance(content.getObjectAt(i)), owfCalc);
48+
}
49+
50+
return result;
51+
}
52+
53+
public static POPODecryptionKeyChallengeContent fromPKIBody(PKIBody pkiBody, DigestCalculatorProvider owfProvider)
54+
{
55+
if (pkiBody.getType() != PKIBody.TYPE_POPO_CHALL)
56+
{
57+
throw new IllegalArgumentException("content of PKIBody wrong type: " + pkiBody.getType());
58+
}
59+
60+
return new POPODecryptionKeyChallengeContent(POPODecKeyChallContent.getInstance(pkiBody.getContent()), owfProvider);
61+
}
62+
63+
public POPODecKeyChallContent toASN1Structure()
64+
{
65+
return POPODecKeyChallContent.getInstance(content);
66+
}
67+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
import java.io.IOException;
4+
import java.io.OutputStream;
5+
6+
import org.bouncycastle.asn1.ASN1EncodableVector;
7+
import org.bouncycastle.asn1.ASN1Integer;
8+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
9+
import org.bouncycastle.asn1.DERSequence;
10+
import org.bouncycastle.asn1.cmp.Challenge;
11+
import org.bouncycastle.asn1.cmp.POPODecKeyChallContent;
12+
import org.bouncycastle.asn1.cms.EnvelopedData;
13+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
14+
import org.bouncycastle.asn1.x509.GeneralName;
15+
import org.bouncycastle.cms.CMSEnvelopedData;
16+
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
17+
import org.bouncycastle.cms.CMSProcessableByteArray;
18+
import org.bouncycastle.cms.RecipientInfoGenerator;
19+
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
20+
import org.bouncycastle.operator.DigestCalculator;
21+
import org.bouncycastle.operator.DigestCalculatorProvider;
22+
import org.bouncycastle.operator.OperatorCreationException;
23+
import org.bouncycastle.util.Arrays;
24+
25+
/**
26+
* POPODecKeyChallContent ::= SEQUENCE OF Challenge
27+
* -- One Challenge per encryption key certification request (in the
28+
* -- same order as these requests appear in CertReqMessages).
29+
*/
30+
public class POPODecryptionKeyChallengeContentBuilder
31+
{
32+
private final DigestCalculator owfCalculator;
33+
private final ASN1ObjectIdentifier challengeEncAlg;
34+
private ASN1EncodableVector challenges = new ASN1EncodableVector();
35+
36+
public POPODecryptionKeyChallengeContentBuilder(DigestCalculator owfCalculator, ASN1ObjectIdentifier challengeEncAlg)
37+
{
38+
this.owfCalculator = owfCalculator;
39+
this.challengeEncAlg = challengeEncAlg;
40+
}
41+
42+
public POPODecryptionKeyChallengeContentBuilder addChallenge(RecipientInfoGenerator recipientInfGenerator, GeneralName recipient, byte[] A)
43+
throws CMPException
44+
{
45+
byte[] integer = Arrays.clone(A);
46+
47+
try
48+
{
49+
OutputStream dOut = owfCalculator.getOutputStream();
50+
51+
dOut.write(new ASN1Integer(integer).getEncoded());
52+
53+
dOut.close();
54+
}
55+
catch (IOException e)
56+
{
57+
throw new CMPException("unable to calculate witness", e);
58+
}
59+
60+
CMSEnvelopedData encryptedChallenge;
61+
try
62+
{
63+
CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
64+
65+
edGen.addRecipientInfoGenerator(recipientInfGenerator);
66+
67+
encryptedChallenge = edGen.generate(
68+
new CMSProcessableByteArray(new Challenge.Rand(A, recipient).getEncoded()),
69+
new JceCMSContentEncryptorBuilder(challengeEncAlg).setProvider("BC").build());
70+
}
71+
catch (Exception e)
72+
{
73+
throw new CMPException("unable to encrypt challenge", e);
74+
}
75+
76+
EnvelopedData encryptedRand = EnvelopedData.getInstance(encryptedChallenge.toASN1Structure().getContent());
77+
78+
if (this.challenges.size() == 0)
79+
{
80+
this.challenges.add(new Challenge(owfCalculator.getAlgorithmIdentifier(), owfCalculator.getDigest(), encryptedRand));
81+
}
82+
else
83+
{
84+
this.challenges.add(new Challenge(owfCalculator.getDigest(), encryptedRand));
85+
}
86+
return this;
87+
}
88+
89+
public POPODecryptionKeyChallengeContent build()
90+
{
91+
return new POPODecryptionKeyChallengeContent(POPODecKeyChallContent.getInstance(new DERSequence(challenges)), new DigestCalculatorProvider()
92+
{
93+
@Override
94+
public DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier)
95+
throws OperatorCreationException
96+
{
97+
return owfCalculator;
98+
}
99+
});
100+
}
101+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
import org.bouncycastle.asn1.ASN1Integer;
4+
import org.bouncycastle.asn1.cmp.PKIBody;
5+
import org.bouncycastle.asn1.cmp.POPODecKeyRespContent;
6+
7+
public class POPODecryptionKeyResponseContent
8+
{
9+
private final POPODecKeyRespContent respContent;
10+
11+
POPODecryptionKeyResponseContent(POPODecKeyRespContent respContent)
12+
{
13+
this.respContent = respContent;
14+
}
15+
16+
public byte[][] getResponses()
17+
{
18+
ASN1Integer[] resps = respContent.toASN1IntegerArray();
19+
byte[][] rv = new byte[resps.length][];
20+
21+
for (int i = 0; i != resps.length; i++)
22+
{
23+
rv[i] = resps[i].getValue().toByteArray();
24+
}
25+
26+
return rv;
27+
}
28+
29+
public static POPODecryptionKeyResponseContent fromPKIBody(PKIBody pkiBody)
30+
{
31+
if (pkiBody.getType() != PKIBody.TYPE_POPO_REP)
32+
{
33+
throw new IllegalArgumentException("content of PKIBody wrong type: " + pkiBody.getType());
34+
}
35+
36+
return new POPODecryptionKeyResponseContent(POPODecKeyRespContent.getInstance(pkiBody.getContent()));
37+
}
38+
39+
public POPODecKeyRespContent toASN1Structure()
40+
{
41+
return respContent;
42+
}
43+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.bouncycastle.cert.cmp;
2+
3+
import java.math.BigInteger;
4+
5+
import org.bouncycastle.asn1.ASN1EncodableVector;
6+
import org.bouncycastle.asn1.ASN1Integer;
7+
import org.bouncycastle.asn1.DERSequence;
8+
import org.bouncycastle.asn1.cmp.POPODecKeyRespContent;
9+
10+
public class POPODecryptionKeyResponseContentBuilder
11+
{
12+
private ASN1EncodableVector v = new ASN1EncodableVector();
13+
14+
public POPODecryptionKeyResponseContentBuilder addChallengeResponse(byte[] response)
15+
{
16+
v.add(new ASN1Integer(new BigInteger(response)));
17+
18+
return this;
19+
}
20+
21+
public POPODecryptionKeyResponseContent build()
22+
{
23+
return new POPODecryptionKeyResponseContent(POPODecKeyRespContent.getInstance(new DERSequence(v)));
24+
}
25+
}

pkix/src/main/java/org/bouncycastle/cert/cmp/ProtectedPKIMessageBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,20 @@ public ProtectedPKIMessageBuilder setBody(int bodyType, CertificateConfirmationC
212212
return this;
213213
}
214214

215+
public ProtectedPKIMessageBuilder setBody(POPODecryptionKeyChallengeContent popoDecKeyChallContent)
216+
{
217+
this.body = new PKIBody(PKIBody.TYPE_POPO_CHALL, popoDecKeyChallContent.toASN1Structure());
218+
219+
return this;
220+
}
221+
222+
public ProtectedPKIMessageBuilder setBody(POPODecryptionKeyResponseContent popoDecKeyRespContent)
223+
{
224+
this.body = new PKIBody(PKIBody.TYPE_POPO_REP, popoDecKeyRespContent.toASN1Structure());
225+
226+
return this;
227+
}
228+
215229
/**
216230
* Add an "extra certificate" to the message.
217231
*

0 commit comments

Comments
 (0)