'Bouncycastle how to envelop the signed digest it in the original document with java?

In connection with this issue: https://github.com/bcgit/bc-java/issues/1021

I am looking for the best method to envelope the signed hash of a document inside the original document.

Here I try to describe as clearly as possible the use case I am encountering with the CADES type signature:

enter image description here

Here the code i use, but the the validation of the signature is always "The signature is not valid or corrupted":

public class EnvelopedHashToFileCades {

    public static ASN1ObjectIdentifier SIGNATURE_ALGORITHM_OID = PKCSObjectIdentifiers.sha256WithRSAEncryption;
    
    private static Provider provider;
    private static JcaX509CertificateConverter certHolderConverter = new JcaX509CertificateConverter().setProvider ("BC");
    private static CertificateFactory certFactory;
    private static ASN1ObjectIdentifier signatureAlgorithmOid;
    
    static {
        provider = new BouncyCastleProvider();      
        Security.addProvider(provider);
        certHolderConverter = new JcaX509CertificateConverter().setProvider("BC");
        signatureAlgorithmOid = PKCSObjectIdentifiers.sha256WithRSAEncryption;
        try {
            certFactory = CertificateFactory.getInstance("X.509", "BC");
        } catch (CertificateException | NoSuchProviderException e) {
            System.err.println(e.getMessage());
        }   
    }
    
    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        Security.addProvider(new BouncyCastleProvider());

        Paths.get("target/output").toFile().mkdirs();

        java.nio.file.Path pdf = Paths.get("pom.xml");
        java.nio.file.Path p7m = Paths.get("target/output", String.format("pom-%s.p7m", System.currentTimeMillis()));

        byte[] data = Files.readAllBytes(pdf);
        boolean detached = false;
        
        //CadesSignature obj = new CadesSignature();
        //byte[] pkcs7 = obj.sign(data, detached);
        
        String encodedCertificateBase64 = args[0];
        String encodedSignatureBase64 = args[1];
        
        byte[] pkcs7 = signCADES(data, detached, encodedCertificateBase64, encodedSignatureBase64);

        FileOutputStream fos = new FileOutputStream(p7m.toFile());
        fos.write(pkcs7);
        fos.close();
        
