繁体   English   中英

如何使用带有JarSigner的XMSS(PQC)签名方案对.jar文件进行签名

[英]How to sign a .jar file using XMSS (PQC) Signature Scheme with JarSigner

我正在尝试使用JarSigner对带有XMSS签名的.jar文件进行签名。 通过使用BouncyCastleJCA / JCE量子后密码学提供程序 ,可以以编程方式生成XMSS和XMSSMT KeyPair (示例) 据我所知,要使用JarSigner,至关重要的是提供一个KeyStore和该条目的别名,该别名要使用以下代码签名: jarsigner -keystore myKeystore -storetype JKS -storepass password -keypass password myjarfile.jar keystoreEntryAlias密钥库条目包含公共/秘密密钥对和关联的X.509证书。

使用JarSigner签名Jar文件的“正常”方式如下:

  1. 使用keytool生成公共/秘密密钥对和证书,然后将它们存储在KeyStore中( keytool -genkeypair -alias keystoreEntryAlias -keyalg RSA -sigalg SHA256withRSA -dname CN=MyCompanyName -storetype JKS -keypass password -keystore mykeystore.jks -storepass password
  2. 使用JarSigner并使用别名为keyotreEntryAlias的mykeystore.jks中存储的SecretKey对.jar进行签名( jarsigner -keystore mykeystore.jks -storetype jks -storepass passeword -keypass password myjarfile.jar keystoreEntryAlias

为了使用XMSS密钥对文件签名,理论上我有两种可能性:

  1. 使用BCPQC以编程方式创建XMSS密钥对,将其存储在mykeystore中,并通过CLI使用jarsigner -keystore mykeystore -alias xmss对我的文件进行签名。
  2. 将BCPQC-Provider与keytool结合使用,以便直接通过CLI生成XMSS KeyPair,并将其存储在mykeystore中(keytool在这里还需要2个参数: -providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider-providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider -providerpath C:\\Path\\to\\BouncyCastle\\provider\\bcprov-jdk15on-160.jar ),然后使用JarSigner使用密钥库条目对文件进行签名

可悲的是,我遇到了两种可能性的问题:

  1. 由于我尚未找到一种无需CLI即可使用JarSigner的方法,因此需要将生成的XMSS密钥对放入密钥库中,但是为此,我需要包含公共XMSS密钥的证书。 BouncyCastle确实提供了X.509CertificateBuilder,可用于生成所需的证书,但是,如果您查看生成的证书,尤其是在签名算法和公钥(位于底部[CertificateBuilderExample]的源代码)中使用XMSSMT)
  2. 看来keytool仅使用BCPQCProvider的init(int)重载,而XMSSKeyPairGeneratorSpi拒绝了该重载; 它想要的是AlgorithmParameterSpec特别是XMSSParameterSpec,或者根本不需要初始化-如果我尝试使用后者,它会生成密钥对,但是生成的密钥无法编码,因此无法存储在KeyStore中。

我的问题现在是:

有谁知道使用XMSS / XMSSMT和JarSigner签名.jar文件的方法,并且可以提供关于他/她做对了什么我做错了的更多或更少的详细解释? 或者,如果我对上述任何事情都不对,请提供更正并指出解决方法?


更新1 :现在,我可以使用另一个X509CertificateGenerator(源代码位于[X509CertificateGenerator]底部)和从此处此处此处收集的intel,使用BouncyCastle提供的RSA以编程方式成功签名jar文件 (在处签名的源代码)底部[RSA_JarSigner])。

