java - 尝试从证书中获取签名时出现格式错误的内容异常

标签 java security certificate bouncycastle sign

我已经编写了以下代码来使用我的证书存储中的证书来验证文件的签名。但是,当我尝试获取其签名并将其传递给 SignedData 方法时,出现以下异常。

org.bouncycastle.cms.CMSException: Malformed content.
    at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
    at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
    at org.bouncycastle.cms.CMSSignedData.<init>(Unknown Source)
    at VerifyFinal.main(VerifyFinal.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.DERApplicationSpecific
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
    at org.bouncycastle.asn1.cms.ContentInfo.getInstance(Unknown Source)
    ... 9 more

下面是我用来验证文件签名的代码。

Security.addProvider(new BouncyCastleProvider());

            KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
            msCertStore.load(null, null);
            X509Certificate cer = ((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority"));
            PublicKey pubKey = cer.getPublicKey();

            byte[] sigToVerify = cer.getSignature();
            Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
            signature.initVerify(pubKey);

            CMSSignedData cms = new CMSSignedData(cer.getSignature());
            Store store = cms.getCertificates();
            SignerInformationStore signers = cms.getSignerInfos();
            Collection c = signers.getSigners();
            Iterator it = c.iterator();
            while (it.hasNext()) {
                SignerInformation signer = (SignerInformation) it.next();
                Collection certCollection = store.getMatches(signer.getSID());
                Iterator certIt = certCollection.iterator();
                X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
                X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
                if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
                    System.out.println("verified");
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }

如果您需要,下面是我签署文件的方式。

 File file = new File("G:\\Projects\\test.zip");
        fin = new FileInputStream(file);
        byte fileContent[] = new byte[(int) file.length()];


        Security.addProvider(new BouncyCastleProvider());

        KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
        ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
        Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());

        //Sign
        PrivateKey privKey = (PrivateKey) key;
        Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
        signature.initSign(privKey);
        signature.update(fileContent);

        //Build CMS
        X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
        List certList = new ArrayList();
        CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
        certList.add(cert);
        Store certs = new JcaCertStore(certList);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
        gen.addCertificates(certs);
        CMSSignedData sigData = gen.generate(msg, true);

        BASE64Encoder encoder = new BASE64Encoder();

        String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
        System.out.println("Signed content: " + signedContent + "\n");

        String envelopedData = encoder.encode(sigData.getEncoded());
        System.out.println("Enveloped data: " + envelopedData);

在 VOLKERK 的评论之后:

我如何生成签名+数据文件:

public static void main(String[] args) throws Exception {

        // String text = "This is a message";

        // File file = new
        // File("C:\\Users\\mayooranM\\Desktop\\SignatureVerificationTest\\ProcessExplorer.zip");
        // fin = new FileInputStream(file);
        // byte fileContent[] = new byte[(int) file.length()];

        Path filepath = Paths.get("G:\\IntelliJTestProjects\\googleplaces.zip");
        byte[] fileContent = Files.readAllBytes(filepath);

        Security.addProvider(new BouncyCastleProvider());

        KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
        ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
        Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());

        // Sign
        PrivateKey privKey = (PrivateKey) key;
        Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
        signature.initSign(privKey);
        signature.update(fileContent);

        // Build CMS
        X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
        List certList = new ArrayList();
        CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
        certList.add(cert);
        Store certs = new JcaCertStore(certList);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
        gen.addSignerInfoGenerator(
                new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
                        .build(sha1Signer, cert));
        gen.addCertificates(certs);
        CMSSignedData sigData = gen.generate(msg, true);

        BASE64Encoder encoder = new BASE64Encoder();

        String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
        System.out.println("Signed content: " + signedContent + "\n");

        String envelopedData = encoder.encode(sigData.getEncoded());
        System.out.println("Enveloped data: " + envelopedData);

        FileOutputStream fos = new FileOutputStream(
                "G:\\IntelliJTestProjects\\SignedZip.zip");
        fos.write(envelopedData.getBytes());
        fos.close();

    }

我如何验证数据:

public static void main(String[] args) {
        try {
            Security.addProvider(new BouncyCastleProvider());

            Path path = Paths
                    .get("G:\\IntelliJTestProjects\\SignedZip.zip");
            byte[] signedContent = Files.readAllBytes(path);

            String output = new String(signedContent);

            System.out.println("output: " + output);

            CMSSignedData cms = new CMSSignedData(Base64.decode(signedContent));
            Store store = cms.getCertificates();
            SignerInformationStore signers = cms.getSignerInfos();
            Collection c = signers.getSigners();
            Iterator it = c.iterator();
            while (it.hasNext()) {
                SignerInformation signer = (SignerInformation) it.next();
                Collection certCollection = store.getMatches(signer.getSID());
                Iterator certIt = certCollection.iterator();
                X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
                X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
                if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
                    System.out.println("verified");
                }
            }

            CMSProcessable origData = cms.getSignedContent() ;
            byte[] originalContent  = (byte[]) origData.getContent();

             ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(originalContent));
                ZipEntry entry = null;
                while ((entry = zipStream.getNextEntry()) != null) {

                    String entryName = entry.getName();

                    FileOutputStream out = new FileOutputStream(entryName);

                    byte[] byteBuff = new byte[4096];
                    int bytesRead = 0;
                    while ((bytesRead = zipStream.read(byteBuff)) != -1)
                    {
                        out.write(byteBuff, 0, bytesRead);
                    }

                    out.close();
                    zipStream.closeEntry();
                }
                zipStream.close();

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

最佳答案

第 2 部分

好的,现在您已经有了包含 pkcs7 签名数据的文件,让我们尝试检索内容并验证完整性……和有效性。
目标还是不要将整个东西加载到内存中。看起来像 CMSSignedDataParser可以做到。
由于文档说

Note: that because we are in a streaming mode [...] it is important that the methods on the parser are called in the appropriate order.

那么,让我们首先看看到目前为止我们实际得到了什么。为此,我使用了一个包含 Mary had a little lamb 行的文本文件作为输入文件,而不是 .m4v(或您的情况下的 .zip)并将结果传递给 http://lapo.it/asn1js/ (你一定会喜欢这个工具......)

The asn1-structure of the result

因此,实际内容出现在签名数据之前,我们必须按照它们在文件中出现的顺序读取条目。反过来会更容易,但是...... 这个想法是将内容写入目标文件,而不管它是否 checkout 。如果它不只是删除文件。 (缺点:如果它包含例如病毒,则可能会触发病毒扫描程序……太糟糕了。我将由您来处理。)

public class SignedDataTest {
  ... see Part 1

    private static void verify(Path signedFile, Path extractToFile) throws Exception {
        FileInputStream fis = new FileInputStream(signedFile.toFile());

        DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
        CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);

        // we have to read the whole stream sp.getSignedContent().getContentStream()
        // just copy it to the target file
        Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
        // now we can go on with the other stuff.....

        Store certStore = sp.getCertificates();
        // the examples create a new instance of this for each certificate. 
        // I don't think that's necessary, but you might want to look into that...
        JcaSimpleSignerInfoVerifierBuilder verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC");

        for (Object objSigner : sp.getSignerInfos().getSigners()) {
            SignerInformation signer = (SignerInformation) objSigner;
            // as I understand it, there should be only one match ....but anyways....
            for (Object objMatch : certStore.getMatches(signer.getSID())) {
                X509CertificateHolder certHolder = (X509CertificateHolder) objMatch;
                System.out.print("verifying against " + certHolder.getSubject().toString());
                if (signer.verify(verifier.build(certHolder))) {
                    System.out.println(": verified");
                } else {
                    System.out.println(": no match");
                }
            }
        }
    }
}

