如何使用带有 JarSigner 的 XMSS (PQC) 签名方案对 .jar 文件进行签名
How to sign a .jar file using XMSS (PQC) Signature Scheme with JarSigner
我正在尝试使用 JarSigner to sign .jar files with XMSS 签名。
配合使用JCA/JCE Post-Quantum Cryptography Provider from BouncyCastle it is possible to generate XMSS and XMSSMT KeyPairs programmatically (example)。
据我所知,为了使用 JarSigner,提供一个 KeyStore 和条目的别名是至关重要的,一个人想用它来签署它的代码: jarsigner -keystore myKeystore -storetype JKS -storepass password -keypass password myjarfile.jar keystoreEntryAlias
KeyStore 条目包含 Public/Secret KeyPair 和关联的 X.509 证书。
使用 JarSigner 对 Jar 文件进行签名的 'normal' 方式如下:
- 使用 keytool 生成 Public/Secret 密钥对和证书,然后将它们存储在密钥库中 (
keytool -genkeypair -alias keystoreEntryAlias -keyalg RSA -sigalg SHA256withRSA -dname CN=MyCompanyName -storetype JKS -keypass password -keystore mykeystore.jks -storepass password
)
- 使用 JarSigner 使用存储在 mykeystore.jks 中的 SecretKey 和别名 keysotreEntryAlias (
jarsigner -keystore mykeystore.jks -storetype jks -storepass passeword -keypass password myjarfile.jar keystoreEntryAlias
) 对 .jar 进行签名
为了使用 XMSS 密钥签署我的文件,理论上我有两种可能性:
- 使用 BCPQC 以编程方式创建 XMSS 密钥对,将它们存储在 mykeystore 中并通过 CLI 使用
jarsigner -keystore mykeystore -alias xmss
对我的文件进行签名。
- 将 BCPQC-Provider 与 keytool 一起使用,以便直接通过 CLI 生成 XMSS KeyPairs 并将它们存储在 mykeystore 中(这里 keytool 还需要 2 个参数:
-providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
和 -providerpath C:\Path\to\BouncyCastle\provider\bcprov-jdk15on-160.jar
)然后签署使用 JarSigner 的密钥库条目文件
可悲的是我 运行 遇到了两种可能性的问题:
- 由于我还没有找到在没有 CLI 的情况下使用 JarSigner 的方法,因此我需要将生成的 XMSS 密钥对放在密钥库中,但因此我需要包含 Public XMSS 密钥的证书。 BouncyCastle 确实提供了一个 X.509CertificateBuilder 可用于生成所需的证书,但是如果您查看生成的证书,尤其是在签名算法和 Public 密钥(底部的源代码 [ CertificateBuilderExample],使用 XMSSMT)
- 看来 keytool 只使用来自 BCPQCProvider 的 init(int) 重载,而 XMSSKeyPairGeneratorSpi 拒绝了;它需要 AlgorithmParameterSpec 特别是 XMSSParameterSpec,或者根本不需要初始化——如果我尝试后者,它确实会生成一个密钥对,但是生成的密钥不能被编码,因此不能存储在 KeyStore 中。
我现在的问题是:
有谁知道使用 XMSS/XMSSMT 和 JarSigner 对 .jar 文件进行签名的方法,并且可以或多或少地详细解释 he/her 做对了我做错了什么?
或者,如果我在上面提到的任何地方都错了,请提供更正并指出这样做的方法?
更新 1:我现在可以使用另一个 X509CertificateGenerator(底部的源代码 [X509CertificateGenerator])和从 , here and here 收集的英特尔,到 使用 BouncyCastle 提供的 RSA 以编程方式成功签署 jar 文件(签名源代码在底部 [RSA_JarSigner])。
如果我尝试将用于使用 RSA 签名的相同方案应用到使用 XMSS 或 XMSSMT 签名,我 运行 进入由 NoSuchAlgorithmException: unrecognized algorithm name: XMSS
引起的 JarSignerException: Error in signer materials
([= 的源代码115=] 在底部 [SignXMSS] [SignXMSSMT].
希望有人能帮我找出问题所在!
更新 2:生成的 XMSS(或 XMSSMT)证书的问题似乎是由于签名算法(即 SHA256withXMSS)的条目已通过作为系统未知的 ASN1ObjectIdentifier。因此,我做了一些研究,看看 BouncyCastle 是否偶然在某处放置了 XMSS 证书生成器。宾果游戏,here 是一个!
我稍微缩短了代码,得到了 1 个生成器和 1 个验证器(源代码在底部 [XMSSGen] [XMSSVer]。
生成器为我提供了与其他方法(例如 [X509CertificateGenerator])相同的证书。
验证者遗憾地提示我这个丑陋的错误:Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384) at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32) at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
也许有人知道它的来源/如何修复它。为了查看 BC 是否可以使用自己创建的 XMSS 证书。
编辑:验证器的一个问题:PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
为什么我们需要私钥来验证证书?没有意义 - 只需删除它即可工作 ^^(或至少应该)
更新 3:我终于设法让验证器正常工作,所以我目前能够生成和验证 XMSS 证书。我还能够将 XMSS KeyPairs 与包含 PublicKey 的证书一起存储在 KeyStore 中。虽然我仍然无法签署任何 .jar 文件。
现在有点奇怪了:我可以用 BC XMSS KeyPairs 签名(当然我是,这就是他们制作的目的)如果我保存它们(或者至少是 PrivateKey,因为他需要签署东西)并在之后重新加载它以再次用它签署东西,它不起作用。无论是将它们存储在 KeyStore 中并检索它们,还是将密钥作为编码字节保存到文件中并再次加载它们。 (如果您对代码感兴趣,请发表评论,我会在此处 post)
我的建议是:由于 XMSS 签名方案需要保存状态(OTS 已使用的状态),因此在再次加载时无法从 PrivateKey 中检索到此状态(无论是从 KeyStore 还是文件无关紧要),因此不能用于签名。
[CertificateBuilderExample]
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class App {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastlePQCProvider());
SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
String datefrom = "12-08-2018 10:20:56";
String dateuntil = "12-05-2020 10:20:56";
Date from = sdf.parse(datefrom);
Date until = sdf.parse(dateuntil);
// Create self signed Root CA certificate
KeyPair rootCAKeyPair = generateKeyPair();
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
new X500Name("CN=rootCA"), // issuer authority
BigInteger.valueOf(new Random().nextInt()), //serial number of certificate
from, // start of validity
until, //end of certificate validity
new X500Name("CN=rootCA"), // subject name of certificate
rootCAKeyPair.getPublic()); // public key of certificate
// key usage restrictions
builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
X509Certificate rootCA = new JcaX509CertificateConverter().getCertificate(builder
.build(new JcaContentSignerBuilder("SHA256withXMSSMT").setProvider("BCPQC").
build(rootCAKeyPair.getPrivate()))); // private key of signing authority , here it is self signed
saveToFile(rootCA, "rootCA.cer");
}
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();
System.out.print("Public key:" + Arrays.toString(kp.getPublic().getEncoded()));
return kp;
}
private static void saveToFile(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException {
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(certificate.getEncoded());
fileOutputStream.flush();
fileOutputStream.close();
}
}
[X509CertificateGenerator]
public X509Certificate generateCertificate(String dn, KeyPair keyPair, int validity, String sigAlgName) throws GeneralSecurityException, IOException {
PrivateKey privateKey = keyPair.getPrivate();
X509CertInfo info = new X509CertInfo();
Date from = new Date();
Date to = new Date(from.getTime() + validity * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
BigInteger serialNumber = new BigInteger(64, new SecureRandom());
X500Name owner = new X500Name(dn);
AlgorithmId sigAlgId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNumber));
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(sigAlgId));
// Sign the cert to identify the algorithm that's used.
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
// Update the algorith, and resign.
sigAlgId = (AlgorithmId) certificate.get(X509CertImpl.SIG_ALG);
info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgId);
certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
return certificate;
}
[RSA_JarSigner]
public class JarSignerTest {
public static void main(String[] args) throws Exception{
JarSignerTest jst = new JarSignerTest();
jst.SignRSA();
}
public void SignRSA() throws Exception{
Security.addProvider(new BouncyCastleProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(1024, new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withRSA")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withRSA")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
}
[SignXMSS]
public void SignXMSS() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSS", "BCPQC");
kpGen.initialize(new XMSSParameterSpec(10, XMSSParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSS")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSS", new BouncyCastlePQCProvider())
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[SignXMSSMT]
public void SignXMSSMT() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSSMT")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSSMT")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[XMSSGen]
public class BCXMSSCertificateGenerator {
public static X509Certificate generateCertificate(PrivateKey privKey, PublicKey pubKey, int duration, boolean isSelfSigned) throws Exception {
Provider BC = new BouncyCastleProvider();
//
// distinguished name table.
//
X500NameBuilder builder = createStdBuilder();
//
// create the certificate - version 3
//
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withXMSS").setProvider("BCPQC").build(privKey);
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(new X500Name("cn=Java"), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))), builder.build(), pubKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
if (isSelfSigned) {
//
// check verifies in general
//
cert.verify(pubKey);
//
// check verifies with contained key
//
cert.verify(cert.getPublicKey());
}
ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
return (X509Certificate) fact.generateCertificate(bIn);
}
private static X500NameBuilder createStdBuilder() {
X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
builder.addRDN(RFC4519Style.c, "AU");
builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
builder.addRDN(RFC4519Style.l, "Melbourne");
builder.addRDN(RFC4519Style.st, "Victoria");
builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto@bouncycastle.org");
return builder;
}
}
[XMSSVer]
public class BCXMSSCertificateVerifyer {
public static boolean verifyCertificate(byte[] certBytes, String sigAlgorithm, byte[] keyBytes) throws Exception{
ByteArrayInputStream bIn;
bIn = new ByteArrayInputStream(certBytes);
CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
Certificate cert = fact.generateCertificate(bIn);
PublicKey k = cert.getPublicKey();
X509CertificateHolder certHldr = new X509CertificateHolder(certBytes);
certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BCPQC").build(k));
System.out.println(cert);
KeyFactory keyFactory = KeyFactory.getInstance(k.getAlgorithm(), "BCPQC");
PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); // ERROR at this line
/*_______________________________________________________________________________________________________________*\
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source)
at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384)
at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32)
at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
_________________________________________________________________________________________________________________
/* */
Signature signer = Signature.getInstance(sigAlgorithm, "BCPQC");
signer.initSign(privKey);
signer.update(certBytes);
byte[] sig = signer.sign();
signer.initVerify(cert);
signer.update(certBytes);
signer.verify(sig);
return true;
}
}
TL;DR: 使用 PQC 签名方案(例如 BC 使用 内置 JarSigner 提供的 XMSS 对 .jar 文件进行签名可能的。但是,可以编写一个 JarSigner,然后就可以了。
尽管 Oracle 为加密提供程序(例如 BC)提供了 JCA/JCE 开箱即用的集成,但 JarSigner 不使用那些已注册的提供程序进行签名或验证。
考虑到 JarSigner:支持的算法是硬编码的,因此无法扩展。
将 JarSigner 与 BC Provider 一起使用的唯一方法是完全重建它。但是不推荐这样做。
对于那些不怕劫持 jdk 源代码的人,您的项目必须 "override" 来自 jdk 的以下 classes:
- 算法Id
- 属性
- 内容信息
- EndEntityChecker
- HttpTimestamper
- 无效的 JarIndexError
- 贾伦特里
- JarException
- JarFile
- JarIndex
- JarInputStream
- JarOutputStream
- JarSigner
- JarVerifier
- JavaUtilJarAccess
- JavaUtilJarAccessImpl
- JavaUtilZipFileAccess
- 主要 (jdk.jartool.sun.security.tools.jarsigner)
- 清单
- ManifestEntryVerifier
- Pack200
- PKCS7
- PKIX 验证器
- 资源
- Resources_ja
- Resources_zh_CN
- SharedSecrets
- SignatureFileVerifier
- 签名者信息
- 简单验证器
- TimestampedSigner
- 时间戳
- TimestampToken
- TS 响应
- 验证者
- VersionedStream
因此将原始代码复制粘贴到您的项目中,并删除您劫持的所有 classes 的导入,这样您的 "custom" classes 将被采用而不是官方的.
注意:上面提到的大多数classes都可以在java.base模块中找到,尽管有些在jdk.jartool模块中(例如JarSigner本身)。
克隆 jdk 的必要部分以使 JarSigner 工作后,您终于可以继续实施 BC 提供程序和 PQC 签名方案的支持,尤其是 XMSSMT 和 SPHINCS,因为 .
请注意,对于已签名的 jar 文件的 the verification,JarVerifier 采用签名块文件的文件扩展名,并检查其是 .RSA、.DCA 还是 .EC。因此,您必须添加 .XMSS .XMSSMT .SPHINCS256 等。您还必须在 parseSignedData 方法中告诉 PKCS7 class 使用 BC 证书生成器。
还有一些其他的东西要改变(例如 AlgorithmID)我不记得了,但是一旦你完成了所有必要的步骤,你的 JarSigner 除了使用 "normal" RSA DCA 和 EC 签名和验证。
很遗憾,我无法与您分享最终的源代码,但我会尽可能多地回答您的问题。
我正在尝试使用 JarSigner to sign .jar files with XMSS 签名。
配合使用JCA/JCE Post-Quantum Cryptography Provider from BouncyCastle it is possible to generate XMSS and XMSSMT KeyPairs programmatically (example)。
据我所知,为了使用 JarSigner,提供一个 KeyStore 和条目的别名是至关重要的,一个人想用它来签署它的代码: jarsigner -keystore myKeystore -storetype JKS -storepass password -keypass password myjarfile.jar keystoreEntryAlias
KeyStore 条目包含 Public/Secret KeyPair 和关联的 X.509 证书。
使用 JarSigner 对 Jar 文件进行签名的 'normal' 方式如下:
- 使用 keytool 生成 Public/Secret 密钥对和证书,然后将它们存储在密钥库中 (
keytool -genkeypair -alias keystoreEntryAlias -keyalg RSA -sigalg SHA256withRSA -dname CN=MyCompanyName -storetype JKS -keypass password -keystore mykeystore.jks -storepass password
) - 使用 JarSigner 使用存储在 mykeystore.jks 中的 SecretKey 和别名 keysotreEntryAlias (
jarsigner -keystore mykeystore.jks -storetype jks -storepass passeword -keypass password myjarfile.jar keystoreEntryAlias
) 对 .jar 进行签名
为了使用 XMSS 密钥签署我的文件,理论上我有两种可能性:
- 使用 BCPQC 以编程方式创建 XMSS 密钥对,将它们存储在 mykeystore 中并通过 CLI 使用
jarsigner -keystore mykeystore -alias xmss
对我的文件进行签名。 - 将 BCPQC-Provider 与 keytool 一起使用,以便直接通过 CLI 生成 XMSS KeyPairs 并将它们存储在 mykeystore 中(这里 keytool 还需要 2 个参数:
-providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
和-providerpath C:\Path\to\BouncyCastle\provider\bcprov-jdk15on-160.jar
)然后签署使用 JarSigner 的密钥库条目文件
可悲的是我 运行 遇到了两种可能性的问题:
- 由于我还没有找到在没有 CLI 的情况下使用 JarSigner 的方法,因此我需要将生成的 XMSS 密钥对放在密钥库中,但因此我需要包含 Public XMSS 密钥的证书。 BouncyCastle 确实提供了一个 X.509CertificateBuilder 可用于生成所需的证书,但是如果您查看生成的证书,尤其是在签名算法和 Public 密钥(底部的源代码 [ CertificateBuilderExample],使用 XMSSMT)
- 看来 keytool 只使用来自 BCPQCProvider 的 init(int) 重载,而 XMSSKeyPairGeneratorSpi 拒绝了;它需要 AlgorithmParameterSpec 特别是 XMSSParameterSpec,或者根本不需要初始化——如果我尝试后者,它确实会生成一个密钥对,但是生成的密钥不能被编码,因此不能存储在 KeyStore 中。
我现在的问题是:
有谁知道使用 XMSS/XMSSMT 和 JarSigner 对 .jar 文件进行签名的方法,并且可以或多或少地详细解释 he/her 做对了我做错了什么? 或者,如果我在上面提到的任何地方都错了,请提供更正并指出这样做的方法?
更新 1:我现在可以使用另一个 X509CertificateGenerator(底部的源代码 [X509CertificateGenerator])和从
如果我尝试将用于使用 RSA 签名的相同方案应用到使用 XMSS 或 XMSSMT 签名,我 运行 进入由 NoSuchAlgorithmException: unrecognized algorithm name: XMSS
引起的 JarSignerException: Error in signer materials
([= 的源代码115=] 在底部 [SignXMSS] [SignXMSSMT].
希望有人能帮我找出问题所在!
更新 2:生成的 XMSS(或 XMSSMT)证书的问题似乎是由于签名算法(即 SHA256withXMSS)的条目已通过作为系统未知的 ASN1ObjectIdentifier。因此,我做了一些研究,看看 BouncyCastle 是否偶然在某处放置了 XMSS 证书生成器。宾果游戏,here 是一个!
我稍微缩短了代码,得到了 1 个生成器和 1 个验证器(源代码在底部 [XMSSGen] [XMSSVer]。
生成器为我提供了与其他方法(例如 [X509CertificateGenerator])相同的证书。
验证者遗憾地提示我这个丑陋的错误:Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384) at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32) at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
也许有人知道它的来源/如何修复它。为了查看 BC 是否可以使用自己创建的 XMSS 证书。
编辑:验证器的一个问题:PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
为什么我们需要私钥来验证证书?没有意义 - 只需删除它即可工作 ^^(或至少应该)
更新 3:我终于设法让验证器正常工作,所以我目前能够生成和验证 XMSS 证书。我还能够将 XMSS KeyPairs 与包含 PublicKey 的证书一起存储在 KeyStore 中。虽然我仍然无法签署任何 .jar 文件。
现在有点奇怪了:我可以用 BC XMSS KeyPairs 签名(当然我是,这就是他们制作的目的)如果我保存它们(或者至少是 PrivateKey,因为他需要签署东西)并在之后重新加载它以再次用它签署东西,它不起作用。无论是将它们存储在 KeyStore 中并检索它们,还是将密钥作为编码字节保存到文件中并再次加载它们。 (如果您对代码感兴趣,请发表评论,我会在此处 post)
我的建议是:由于 XMSS 签名方案需要保存状态(OTS 已使用的状态),因此在再次加载时无法从 PrivateKey 中检索到此状态(无论是从 KeyStore 还是文件无关紧要),因此不能用于签名。
[CertificateBuilderExample]
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class App {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastlePQCProvider());
SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
String datefrom = "12-08-2018 10:20:56";
String dateuntil = "12-05-2020 10:20:56";
Date from = sdf.parse(datefrom);
Date until = sdf.parse(dateuntil);
// Create self signed Root CA certificate
KeyPair rootCAKeyPair = generateKeyPair();
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
new X500Name("CN=rootCA"), // issuer authority
BigInteger.valueOf(new Random().nextInt()), //serial number of certificate
from, // start of validity
until, //end of certificate validity
new X500Name("CN=rootCA"), // subject name of certificate
rootCAKeyPair.getPublic()); // public key of certificate
// key usage restrictions
builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
X509Certificate rootCA = new JcaX509CertificateConverter().getCertificate(builder
.build(new JcaContentSignerBuilder("SHA256withXMSSMT").setProvider("BCPQC").
build(rootCAKeyPair.getPrivate()))); // private key of signing authority , here it is self signed
saveToFile(rootCA, "rootCA.cer");
}
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();
System.out.print("Public key:" + Arrays.toString(kp.getPublic().getEncoded()));
return kp;
}
private static void saveToFile(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException {
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(certificate.getEncoded());
fileOutputStream.flush();
fileOutputStream.close();
}
}
[X509CertificateGenerator]
public X509Certificate generateCertificate(String dn, KeyPair keyPair, int validity, String sigAlgName) throws GeneralSecurityException, IOException {
PrivateKey privateKey = keyPair.getPrivate();
X509CertInfo info = new X509CertInfo();
Date from = new Date();
Date to = new Date(from.getTime() + validity * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
BigInteger serialNumber = new BigInteger(64, new SecureRandom());
X500Name owner = new X500Name(dn);
AlgorithmId sigAlgId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNumber));
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(sigAlgId));
// Sign the cert to identify the algorithm that's used.
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
// Update the algorith, and resign.
sigAlgId = (AlgorithmId) certificate.get(X509CertImpl.SIG_ALG);
info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgId);
certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
return certificate;
}
[RSA_JarSigner]
public class JarSignerTest {
public static void main(String[] args) throws Exception{
JarSignerTest jst = new JarSignerTest();
jst.SignRSA();
}
public void SignRSA() throws Exception{
Security.addProvider(new BouncyCastleProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(1024, new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withRSA")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withRSA")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
}
[SignXMSS]
public void SignXMSS() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSS", "BCPQC");
kpGen.initialize(new XMSSParameterSpec(10, XMSSParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSS")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSS", new BouncyCastlePQCProvider())
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[SignXMSSMT]
public void SignXMSSMT() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\Path\to\jar\toSign\jarfile.jar"),
outputfile = new File("C:\Path\to\signedJar\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSSMT")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSSMT")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[XMSSGen]
public class BCXMSSCertificateGenerator {
public static X509Certificate generateCertificate(PrivateKey privKey, PublicKey pubKey, int duration, boolean isSelfSigned) throws Exception {
Provider BC = new BouncyCastleProvider();
//
// distinguished name table.
//
X500NameBuilder builder = createStdBuilder();
//
// create the certificate - version 3
//
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withXMSS").setProvider("BCPQC").build(privKey);
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(new X500Name("cn=Java"), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))), builder.build(), pubKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
if (isSelfSigned) {
//
// check verifies in general
//
cert.verify(pubKey);
//
// check verifies with contained key
//
cert.verify(cert.getPublicKey());
}
ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
return (X509Certificate) fact.generateCertificate(bIn);
}
private static X500NameBuilder createStdBuilder() {
X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
builder.addRDN(RFC4519Style.c, "AU");
builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
builder.addRDN(RFC4519Style.l, "Melbourne");
builder.addRDN(RFC4519Style.st, "Victoria");
builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto@bouncycastle.org");
return builder;
}
}
[XMSSVer]
public class BCXMSSCertificateVerifyer {
public static boolean verifyCertificate(byte[] certBytes, String sigAlgorithm, byte[] keyBytes) throws Exception{
ByteArrayInputStream bIn;
bIn = new ByteArrayInputStream(certBytes);
CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
Certificate cert = fact.generateCertificate(bIn);
PublicKey k = cert.getPublicKey();
X509CertificateHolder certHldr = new X509CertificateHolder(certBytes);
certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BCPQC").build(k));
System.out.println(cert);
KeyFactory keyFactory = KeyFactory.getInstance(k.getAlgorithm(), "BCPQC");
PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); // ERROR at this line
/*_______________________________________________________________________________________________________________*\
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source)
at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384)
at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32)
at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
_________________________________________________________________________________________________________________
/* */
Signature signer = Signature.getInstance(sigAlgorithm, "BCPQC");
signer.initSign(privKey);
signer.update(certBytes);
byte[] sig = signer.sign();
signer.initVerify(cert);
signer.update(certBytes);
signer.verify(sig);
return true;
}
}
TL;DR: 使用 PQC 签名方案(例如 BC 使用 内置 JarSigner 提供的 XMSS 对 .jar 文件进行签名可能的。但是,可以编写一个 JarSigner,然后就可以了。
尽管 Oracle 为加密提供程序(例如 BC)提供了 JCA/JCE 开箱即用的集成,但 JarSigner 不使用那些已注册的提供程序进行签名或验证。 考虑到 JarSigner:支持的算法是硬编码的,因此无法扩展。
将 JarSigner 与 BC Provider 一起使用的唯一方法是完全重建它。但是不推荐这样做。
对于那些不怕劫持 jdk 源代码的人,您的项目必须 "override" 来自 jdk 的以下 classes:
- 算法Id
- 属性
- 内容信息
- EndEntityChecker
- HttpTimestamper
- 无效的 JarIndexError
- 贾伦特里
- JarException
- JarFile
- JarIndex
- JarInputStream
- JarOutputStream
- JarSigner
- JarVerifier
- JavaUtilJarAccess
- JavaUtilJarAccessImpl
- JavaUtilZipFileAccess
- 主要 (jdk.jartool.sun.security.tools.jarsigner)
- 清单
- ManifestEntryVerifier
- Pack200
- PKCS7
- PKIX 验证器
- 资源
- Resources_ja
- Resources_zh_CN
- SharedSecrets
- SignatureFileVerifier
- 签名者信息
- 简单验证器
- TimestampedSigner
- 时间戳
- TimestampToken
- TS 响应
- 验证者
- VersionedStream
因此将原始代码复制粘贴到您的项目中,并删除您劫持的所有 classes 的导入,这样您的 "custom" classes 将被采用而不是官方的.
注意:上面提到的大多数classes都可以在java.base模块中找到,尽管有些在jdk.jartool模块中(例如JarSigner本身)。
克隆 jdk 的必要部分以使 JarSigner 工作后,您终于可以继续实施 BC 提供程序和 PQC 签名方案的支持,尤其是 XMSSMT 和 SPHINCS,因为
请注意,对于已签名的 jar 文件的 the verification,JarVerifier 采用签名块文件的文件扩展名,并检查其是 .RSA、.DCA 还是 .EC。因此,您必须添加 .XMSS .XMSSMT .SPHINCS256 等。您还必须在 parseSignedData 方法中告诉 PKCS7 class 使用 BC 证书生成器。 还有一些其他的东西要改变(例如 AlgorithmID)我不记得了,但是一旦你完成了所有必要的步骤,你的 JarSigner 除了使用 "normal" RSA DCA 和 EC 签名和验证。
很遗憾,我无法与您分享最终的源代码,但我会尽可能多地回答您的问题。