使用Java将外部PKCS1字节数组和证书添加到CMS容器

时间:2014-03-18 04:04:14

标签: java digital-signature bouncycastle pkcs#7

我们有创建PKCS1 v2.1数字签名的客户端应用程序(Applet和Silverlight)。数字签名创建为PKCS1,因为原始内容未下载到客户端,只有内容' hash被发送到客户端以节省带宽。

我们正在尝试创建PKCS7 / CMS容器服务器端based on the information from this post

  1. 阅读证书并加​​载为X509Certificate类型
  2. 将PKCS1签名读取为base64并加载为字节数组
  3. 实例化新的ASN1ObjectIdentifier并设置PKCS1 OID(1.2.840.113549.1.1)
  4. 使用asn1对象和signare byte []作为参数创建新的CMSTypedData CMSProcessableByteArray
  5. 创建新的CMSSignedGenerator并添加证书
  6. 使用CMSTypedData类型
  7. 创建新的CMSSignedData作为分离的签名

    但是,当进入第5步和第6步时,由于BC CMSSignedGenerator和CMSSignedData类不支持在没有私钥的情况下添加签名者,因此

    CMS创建:

        // 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();
    
    }
    

    有关如何完成CMS容器的任何想法?也许使用AttributeTable为时间戳等添加多个OID,但这似乎也不起作用。

1 个答案:

答案 0 :(得分:3)

在查看此参考项目https://code.google.com/p/j4ops/后,我找到了答案。本指南也是一个很大的帮助,虽然它专门处理使用iText的PDF,它使用BC来自加密操作:http://itextpdf.com/book/digitalsignatures20130304.pdf。诀窍是通过实现使用符号(byte [] toEncrypt)方法的签名者接口将签名操作委托给外部提供者(PKCS11,PKCS12等)。这样,可以设置提供程序,然后只需调用sign方法,并保留有关如何签署提供程序本身的实现细节。

Bouncy Castle使用带有SignerInf内部类的CMSSignedDataGenerator类分别构建CMS容器和签名者信息。因此,诀窍是构建一个不需要私钥的SignerInf对象,因为应该将sign()操作委托给提供者。特别是在使用智能卡时,私钥可能甚至不可用。此外,在签署哈希和构建CMS容器时,需要考虑需要添加为签名属性和/或未签名属性的信息。所以这些是解决问题的基本步骤:

// 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();