简体   繁体   中英

Decode string with PKC7 in Java?

There is the same question here: Decode string with PKC7 in Java I tried the solution described there, but obviously it is a little outdated, because in the current bouncycastle library there is no org.bouncycastle.openssl.PEMReader class, but org.bouncycastle.openssl.PEMParser. and when i used it in the example:

        PEMParser pr = new PEMParser(sr);
        X509Certificate cert = (X509Certificate)pr.readObject();

i got an exception:

Exception in thread "main" java.lang.ClassCastException: class org.bouncycastle.asn1.cms.ContentInfo cannot be cast to class java.security.cert.X509Certificate

I would like just to ask someone to provide solution of decoding PKCS#7 with the current Bouncycastle library.

The Maven part for Bouncycastle looks like this

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.58</version>
        </dependency>

This is the PKCS#7 that i am trying to decode:

 -----BEGIN PKCS7-----
MIIC5wYJKoZIhvcNAQcCoIIC2DCCAtQCAQExADALBgkqhkiG9w0BBwGgggK8MIICuDCCAl6gAwIB
AgIQXCpspdawOszPHWPu6BzK7DAKBggqhkjOPQQDAjBAMQswCQYDVQQGEwJERTEUMBIGA1UEChML
U29mdHdhcmUgQUcxGzAZBgNVBAMTElFBIFRoaW4tRWRnZSBDQSBHMTAeFw0yMjA2MjIwNzM1MDJa
Fw0yNTA2MjIwNzM1MDJaMCUxIzAhBgNVBAMTGlJlcXVlc3RlZCBUZXN0IENlcnRpZmljYXRlMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjahV4/HclaS48gxI6ojGk1WZsBr+oS2PwAG5
abWG1ilmOGakfSoJt5exz3+vfbqX9Wewge7kE+2ehM3kyhLxtRLc9o2IbvbRsonyncIhb0SqJ+6j
oTK8b7Dd1XwTFNLMNpQK2trY6c0ScBrdJarPv0QKszlpJHpFCCjozlaExdFKeYINUzSN6UKMd9Kw
L6T/3QPOQSBLVAwKnBC8dZnxmoG8Nr0XOJE0FBDISnB/PkE70w8bTqquCCqdWInFZJkZog+QsYBu
LDZq5ODeoUfSrkfnrR1xudgS5/OYRsU5jfwU5h4D1bM1MSQAMseOalFO+Iv5tm4tszNF3HNwSn8N
KQIDAQABo4GJMIGGMBMGA1UdJQQMMAoGCCsGAQUFBwMCMBMGA1UdIwQMMAqACEhBtjq9T25cMAsG
A1UdDwQEAwIFoDBNBgNVHR8ERjBEMEKgQKA+hjxodHRwOi8vZHAuMzgucWEuZ28ubmV4dXNncm91
cC5jb20vY3JsL3FhX3RoaW4tZWRnZV9jYV9nMS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIhALgVQ4GE
NDd3KkjDjDE1a3Kn5FzjTWVjDc4ZdCsJvm1yAiBHdeGu8Sklq57MM1eSMMS7C4KvAYepzPkM4/9s
vgRT8zEA
-----END PKCS7-----

PKCS7 and its successor CMS is a wide-ranging standard that includes many types of messages. Some of these messages can contain one or more (X.509) certificate(s). If your PKCS7/CMS is actually a SignedData containing certificate(s), you don't need BouncyCastle at all; you can directly read the certificate(s) with standard Java CertificateFactory as described in the introductory section of the javadoc .

That is actually what the hacky code in the Q you link does; by putting a false PEM label of BEGIN/END CERTIFICATE on data that is in fact NOT a certificate, but actually a PKCS7/CMS SignedData containing a certificate, it causes the now-obsolete PEMReader code to call CertificateFactory internally. With more recent PEMParser that wouldn't work; you must parse it as PKCS7/CMS and then resolve it yourself. (Even 1.58 isn't exactly current, it's from 2017.) If you want to go that way, the following example reads the data from that Q (correctly labelled as BEGIN/END PKCS7, NOT faked as CERTIFICATE) and extracts the one cert:

    String in = new String(Files.readAllBytes(Paths.get(args[0])) );
    // import org.bouncycastle.asn1.* and ...cms.* (not superseded ...pkcs.*)
    // and of course org.bouncycastle.openssl.PEMParser 
    @SuppressWarnings("resource") // I don't bother closing the PEMParser
    ContentInfo ci = (ContentInfo) new PEMParser(new StringReader(in)).readObject();
    if( !ci.getContentType().equals(CMSObjectIdentifiers.signedData) ) 
        throw new Exception ("not SignedData"); // or other handling as suitable
    SignedData sd = SignedData.getInstance( ci.getContent() );
    // this takes the first/only cert and fails if none; could handle missing
    // and/or loop over multiple looking for or selecting the desired one or ones
    byte[] raw = ((ASN1Sequence)sd.getCertificates().getObjects().nextElement()) .getEncoded();
    X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509")
            .generateCertificate(new ByteArrayInputStream(raw));
    System.out.println (cert.toString());

Numerous variations are possible depending on exactly what you need to handle.

Note SignedData.certs is an ASN.1 SET and arbitrarily ordered. If used for eg a rootlist this doesn't matter, but if used for a cert chain -- as many CAs did in ancient times -- the end-entity or 'leaf' cert, although logically first in the chain, may not be physically first in the PKCS7/CMS. In recent years CAs more often just use a sequence of (individual) PEM certs, which does preserve order -- and which CertificateFactory and keytool etc also handles.


Added: you now disclose you (also?) want the certificate in PEM form (the textlike form beginning -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- ). This is trivial with Bouncy; just do

JcaPEMWriter w = new JcaPEMWriter (/*suitable Writer to a file or whatever */)
w.writeObject(cert); 
w.close(); // or w.flush(); to keep underlying writer/stream open

or if you only need the PEM and aren't doing anything cryptoish with the cert object, more simply

PemWriter w = new PemWriter (/*suitable Writer as above */);
w.writeObject (new PemObject ("CERTIFICATE",raw)); 
w.close(); // ditto 

For the standard-Java case you must do some of the work yourself:

// create or obtain a suitable Writer to file or whatever as w
w.write("-----BEGIN CERTIFICATE-----" + eol
    + Base64.getMimeEncoder().encodeToString(cert.getEncoded()) + eol
    + "-----END CERTIFICATE-----" + eol);
// w.flush() or w.close() if needed depending on the Writer

// or for a PrintStream (like System.out) optionally like
p.println("-----BEGIN CERTIFICATE-----");
p.write(Base64.getMimeEncoder().encode(cert.getEncoded());
p.println(); 
p.println("-----END CERTIFICATE-----");

// in principle eol should be appropriate for your output
// e.g. System.getProperty("line.separator") for a local file
// but possibly different for data sent someplace else;
// in practice PEM data usually works with either CRLF or LF 
// on _all_ systems (and if not are usually easy to fix)

// _some_ systems even accept the base64 on one unbroken line
// as from Base64.getEncoder() rather than .getMimeEncoder() 
// but I wouldn't suggest relying on that, it is nonstandard

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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