Adding external PKCS1 byte array and Certificate t

2019-05-11 21:19发布

问题:

We have client applications (Applets and Silverlight) that create a PKCS1 v2.1 digital signature. The digital signature is created as PKCS1 since the original content is not downloaded to the client, only the contents' hash is sent to the client to conserve bandwidth.

We are attempting to create a PKCS7/CMS container server side, based on the information from this post:

  1. Reading certificate and loading as X509Certificate type
  2. Reading PKCS1 signature as base64 and loading as byte array
  3. Instantiating new ASN1ObjectIdentifier and setting PKCS1 OID (1.2.840.113549.1.1)
  4. Creating new CMSTypedData CMSProcessableByteArray, using asn1 object and signare byte[] as parameters
  5. Create new CMSSignedGenerator and add certificates
  6. Create new CMSSignedData as detached signature using CMSTypedData type

However, when getting to steps 5 and 6 things break, as the BC CMSSignedGenerator and CMSSignedData classes do not support adding signers without the private key:

CMS creation:

    // Add BC to environment
    Security.addProvider(new BouncyCastleProvider());

    // Read certificate and convert to X509Certificate
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    Path certPath = Paths.get("C:\\MyCertificate.cer");
    byte[] certData = Files.readAllBytes(certPath);
    InputStream in = new ByteArrayInputStream(certData);
    X509Certificate cert = (X509Certificate)certFactory.generateCertificate(in);

    // Add signer certificates to List and add them to store
    List<X509Certificate> certList = new ArrayList<X509Certificate>();
    certList.add(cert);
    Store certs = new JcaCertStore(certList);

    // Get signature in Base64, decode and convert to byte array
    // Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
    String signatureBase64 = "gjTbsD0vSOi6nMlRVbpTLRQ5j+g2h8iEH1DgQx93PDBuwzWT47urKxMAS+75dAhQrkreLt9TGZaDN85e5xEpIF12mK1G+AgCNc370I1bjxOvUU67IVxHkZ+IX8kzSiD2uNuQtk3IrwUqyL30TIo+LDAXmY1AQVZwXAaOYG4bXxI=";
    BASE64Decoder decoder = new BASE64Decoder();
    byte[] signatureByte = decoder.decodeBuffer(signatureBase64);

    // Instantiate new ANS1ObjectIdentifier to identify PKCS1 signature
    ASN1ObjectIdentifier asn1OidPkcs1 = new ASN1ObjectIdentifier("1.2.840.113549.1.1");

    // Table generator
    /*AttributeTable attrT = new AttributeTable();
    SimpleAttributeTableGenerator sAttrTGen = new SimpleAttributeTableGenerator();*/

    // Instantiate new CMSProcessable object
    CMSTypedData msg = new CMSProcessableByteArray(asn1OidPkcs1, signatureByte);

    // Instantiate new CMSSignedDataGenerator object
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

    // ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").s
    gen.addCertificates(certs);
    CMSSignedData sigData = gen.generate(msg, false);


    // BASE64Encoder encoder = new BASE64Encoder();
    new File("C:\\MyCMS.p7s");
    FileOutputStream fileOuputStream = new FileOutputStream("C:\\Users\\gregwerner\\Documents\\Archivos\\miFirma.p7s"); 
    fileOuputStream.write(sigData.getEncoded());
    fileOuputStream.flush();
    fileOuputStream.close();

}

Any ideas on how to complete CMS container? Perhaps using AttributeTable to add muliple OID's for time stamps, etc., but that doesn't seem to work either.

回答1:

I found the answer after looking at this reference project https://code.google.com/p/j4ops/. This guide was also a big help although it deals specifically with PDFs using iText which use BC from crypto operations: http://itextpdf.com/book/digitalsignatures20130304.pdf. The trick is to delegate the signing operation to an external provider (PKCS11, PKCS12, etc) by implementing a Signer interface that uses a sign(byte[] toEncrypt) method, for example. This way, the provider can be set and then just call the sign method and leave the implementation details on how to sign to the provider itself.

Bouncy Castle uses a CMSSignedDataGenerator class with a SignerInf internal class to build the CMS container and the signer information, respectively. So the trick is to build a SignerInf object that doesn't need the private key, as sign() operation should be delegated to the provider. The private key may not even be available particularly when using smart cards. Additionally, the information that needs to be added as signed attributes and/or unsigned attributes needs to be considered when signing the hash and when building the CMS container. So these are the basic steps to solve the problem:

// Build the items to encrypt, objects for method parameters would be obtained previously.
byte[] toEncrypt = externalSignerInfoGenerator.getCmsBytesToSign(hash, 
            signingTime, 
            PKCSObjectIdentifiers.data, 
            x509Cert, 
            timeStampToken, 
            ocsp);
// The externalSignerInfoGenerator.getCmsBytesToSign is a method from a re implemention of the 
// SignerInf inner class from CMSSignedDataGenerator and is used to get a byte array from an 
// org.bouncycastle.asn1.ASN1EncodableVector. To build the vector one should add attributes to
// their corresponding OID's using the org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers interface,
// for example:
ASN1EncodableVector signedAttrVector = buildSignedAttributes (hash, signingTime, contentType,
x509Cert, ocspResp);
// This would call the buildSignedAttributes method to build the signed attributes vector
ASN1EncodableVector signedAttrVector = new ASN1EncodableVector();
// Add CMS attributes
signedAttrVector.add (new Attribute(CMSAttributes.contentType, new DERSet (contentType)));
signedAttrVector.add (new Attribute (CMSAttributes.signingTime, new DERSet(new Time (signingTime))));
signedAttrVector.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hash))));
// Not all attributes are considered in BC's CMSAttributes interface, therefore one would have to add 
// an additional step:
signedAttrVector.add(buildOcspResponseAttribute(ocspResp));
// This method would call buildOcspResponseAttribute to add the object as a PKCSObjectIdentifier
protected Attribute buildOcspResponseAttribute (byte[] ocspResp) throws IOException, CMSException {
    return new Attribute (PKCSObjectIdentifiers.id_aa_ets_revocationRefs, 
    new DERSet(DERUtil.readDERObject(ocspResp)));
}  

// Call sign method from provider, such as PKCS11, PKCS12, etc.
byte [] signature = getSignProvider().sign(toEncrypt);
// Now build standard org.bouncycastle.cms.SignerInfoGenerator with hash, signed data 
// and certificate to add to CMS, create attached or detached signature
// create signed envelope
CMSSignedData envdata = externalCMSSignedDataGenerator.generate(false);                
byte[] enveloped = envdata.getEncoded();