Skip to content

Commit a26f6df

Browse files
author
Eugene Bochilo
committed
Make signing algorithm agnostic
DEVSIX-7617
1 parent 3df8c2a commit a26f6df

File tree

15 files changed

+209
-24
lines changed

15 files changed

+209
-24
lines changed

bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ This file is part of the iText (R) project.
309309
import org.bouncycastle.openssl.PEMParser;
310310
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
311311
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
312+
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
313+
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
312314
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
313315
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
314316
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
@@ -335,6 +337,27 @@ public BouncyCastleFactory() {
335337
// Empty constructor.
336338
}
337339

340+
/**
341+
* {@inheritDoc}
342+
*/
343+
@Override
344+
public String getAlgorithmOid(String name) {
345+
try {
346+
AlgorithmIdentifier algorithmIdentifier = new DefaultSignatureAlgorithmIdentifierFinder().find(name);
347+
return algorithmIdentifier.getAlgorithm().getId();
348+
} catch (IllegalArgumentException ignored) {
349+
return null;
350+
}
351+
}
352+
353+
/**
354+
* {@inheritDoc}
355+
*/
356+
@Override
357+
public String getAlgorithmName(String oid) {
358+
return new DefaultAlgorithmNameFinder().getAlgorithmName(new ASN1ObjectIdentifier(oid));
359+
}
360+
338361
/**
339362
* {@inheritDoc}
340363
*/

bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ class BouncyCastleDefaultFactory implements IBouncyCastleFactory {
153153
BouncyCastleDefaultFactory() {
154154
// Empty constructor
155155
}
156+
157+
@Override
158+
public String getAlgorithmOid(String name) {
159+
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
160+
}
161+
162+
@Override
163+
public String getAlgorithmName(String oid) {
164+
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
165+
}
156166

157167
@Override
158168
public IASN1ObjectIdentifier createASN1ObjectIdentifier(IASN1Encodable encodable) {

bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ This file is part of the iText (R) project.
313313
import org.bouncycastle.openssl.PEMParser;
314314
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
315315
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
316+
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
317+
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
316318
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
317319
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
318320
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
@@ -339,6 +341,23 @@ public BouncyCastleFipsFactory() {
339341
// Empty constructor.
340342
}
341343

344+
/**
345+
* {@inheritDoc}
346+
*/
347+
@Override
348+
public String getAlgorithmOid(String name) {
349+
AlgorithmIdentifier algorithmIdentifier = new DefaultSignatureAlgorithmIdentifierFinder().find(name);
350+
return algorithmIdentifier.getAlgorithm().getId();
351+
}
352+
353+
/**
354+
* {@inheritDoc}
355+
*/
356+
@Override
357+
public String getAlgorithmName(String oid) {
358+
return new DefaultAlgorithmNameFinder().getAlgorithmName(new ASN1ObjectIdentifier(oid));
359+
}
360+
342361
/**
343362
* {@inheritDoc}
344363
*/

commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,25 @@ This file is part of the iText (R) project.
152152
* selected depending on a bouncy-castle dependency specified by the user.
153153
*/
154154
public interface IBouncyCastleFactory {
155+
156+
/**
157+
* Get signing algorithm oid from its name.
158+
*
159+
* @param name name of the algorithm
160+
*
161+
* @return algorithm oid
162+
*/
163+
String getAlgorithmOid(String name);
164+
165+
/**
166+
* Get signing algorithm name from its oid.
167+
*
168+
* @param oid oid of the algorithm
169+
*
170+
* @return algorithm name
171+
*/
172+
String getAlgorithmName(String oid);
173+
155174
/**
156175
* Cast ASN1 encodable wrapper to the ASN1 object identifier wrapper.
157176
*

sharpenConfiguration.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@
467467
<file path="com/itextpdf/signatures/PdfPKCS7ManuallyPortedTest.java"/>
468468
</fileset>
469469
<fileset reason="Different implementation on .NET and java">
470-
<file path="com/itextpdf/signatures/sign/IsoSignatureExtensionsRoundtripTests.java"/>
470+
<file path="com/itextpdf/signatures/sign/IsoSignatureExtensionsRoundtripTest.java"/>
471471
</fileset>
472472
<fileset reason=".pem files reading logic is different in java and .net">
473473
<file path="com/itextpdf/signatures/testutils/PemFileHelper.java"/>

sign/src/main/java/com/itextpdf/signatures/PdfPKCS7.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ public String getSignatureMechanismName() {
657657
// there's no need to duplicate that information in the algorithm name.
658658
return "RSASSA-PSS";
659659
default:
660-
return getDigestAlgorithmName() + "with" + getSignatureAlgorithmName();
660+
return SignatureMechanisms.getMechanism(signatureMechanismOid, getDigestAlgorithmName());
661661
}
662662
}
663663

sign/src/main/java/com/itextpdf/signatures/PrivateKeySignature.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.signatures;
2424

25+
import com.itextpdf.commons.utils.MessageFormatUtil;
2526
import com.itextpdf.kernel.exceptions.PdfException;
2627
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
2728

2829
import java.security.GeneralSecurityException;
30+
import java.security.NoSuchAlgorithmException;
2931
import java.security.PrivateKey;
3032
import java.security.Signature;
3133

@@ -164,24 +166,38 @@ public ISignatureMechanismParams getSignatureMechanismParameters() {
164166
@Override
165167
public byte[] sign(byte[] message) throws GeneralSecurityException {
166168
String algorithm = getSignatureMechanismName();
167-
Signature sig = SignUtils.getSignatureHelper(algorithm, provider);
168-
if (parameters != null) {
169-
parameters.apply(sig);
169+
Signature sig;
170+
try {
171+
sig = SignUtils.getSignatureHelper(algorithm, provider);
172+
if (parameters != null) {
173+
parameters.apply(sig);
174+
}
175+
sig.initSign(pk);
176+
sig.update(message);
177+
return sig.sign();
178+
} catch (Exception ignored) {
179+
try {
180+
sig = SignUtils.getSignatureHelper(getSignatureAlgorithmName(), provider);
181+
if (parameters != null) {
182+
parameters.apply(sig);
183+
}
184+
sig.initSign(pk);
185+
sig.update(message);
186+
return sig.sign();
187+
} catch (Exception e) {
188+
throw new PdfException(MessageFormatUtil.format(
189+
SignExceptionMessageConstant.ALGORITHMS_NOT_SUPPORTED, algorithm, getSignatureAlgorithmName()),
190+
e);
191+
}
170192
}
171-
sig.initSign(pk);
172-
sig.update(message);
173-
return sig.sign();
174193
}
175194

176195
private String getSignatureMechanismName() {
177196
final String signatureAlgo = this.getSignatureAlgorithmName();
178-
// Ed25519 and Ed448 do not involve a choice of hashing algorithm
179-
// and RSASSA-PSS is parameterised
180-
if ("Ed25519".equals(signatureAlgo) || "Ed448".equals(signatureAlgo)
181-
|| "RSASSA-PSS".equals(signatureAlgo)) {
197+
// RSASSA-PSS is parameterised
198+
if ("RSASSA-PSS".equals(signatureAlgo)) {
182199
return signatureAlgo;
183-
} else {
184-
return getDigestAlgorithmName() + "with" + getSignatureAlgorithmName();
185-
}
200+
}
201+
return getDigestAlgorithmName() + "with" + getSignatureAlgorithmName();
186202
}
187203
}

sign/src/main/java/com/itextpdf/signatures/SignatureMechanisms.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.signatures;
2424

25+
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
26+
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.signatures.logs.SignLogMessageConstant;
28+
2529
import java.util.HashMap;
2630
import java.util.Map;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
2733

2834
/**
2935
* Class that contains OID mappings to extract a signature algorithm name
@@ -32,6 +38,10 @@ This file is part of the iText (R) project.
3238
*/
3339
public class SignatureMechanisms {
3440

41+
private static final Logger LOGGER = LoggerFactory.getLogger(SignatureMechanisms.class);
42+
43+
private static final IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.getFactory();
44+
3545
/** Maps IDs of signature algorithms with its human-readable name. */
3646
static final Map<String, String> algorithmNames = new HashMap<>();
3747
static final Map<String, String> rsaOidsByDigest = new HashMap<>();
@@ -141,23 +151,40 @@ public class SignatureMechanisms {
141151
* @return an OID string, or {@code null} if none was found.
142152
*/
143153
public static String getSignatureMechanismOid(String signatureAlgorithmName, String digestAlgorithmName) {
154+
String resultingOId;
144155
switch (signatureAlgorithmName) {
145156
case "RSA":
146157
final String oId = rsaOidsByDigest.get(digestAlgorithmName);
147-
return oId == null ? SecurityIDs.ID_RSA : oId;
158+
resultingOId = oId == null ? SecurityIDs.ID_RSA : oId;
159+
break;
148160
case "DSA":
149-
return dsaOidsByDigest.get(digestAlgorithmName);
161+
resultingOId = dsaOidsByDigest.get(digestAlgorithmName);
162+
break;
150163
case "ECDSA":
151-
return ecdsaOidsByDigest.get(digestAlgorithmName);
164+
resultingOId = ecdsaOidsByDigest.get(digestAlgorithmName);
165+
break;
152166
case "Ed25519":
153-
return SecurityIDs.ID_ED25519;
167+
resultingOId = SecurityIDs.ID_ED25519;
168+
break;
154169
case "Ed448":
155-
return SecurityIDs.ID_ED448;
170+
resultingOId = SecurityIDs.ID_ED448;
171+
break;
156172
case "RSASSA-PSS":
157173
case "RSA/PSS":
158-
return SecurityIDs.ID_RSASSA_PSS;
174+
resultingOId = SecurityIDs.ID_RSASSA_PSS;
175+
break;
159176
default:
160-
return null;
177+
resultingOId = null;
178+
}
179+
if (resultingOId != null) {
180+
return resultingOId;
181+
}
182+
LOGGER.warn(SignLogMessageConstant.ALGORITHM_NOT_FROM_SPEC);
183+
resultingOId = BOUNCY_CASTLE_FACTORY.getAlgorithmOid(digestAlgorithmName + "with" + signatureAlgorithmName);
184+
if (resultingOId == null) {
185+
return BOUNCY_CASTLE_FACTORY.getAlgorithmOid(signatureAlgorithmName);
186+
} else {
187+
return resultingOId;
161188
}
162189
}
163190

@@ -174,4 +201,21 @@ public static String getAlgorithm(String oid) {
174201
return ret;
175202
}
176203
}
204+
205+
/**
206+
* Get the signing mechanism name for a certain id and digest.
207+
*
208+
* @param oid an id of an algorithm
209+
* @param digest digest of an algorithm
210+
*
211+
* @return name of the mechanism
212+
*/
213+
public static String getMechanism(String oid, String digest) {
214+
String algorithm = getAlgorithm(oid);
215+
if (!algorithm.equals(oid)) {
216+
return digest + "with" + algorithm;
217+
}
218+
LOGGER.warn(SignLogMessageConstant.ALGORITHM_NOT_FROM_SPEC);
219+
return BOUNCY_CASTLE_FACTORY.getAlgorithmName(oid);
220+
}
177221
}

sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ This file is part of the iText (R) project.
2626
* Class that bundles all the error message templates as constants.
2727
*/
2828
public final class SignExceptionMessageConstant {
29+
public static final String ALGORITHMS_NOT_SUPPORTED = "Signing algorithms {0} and {1} are not supported.";
30+
2931
public static final String AUTHENTICATED_ATTRIBUTE_IS_MISSING_THE_DIGEST = "Authenticated attribute is missing "
3032
+ "the digest.";
3133
public static final String AVAILABLE_SPACE_IS_NOT_ENOUGH_FOR_SIGNATURE = "Available space is not enough for "

sign/src/main/java/com/itextpdf/signatures/logs/SignLogMessageConstant.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public final class SignLogMessageConstant {
2929

3030
public static final String EXCEPTION_WITHOUT_MESSAGE =
3131
"Unexpected exception without message was thrown during keystore processing";
32+
33+
public static final String ALGORITHM_NOT_FROM_SPEC =
34+
"Requested algorithm might not be supported by the pdf specification.";
3235

3336
private SignLogMessageConstant() {
3437
// Private constructor will prevent the instantiation of this class directly

0 commit comments

Comments
 (0)