那么,这实际上是做什么/测试的?它从 pkcs7 signedData 中获取签名者信息,然后再次检查 包含在 signedData 中的证书的散列和签名。还不够好,我和任何其他攻击者都可以将任何证书放在那里;所以我创建了一个新的 KeyPiar,为该 key 对生成一个自签名证书,并将我喜欢的任何 zip 文件放在那里,最好是一个讨厌的网络钓鱼工具。
这很可能是您在代码中使用 KeyStore.getInstance("Windows-MY", "SunMSCAPI") 的原因;您隐式信任的 KeyStore。所以,让我们这样做吧。
我们不是从 signedData 文件中的数据构建 SignerInformationVerifier,而是将现成的 validator 传递给该方法。并且此 validator 已准备好来自 Windows“KeyStore”的证书。顺便说一句:您不能任意混合使用 BC 和 SunMSCAPI 提供程序;但您可以通过这种方式混合使用它们,即让 BC 检查数据完整性,让 SunMSCAPI 检查散列是否已被认为值得信赖的东西签名。

(抱歉,我得走了。我将只发布完整的示例类;虽然关于它还有很多话要说......实际上可以写关于它的书......实际上书籍 关于该主题的文章 ;-) )

public class SignedDataTest {

    private static final File KEYSTORE_FILE = new File("c:\\temp\\Software_View_Certificate_Authority.p12");
    private static final String KEYSTORE_TYPE = "pkcs12";
    private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
    private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";

