Skip to content

Commit 924961f

Browse files
committed
Fix KDF parameter parsing (all versions) and KDF parameter value selection (1.1)
1 parent d691e68 commit 924961f

File tree

2 files changed

+44
-25
lines changed

2 files changed

+44
-25
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<groupId>com.github.martinpaljak</groupId>
66
<artifactId>cdoc4j</artifactId>
77
<packaging>jar</packaging>
8-
<version>17.11.26</version>
8+
<version>17.11.26.1</version>
99
<name>cdoc4j</name>
1010
<description>Java library for handling CDOC 1.0/1.1/2.0 files with AES-256 GCM and RSA or ECC recipients
1111
</description>

src/main/java/org/cdoc4j/XMLENC.java

+43-24
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import java.security.spec.ECPoint;
5757
import java.security.spec.ECPublicKeySpec;
5858
import java.util.ArrayList;
59+
import java.util.Arrays;
5960
import java.util.Base64;
6061
import java.util.Collection;
6162

@@ -64,17 +65,17 @@ final class XMLENC {
6465
final static String ALGORITHM = "Algorithm";
6566

6667
// Returns elements to be added to the CDOC XML, based on recipient type
67-
private static Element toRecipient(Document cdoc, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
68+
private static Element toRecipient(Document cdoc, CDOC.Version v, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
6869
if (cert.getPublicKey() instanceof ECPublicKey) {
69-
return toECRecipient(cdoc, name, cert, dek, includecert);
70+
return toECRecipient(cdoc, v, name, cert, dek, includecert);
7071
} else if (cert.getPublicKey() instanceof RSAPublicKey) {
71-
return toRSARecipient(cdoc, name, cert, dek, includecert);
72+
return toRSARecipient(cdoc, v, name, cert, dek, includecert);
7273
} else {
7374
throw new IllegalArgumentException("Unknown public key algorithm: " + cert.getPublicKey().getAlgorithm());
7475
}
7576
}
7677

77-
private static Element toRSARecipient(Document cdoc, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
78+
private static Element toRSARecipient(Document cdoc, CDOC.Version v, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
7879
Element enckey = cdoc.createElement("xenc:EncryptedKey");
7980
enckey.setAttribute("Recipient", name);
8081

@@ -108,14 +109,18 @@ private static Element toRSARecipient(Document cdoc, String name, X509Certificat
108109
return enckey;
109110
}
110111

111-
private static Element toECRecipient(Document cdoc, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
112+
private static Element toECRecipient(Document cdoc, CDOC.Version v, String name, X509Certificate cert, SecretKey dek, boolean includecert) throws GeneralSecurityException {
112113
ECPublicKey k = (ECPublicKey) cert.getPublicKey();
113114

114-
// Generate temporary key.
115+
// Generate ephemeral key.
115116
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
116117
kpg.initialize(k.getParams());
117118
KeyPair keyPair = kpg.generateKeyPair();
118119

120+
SubjectPublicKeyInfo partyVkey = SubjectPublicKeyInfo.getInstance(k.getEncoded());
121+
SubjectPublicKeyInfo partyUkey = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
122+
123+
// Construct XML
119124
Element enckey = cdoc.createElement("xenc:EncryptedKey");
120125
enckey.setAttribute("Recipient", name);
121126

@@ -132,28 +137,35 @@ private static Element toECRecipient(Document cdoc, String name, X509Certificate
132137
//
133138
Element kdm = cdoc.createElement("xenc11:KeyDerivationMethod");
134139
kdm.setAttribute(ALGORITHM, "http://www.w3.org/2009/xmlenc11#ConcatKDF");
135-
//
136-
137-
// Get the OID
138-
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(k.getEncoded());
139-
SubjectPublicKeyInfo tempKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
140140

141-
//System.out.println(subPubKeyInfo.getAlgorithm().getParameters());
142141

143-
//
144142
// String curveName = "urn:oid:" + ASN1ObjectIdentifier.getInstance(subPubKeyInfo.getAlgorithm().getParameters()).toString();
145-
String curveName = "urn:oid:" + subPubKeyInfo.getAlgorithm().getParameters().toString();
143+
String curveName = "urn:oid:" + partyVkey.getAlgorithm().getParameters().toString();
146144
//System.out.println(curveName);
147145

148146
// CRITICAL
149-
byte[] algid = "http://www.w3.org/2001/04/xmlenc#kw-aes256".getBytes(StandardCharsets.US_ASCII);
150-
byte[] uinfo = tempKeyInfo.getPublicKeyData().getBytes();
151-
byte[] vinfo = getCN(cert).getBytes(StandardCharsets.UTF_8);
147+
final byte[] algid;
148+
final byte[] uinfo;
149+
final byte[] vinfo;
150+
151+
// This if assumes that EC is only possible with v11 and v20
152+
// Formatting conforms to hexBinary with zero padding
153+
if (v == CDOC.Version.CDOC_V1_1) {
154+
algid = "ENCDOC-XML|1.1".getBytes(StandardCharsets.US_ASCII);
155+
uinfo = partyUkey.getPublicKeyData().getBytes();
156+
vinfo = cert.getEncoded();
157+
} else if (v == CDOC.Version.CDOC_V2_0) {
158+
algid = "http://www.w3.org/2001/04/xmlenc#kw-aes256".getBytes(StandardCharsets.US_ASCII);
159+
uinfo = partyUkey.getPublicKeyData().getBytes();
160+
vinfo = partyVkey.getPublicKeyData().getBytes();
161+
} else {
162+
throw new IllegalStateException("Invalid document version for EC recipient");
163+
}
152164

153165
Element ckdfp = cdoc.createElement("xenc11:ConcatKDFParams");
154-
ckdfp.setAttribute("AlgorithmID", Hex.toHexString(algid));
155-
ckdfp.setAttribute("PartyUInfo", Hex.toHexString(uinfo));
156-
ckdfp.setAttribute("PartyVInfo", Hex.toHexString(vinfo));
166+
ckdfp.setAttribute("AlgorithmID", Hex.toHexString(Legacy.concatenate(new byte[]{0x00}, algid)));
167+
ckdfp.setAttribute("PartyUInfo", Hex.toHexString(Legacy.concatenate(new byte[]{0x00}, uinfo)));
168+
ckdfp.setAttribute("PartyVInfo", Hex.toHexString(Legacy.concatenate(new byte[]{0x00}, vinfo)));
157169
Element dm = cdoc.createElement("ds:DigestMethod");
158170
dm.setAttribute(ALGORITHM, "http://www.w3.org/2001/04/xmlenc#sha256");
159171
ckdfp.appendChild(dm);
@@ -167,7 +179,7 @@ private static Element toECRecipient(Document cdoc, String name, X509Certificate
167179
eckvnc.setAttribute("URI", curveName);
168180
eckv.appendChild(eckvnc);
169181
Element ecpk = cdoc.createElement("dsig11:PublicKey");
170-
ecpk.setTextContent(Base64.getEncoder().encodeToString(tempKeyInfo.getPublicKeyData().getBytes()));
182+
ecpk.setTextContent(Base64.getEncoder().encodeToString(partyUkey.getPublicKeyData().getBytes()));
171183
eckv.appendChild(ecpk);
172184

173185
kv.appendChild(eckv);
@@ -192,14 +204,14 @@ private static Element toECRecipient(Document cdoc, String name, X509Certificate
192204
KeyAgreement key_agreement = KeyAgreement.getInstance("ECDH");
193205
key_agreement.init(keyPair.getPrivate());
194206
key_agreement.doPhase(cert.getPublicKey(), true);
207+
195208
// Use the shared secret to wrap the actual key
196209
byte[] shared_secret = key_agreement.generateSecret();
197210

198211
// Derive key wrap key with ckdf
199212
ConcatenationKDFGenerator ckdf = new ConcatenationKDFGenerator(new SHA384Digest()); // FIXME: parametrize
200213
ckdf.init(new KDFParameters(shared_secret, Legacy.concatenate(algid, uinfo, vinfo)));
201214
byte[] wrapkeybytes = new byte[32];
202-
203215
ckdf.generateBytes(wrapkeybytes, 0, 32);
204216

205217
SecretKeySpec wrapKey = new SecretKeySpec(wrapkeybytes, "AES");
@@ -253,7 +265,7 @@ static Document makeRecipientsXML(CDOC.Version v, Collection<X509Certificate> re
253265
} else {
254266
// One for every recipient, dependent on algorithm
255267
for (X509Certificate crt : recipients) {
256-
Element enckey = toRecipient(cdoc, privacy ? "Undisclosed" : getCN(crt), crt, dek, privacy);
268+
Element enckey = toRecipient(cdoc, v, privacy ? "Undisclosed" : getCN(crt), crt, dek, privacy);
257269
keyinfo.appendChild(enckey);
258270
}
259271
}
@@ -284,6 +296,13 @@ static ArrayList<Recipient> parseRecipientsXML(Document d) throws IOException, C
284296
byte a[] = Hex.decode(params.getAttributes().getNamedItem("AlgorithmID").getTextContent());
285297
byte u[] = Hex.decode(params.getAttributes().getNamedItem("PartyUInfo").getTextContent());
286298
byte v[] = Hex.decode(params.getAttributes().getNamedItem("PartyVInfo").getTextContent());
299+
// Support only full octets
300+
if (a[0] != 0 || u[0] != 0 || v[0] != 0) {
301+
throw new IOException("Only full octets supported for AlgorithmID, PartyUInfo and PartyVInfo");
302+
}
303+
a = Arrays.copyOfRange(a, 1, a.length);
304+
u = Arrays.copyOfRange(u, 1, u.length);
305+
v = Arrays.copyOfRange(v, 1, v.length);
287306

288307
String kdf = params.getParentNode().getAttributes().getNamedItem("Algorithm").getTextContent();
289308
if (!kdf.equals("http://www.w3.org/2009/xmlenc11#ConcatKDF"))
@@ -313,7 +332,7 @@ static ArrayList<Recipient> parseRecipientsXML(Document d) throws IOException, C
313332
Recipient.ECDHESRecipient r = new Recipient.ECDHESRecipient(cert, name, pk, cgram, a, u, v);
314333
result.add(r);
315334
} else {
316-
throw new RuntimeException("Unknown key encryption algorithm: " + algorithm);
335+
throw new IOException("Unknown key encryption algorithm: " + algorithm);
317336
}
318337
}
319338
} catch (XPathExpressionException | GeneralSecurityException | NullPointerException e) {

0 commit comments

Comments
 (0)