        System.out.println("Done. Result written to " + p7m.toFile());

    }
    
    /**
     * @param data
     * @param detached
     * @param encodedCertificateBase64 e' il certificato in base64
     * @return
     * @throws CMSException
     * @throws OperatorCreationException
     * @throws IOException
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws CertificateException 
     */
    public static byte[] signCADES(byte[] data, boolean detached, String encodedCertificateBase64, String encodedSignatureBase64) throws CMSException, OperatorCreationException, IOException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException {
        
        /*
         * "encodedCert" e' il certificato in base64, come recuperato dalla chiamata GET /certificates/{certificateId}
         */     
        String encodedCert = "-----BEGIN CERTIFICATE-----\n" + encodedCertificateBase64 + "\n-----END CERTIFICATE-----";
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        X509Certificate signingCertificate = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(encodedCert.getBytes()));
    
        /*
         * create the signer
         */
        ContentSigner contentSigner = new MyContentSigner(SIGNATURE_ALGORITHM_OID, getDigestAlgorithm(SIGNATURE_ALGORITHM_OID), encodedSignatureBase64);
            
            
        CMSSignedData signedData = doCadesSign(data, signingCertificate, contentSigner, detached);
        System.out.println("\nDetached? : "  + signedData.isDetachedSignature());
    
        return signedData.getEncoded();
        
    }
    
    /**
     * Builds a CAdES signature for the relevant content according to https://tools.ietf.org/html/rfc3852 and https://tools.ietf.org/html/rfc5126
     * 
     * signedAttrs field must contain 5 attributes:
     * 
     *   - contentType (1.2.840.113549.1.9.3) with value 1.2.840.113549.1.7.1 (data)
     *   - messageDigest (1.2.840.113549.1.9.4)
     *   - signingCertificateV2 (1.2.840.113549.1.9.16.2.47)
     *   - signingTime (1.2.840.113549.1.9.5)
     *   - cmsAlgorithmProtection (1.2.840.113549.1.9.52), see RFC6211
     *   
     * e.g. https://lapo.it/asn1js/#MYIBbzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMTAxMDEyMDI5MzJaMC0GCSqGSIb3DQEJNDEgMB4wDQYJYIZIAWUDBAIBBQChDQYJKoZIhvcNAQELBQAwLwYJKoZIhvcNAQkEMSIEIJgNoeVtRG13TQ4LVsbwRg5rL4iNPHlxQMoDOtrL2nQJMIHUBgsqhkiG9w0BCRACLzGBxDCBwTCBvjCBuwQgGL-vc5NGF_AYwdnVdyQpHSELDEuDzxkX9GCjiIhI-A0wgZYwgY6kgYswgYgxCzAJBgNVBAYTAklUMRUwEwYDVQQKDAxJTkZPQ0VSVCBTUEExIjAgBgNVBAsMGUNlcnRpZmljYXRvcmUgQWNjcmVkaXRhdG8xFDASBgNVBAUTCzA3OTQ1MjExMDA2MSgwJgYDVQQDDB9JbmZvQ2VydCBGaXJtYSBRdWFsaWZpY2F0YSAyIENMAgMC17w
     *
     * 
     * @param data
     * @param signingCertificate
     * @param contentSigner
     * @param detached
     * @return
     * @throws CMSException
     * @throws CertificateEncodingException
     * @throws OperatorCreationException
     * @throws IOException
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     */
    private static CMSSignedData doCadesSign(byte[] data, X509Certificate signingCertificate, ContentSigner contentSigner, boolean detached) throws CMSException, CertificateEncodingException, OperatorCreationException, IOException, NoSuchAlgorithmException, NoSuchProviderException {
        
        List<X509Certificate> certs = Arrays.asList(signingCertificate);
    
        DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();

        /*
         * The DefaultSignedAttributeTableGenerator build a signedAttributes object with: 
         * 
         * - contentType (1.2.840.113549.1.9.3)
         * - messageDigest (1.2.840.113549.1.9.4)
         * - signingTime (1.2.840.113549.1.9.5)
         * - cmsAlgorithmProtection (1.2.840.113549.1.9.52)
         * 
         * We need to add the signingCertificateV2 attribute
         * 
         */
        Hashtable<ASN1ObjectIdentifier, Attribute> attributes = new Hashtable<>();
    
        ASN1ObjectIdentifier digestAlgorithm = getDigestAlgorithm(contentSigner.getAlgorithmIdentifier().getAlgorithm());
        Attribute sc = getSigningCertificateAttribute(digestAlgorithm, signingCertificate);
        attributes.put(sc.getAttrType(), sc);
    
        CMSAttributeTableGenerator signedAttributeTableGenerator = new DefaultSignedAttributeTableGenerator(new AttributeTable(attributes));
        
        SignerInfoGenerator signerInfoGenerator = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider).
                setSignedAttributeGenerator(signedAttributeTableGenerator).
                build(contentSigner, signingCertificate);
        // CMSSignedDataGenerator handles the generation of a pkcs7-signature message
        CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
        cmsGenerator.addCertificates(new JcaCertStore(certs));
        cmsGenerator.addSignerInfoGenerator(signerInfoGenerator);
        
        CMSTypedData cmsData= new CMSProcessableByteArray(data);
        
        CMSSignedData signedData = cmsGenerator.generate(cmsData, !detached);
        
        return signedData;
    
    }
    
    /**
     * Method to build signing-certificate signed attribute. 
     * 
     * @param digestAlgorithm
     *            the digest algorithm to be used
     * @param certificate
     *            The signing certificate to be append
     * @throws IOException 
     * @throws CertificateEncodingException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     */
    public static Attribute getSigningCertificateAttribute(ASN1ObjectIdentifier digestAlgorithm, X509Certificate certificate) throws CertificateEncodingException, IOException, NoSuchAlgorithmException, NoSuchProviderException {

        X509CertificateHolder certHolder = new X509CertificateHolder(certificate.getEncoded());

        X500Name issuerX500Name = certHolder.getIssuer();
        GeneralName generalName = new GeneralName(issuerX500Name);
        GeneralNames generalNames = new GeneralNames(generalName);
        BigInteger serialNumber = certHolder.getSerialNumber();
        IssuerSerial issuerSerial =  new IssuerSerial(generalNames, serialNumber);

        byte[] certDigest = MessageDigest.getInstance(digestAlgorithm.getId(), "BC").digest(certificate.getEncoded());

        ESSCertIDv2 essCertIdv2 = new ESSCertIDv2(new AlgorithmIdentifier(digestAlgorithm), certDigest, issuerSerial);
        SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIdv2);
        return new Attribute(id_aa_signingCertificateV2, new DERSet(signingCertificateV2));

    }

    /**
     * @param signatureAlgorithm
     * @return
     */
    public static ASN1ObjectIdentifier getDigestAlgorithm(ASN1ObjectIdentifier signatureAlgorithm) {
        if (PKCSObjectIdentifiers.sha256WithRSAEncryption.equals(signatureAlgorithm)) {
            return NISTObjectIdentifiers.id_sha256;
        }
        throw new IllegalArgumentException("unsupported signature algorithm " + signatureAlgorithm);
    }
    
    
    /**
     * An implementation of the BouncyCastle ContentSigner interface
     * 
     * 
     * @author YYI9386
     *
     */
    private static class MyContentSigner implements ContentSigner {
                
        private AlgorithmIdentifier signatureAlgorithm;
        private MessageDigest messageDigest;
        private String encodedSignatureBase64;

        private ByteArrayOutputStream dataToSign;

        /**
         * 
         */
        public MyContentSigner(ASN1ObjectIdentifier signatureAlgorithmId, ASN1ObjectIdentifier digestAlorithmId, String encodedSignatureBase64) {

            String signatureAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(signatureAlgorithmId);
            signatureAlgorithm = (new DefaultSignatureAlgorithmIdentifierFinder()).find(signatureAlgorithmName);

            try {
                messageDigest = MessageDigest.getInstance(digestAlorithmId.getId(), "BC");
            } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
                throw new IllegalArgumentException("Unsupported digest algorithm " + digestAlorithmId.getId());
            }
            
            this.encodedSignatureBase64 = encodedSignatureBase64;

        }

        @Override
        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return signatureAlgorithm;
        }

        @Override
        public OutputStream getOutputStream() {
            dataToSign = new ByteArrayOutputStream();
            return dataToSign;
        }

        @Override
        public byte[] getSignature() {

            byte[] data = dataToSign.toByteArray();

            System.out.println("\n*** The dataToSign are:\n" + Base64.getEncoder().encodeToString(data));
            
            byte[] hash = messageDigest.digest(data);

            System.out.println("\n*** The hash to sign is:\n" + Base64.getEncoder().encodeToString(hash) + "\n");

            /*
             * "encoded" e' il valore della firma in base64, come recuperato dalla chiamata POST /certificates/{certificateId}/sign
             */
            //String encoded = CadesSignature.readValue("Enter base64 encoding of the signature value");
            String encoded = this.encodedSignatureBase64;
            return Base64.getDecoder().decode(encoded);

        }

    }   
}


any advise o suggestion i smore than welcome.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source