    private static final Path CONTENT_SRC_PATH = Paths.get("c:\\temp\\test.txt");
    private static final Path CONTENT_TARGET_PATH = Paths.get("c:\\temp\\test-retrieved.txt");
    private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\\temp\\test.txt.signed.pkcs7");

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        doForth();
        andBack();
    }

    private static void doForth() throws Exception {
        KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
        ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
        X500PrivateCredential creds = new X500PrivateCredential(
                (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
                (PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
        );
        createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
    }

    private static void andBack() throws Exception {
        KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
        msCertStore.load(null, null);
        SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("SunMSCAPI")
                .build(((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority")));
        verify(SIGNEDDATA_TARGET_PATH, CONTENT_TARGET_PATH, verifier);
    }

    private static void verify(Path signedFile, Path extractToFile, SignerInformationVerifier verifier) throws Exception {
        FileInputStream fis = new FileInputStream(signedFile.toFile());

        DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
        CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);

        // we have to read the whole stream sp.getSignedContent().getContentStream()
        // just copy it to the target file
        Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
        // now we can go on with the other stuff.....

        Store certStore = sp.getCertificates();
        // the examples create a new instance of this for each certificate. 
        // I don't think that's necessary, but you might want to look into that...

        for (Object objSigner : sp.getSignerInfos().getSigners()) {
            SignerInformation signer = (SignerInformation) objSigner;
            if (signer.verify(verifier)) {
                System.out.println("verified");
                // now(!) you want to keep the target content file
            } else {
                // actually a "org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value"
                // exception will be thrown in case the contents has been altered
                // So, you will need a try-catch(-finally?) construct to delete the target contents file in such cases....
                System.out.println("no match");
            }
        }
    }

    private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
            {
                addSignerInfoGenerator(
                        new JcaSignerInfoGeneratorBuilder(
                                new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
                        ).build(
                                new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
                                creds.getCertificate()
                        )
                );
                addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
                    {
                        add(creds.getCertificate());
                    }
                }));
            }
        };
        try (OutputStream sigOut = gen.open(target, true)) {
            Files.copy(srcfile, sigOut);
        }
    }
}

关于java - 尝试从证书中获取签名时出现格式错误的内容异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35866590/

相关文章:

php - 如何确保文件是 XML 文件

带有Referer的PHP表单安全性

java - Android 上 PEMKeyPair 与 BouncyCasSTLe 的 key 对

.net - 如何获得 MS Office 加载项的安全证书?

java - 如何在不重启设备的情况下重启应用系统?

java - 由于未加载依赖的任务类而导致 Ant 构建失败

asp.net - 反射会带来什么风险? (中等信任度)

java - Ant 属性正则表达式 : problem with\in paths

java - SQL 速度慢,需要帮助。我应该走进内存吗?

C++检查变量以解决越界问题