如果我尝试应用用于RSA签名的相同方案来使用XMSS或XMSSMT签名,则会遇到JarSignerException: Error in signer materialsNoSuchAlgorithmException: unrecognized algorithm name: XMSS导致的JarSignerException: Error in signer materials NoSuchAlgorithmException: unrecognized algorithm name: XMSS (XMSS / XMSSMT的源代码位于底部[SignXMSS] [SignXMSSMT]。

希望有人可以帮助我找出问题所在!


更新2 :似乎与生成的XMSS(或XMSSMT)证书有关的问题是由于以下事实:签名算法(即SHA256withXMSS)的条目作为ASN1ObjectIdentifier传递给了系统,目前尚不为人所知。 因此,我进行了一些研究,以查看BouncyCastle是否不是偶然地在某处放置了XMSS证书生成器。宾果游戏, 是一个!

我稍微缩短了代码,并提出了1个生成器和1个验证器(源代码位于[XMSSGen] [XMSSVer]底部。生成器tho给了我与其他方法(例如[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密钥对和包含公钥的证书存储在密钥库中。 虽然我仍然无法使用任何.jar文件进行签名。

现在有些奇怪:我可以使用BC XMSS KeyPair对事物进行签名(当然,这就是它们的用途),如果我保存它们(或至少是PrivateKey,因为他需要对事物进行签名) ),然后重新加载,以再次对其进行签名,则无法正常工作。 无论是将它们存储在KeyStore中并进行检索,还是将密钥作为编码的字节保存到文件中并再次加载它们,都不会。 (如果您对代码感兴趣,请发表评论,我会在此处发布)

我的建议是:由于XMSS签名方案要求保存一个状态(已使用的OTS的状态),因此在再次加载该私钥时无论从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签名.jar文件诸如BC由BC使用内置 JarSigner提供的XMSS签名方案。 但是,可以使用它的一个JarSigner来实现。

尽管Oracle通过JCA / JCE为BC等加密提供程序提供了现成的集成,但JarSigner并未使用这些注册的提供程序进行签名或验证。 考虑到JarSigner:受支持的算法是硬编码的,因此无法扩展。

与BC Provider一起使用JarSigner的唯一方法是完全重建它。 但是,不建议这样做。

对于那些不怕劫持jdk源代码的人,您的项目必须“覆盖” jdk中的以下类:

  • AlgorithmId
  • 属性
  • ContentInfo
  • EndEntityChecker
  • HttpTimestamper
  • InvalidJarIndexError
  • 的JarEntry
  • JarException
  • jar文件
  • JarIndex
  • 的JarInputStream
  • 的JarOutputStream
  • 的jarsigner
  • JarVerifier
  • JavaUtilJarAccess
  • JavaUtilJarAccessImpl
  • JavaUtilZipFileAccess
  • 主(jdk.jartool.sun.security.tools.jarsigner)
  • 表现
  • ManifestEntryVerifier
  • Pack200
  • PKCS7
  • PKIXValidator
  • 资源
  • Resources_ja
  • Resources_zh_CN
  • SharedSecrets
  • SignatureFileVerifier
  • 个SignerInfo
  • SimpleValidator
  • TimestampedSigner
  • 戳器
  • 时间戳标记
  • TSResponse
  • 验证器
  • VersionedStream

因此,将原始代码复制粘贴到您的项目中,并删除所有您劫持的类的导入,这样就可以采用“自定义”类而不是正式类。

注意:上面提到的大多数类都可以在java.base模块中找到,尽管有些在jdk.jartool模块中(例如JarSigner本身)。

克隆了jdk的必要部分以使JarSigner正常工作之后,您最终可以继续实施BC提供程序以及PQC签名方案(尤其是XMSSMT和SPHINCS)的支持,因为目前看来XMSS存在一些主要问题

请注意,为了验证签名的jar文件,JarVerifier会使用签名块文件的文件扩展名,而是检查其.RSA,.DCA或.EC。 因此,您将必须添加.XMSS .XMSSMT .SPHINCS256等。还必须告诉parseSignedData方法中的PKCS7类使用BC证书生成器。 我也忘记了其他一些更改(例如AlgorithmID),但是一旦完成所有必要的步骤,您的JarSigner就可以使用BC签名和验证.jar文件,除了使用“常规” RSA DCA和EC签名和验证。

遗憾的是,尽管我将尝试回答您的尽可能多的问题,但是我将无法与您共享最终的源代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM