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

南笙酒味 提交于 2019-12-12 09:56:40

问题


I am trying to use JarSigner to sign .jar files with XMSS Signatures. With the use of the JCA/JCE Post-Quantum Cryptography Provider from BouncyCastle it is possible to generate XMSS and XMSSMT KeyPairs programmatically (example). In order to use JarSigner it is, as far as I know, crucial to provide a KeyStore and the alias of the entry one wants to sign its code with: jarsigner -keystore myKeystore -storetype JKS -storepass password -keypass password myjarfile.jar keystoreEntryAlias The KeyStore entry contains the Public/Secret KeyPair and the assosiated X.509 Certificate.

The 'normal' way of signing a Jar file with the JarSigner is as follows:

  1. Use keytool to generate the Public/Secret KeyPair and a Certificate then store them in a KeyStore (keytool -genkeypair -alias keystoreEntryAlias -keyalg RSA -sigalg SHA256withRSA -dname CN=MyCompanyName -storetype JKS -keypass password -keystore mykeystore.jks -storepass password)
  2. Use JarSigner to sign the .jar using the SecretKey stored in mykeystore.jks with the alias keysotreEntryAlias (jarsigner -keystore mykeystore.jks -storetype jks -storepass passeword -keypass password myjarfile.jar keystoreEntryAlias)

In order to Sign my file with an XMSS Key I have theoretically two possibilities:

  1. Use BCPQC to create XMSS KeyPairs programmatically, store them in mykeystore and use jarsigner -keystore mykeystore -alias xmss via CLI to sign my file.
  2. Use BCPQC-Provider with the keytool in order to directly via CLI generate XMSS KeyPairs and store them in mykeystore (keytool would here need 2 more arguments: -providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider and -providerpath C:\Path\to\BouncyCastle\provider\bcprov-jdk15on-160.jar) Then sign the file with the keystore entry using JarSigner

Sadly I run into problems with both possibilities:

  1. As I have not found a way yet to use JarSigner without CLI I need to put my generated XMSS KeyPairs in a KeyStore, but therefor I need the Certificate which includes the Public XMSS Key. BouncyCastle does provide a X.509CertificateBuilder which can be used to generate the needed Certificate, however something seems to go wrong if you take a look at the generated Certificate, especially at Signature Algorithm and Public Key(Source code at bottom [CertificateBuilderExample], using XMSSMT)
  2. It appears keytool only uses the init(int) overload from BCPQCProvider and XMSSKeyPairGeneratorSpi rejects that; it wants AlgorithmParameterSpec specifically XMSSParameterSpec, or no init at all -- and if I try the latter, it does generates a keypair, but the resulting keys can't be encoded and thus can't be stored in KeyStore.

My Question would now be:

Does anyone know of a way to sign .jar Files using XMSS / XMSSMT with JarSigner and can provide a more or less detailed explanation on what he/her did right that I did wrong? Or if I was wrong about anything I mentioned above provide a correction and point out a way to do so ?


UPDATE 1: I am now able, with the use of another X509CertificateGenerator (Source code at bottom [X509CertificateGenerator]) and intel gathered from here, here and here, to successfully sign jar files programmatically with RSA provided from BouncyCastle (Source code for signing at bottom [RSA_JarSigner]).

If I try to apply the same scheme used to sign with RSA to sign with XMSS or XMSSMT I run into a JarSignerException: Error in signer materialscaused by NoSuchAlgorithmException: unrecognized algorithm name: XMSS (Source code for XMSS/XMSSMT at bottom [SignXMSS] [SignXMSSMT].

Hopefully someone can help me figure out where the problem is!


UPDATE 2: It seems the problem with the generated XMSS (or XMSSMT) certificates is due to the fact that the entry for the Signature Algorithm (which is SHA256withXMSS) is passed as an ASN1ObjectIdentifier which is unknown to the system yet. Thus I did some research to see if BouncyCastle does not by accident have an XMSS Certificate Generator laying around somewhere.. Bingo, here is one!

I shortened the code a bit and came up with 1 generator and 1 verifier (Source code at bottom [XMSSGen] [XMSSVer]. The generator tho gives me the same Certificates I already got with the other methods (such as [X509CertificateGenerator]). The verifier sadly prompts me this ugly error: 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)

Maybe someone has an idea where it comes from / how to fix it. In order to see if BC it self can work with its own created XMSS Certificates.

Edit: One problem with the verifier: PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); why would we need a private Key to verify the Certificate ? Makes no sense - just remove it and it works ^^ (or at least should)


UPDATE 3: I finally managed to get the the verifier to work properly, so I am currently able to produce and verify XMSS Certificates. I am also able to store XMSS KeyPairs with a Certificate containing the PublicKey in a KeyStore. Though I am still not able to sign any .jar file with.

Now comes something a bit strange: I am able to sign things with the BC XMSS KeyPairs (of course I am, thats what they got made for) tho if I save them (or at least the PrivateKey, as he is required to sign stuff) and reload it afterwards to sign stuff again with it, it does not work. Neither if I store them in a KeyStore and retrieve them nor if I save the Key as encoded Bytes to a file and load them again. (If your interested in the code, just comment and I'll post it here)

My suggestion is: as the XMSS Signature Scheme requires a state (the state of the OTS already used) to be saved, that this state somehow cannot be retrieved from the PrivateKey as it is loaded again (whether from KeyStore or file doesnt matter) and thus cannot be used to sign something with.


[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;
    }
}

回答1:


TL;DR: Signing .jar Files with PQC Signature schemes such as XMSS provided by BC using the built in JarSigner is not possible. However one can wright its one JarSigner which then can.

Although Oracle provides with JCA/JCE an out of the box integration for Crypto Provider such as BC those registered providers are not used from JarSigner neither for signing nor for verifying. Considering the JarSigner: the supported algorithms are hard-coded and can thus not be extended.

The only way to use JarSigner with the BC Provider is to rebuild it entirely. However this is not recommended.

For those which are not afraid of hijacking jdk source code, your project has to "override" the following classes from the jdk:

  • AlgorithmId
  • Attributes
  • ContentInfo
  • EndEntityChecker
  • HttpTimestamper
  • InvalidJarIndexError
  • Jarentry
  • JarException
  • JarFile
  • JarIndex
  • JarInputStream
  • JarOutputStream
  • JarSigner
  • JarVerifier
  • JavaUtilJarAccess
  • JavaUtilJarAccessImpl
  • JavaUtilZipFileAccess
  • Main (jdk.jartool.sun.security.tools.jarsigner)
  • Manifest
  • ManifestEntryVerifier
  • Pack200
  • PKCS7
  • PKIXValidator
  • Resources
  • Resources_ja
  • Resources_zh_CN
  • SharedSecrets
  • SignatureFileVerifier
  • SignerInfo
  • SimpleValidator
  • TimestampedSigner
  • Timestamper
  • TimestampToken
  • TSResponse
  • Validator
  • VersionedStream

Therefor copy paste the original code into your project and remove the import of all classes which you hijack so your "custom" classes will be taken instead of the official ones.

Note: Most of the classes mentioned above can be found in the java.base module, although some are in the jdk.jartool module (such as the JarSigner itself).

After cloning the necessary part of the jdk to get the JarSigner to work you can finally move on implementing your BC provider and the support of the PQC Signature Schemes, especially XMSSMT and SPHINCS, as for the moment it seems like XMSS has some major problems.

Note that for the verification of a signed jar file the JarVerifier takes the file extension of the signature block file and checks rather if its .RSA, .DCA or .EC. So you will have to add .XMSS .XMSSMT .SPHINCS256 etc. You will also have to tell the PKCS7 class in the parseSignedData method to use a BC certificate generator. There are a few other things to change as well (e.g. AlgorithmID) which I don't remember, but once you have done all the necessary steps, your JarSigner is able to sign and verify .jar files using BC in addition to using the "normal" RSA DCA and EC signing and verifying.

Sadly I will not be able to share the final Source Code with you, although I will try answer as much of your questions as possible.



来源:https://stackoverflow.com/questions/53482698/how-to-sign-a-jar-file-using-xmss-pqc-signature-scheme-with-jarsigner

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!