diff --git a/src/main/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParser.java b/src/main/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParser.java index 3c8db31..84291d0 100644 --- a/src/main/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParser.java +++ b/src/main/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParser.java @@ -33,6 +33,7 @@ import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.operator.OperatorCreationException; @@ -103,7 +104,7 @@ public SignedCertificateMessageParser(@NonNull byte[] cmsMessage) { * The result of parsing process will be immediately available. * * @param cmsSignature base64 encoded detached CMS signature bytes. - * @param cmsPayload base64 encoded CMS message payload. + * @param cmsPayload base64 encoded CMS message payload. */ public SignedCertificateMessageParser(@NonNull byte[] cmsSignature, @NonNull byte[] cmsPayload) { raw = cmsSignature; @@ -128,7 +129,7 @@ public SignedCertificateMessageParser(@NonNull String cmsMessage) { * The result of parsing process will be immediately available. * * @param cmsSignature base64 encoded detached CMS signature string. - * @param cmsPayload base64 encoded CMS message payload string. + * @param cmsPayload base64 encoded CMS message payload string. */ public SignedCertificateMessageParser(@NonNull String cmsSignature, @NonNull String cmsPayload) { raw = cmsSignature.getBytes(StandardCharsets.UTF_8); @@ -141,7 +142,7 @@ public SignedCertificateMessageParser(@NonNull String cmsSignature, @NonNull Str * The result of parsing process will be immediately available. * * @param cmsSignature base64 encoded detached CMS signature bytes. - * @param cmsPayload base64 encoded CMS message payload string. + * @param cmsPayload base64 encoded CMS message payload string. */ public SignedCertificateMessageParser(@NonNull byte[] cmsSignature, @NonNull String cmsPayload) { raw = cmsSignature; @@ -154,7 +155,7 @@ public SignedCertificateMessageParser(@NonNull byte[] cmsSignature, @NonNull Str * The result of parsing process will be immediately available. * * @param cmsSignature base64 encoded detached CMS signature string. - * @param cmsPayload base64 encoded CMS message payload bytes. + * @param cmsPayload base64 encoded CMS message payload bytes. */ public SignedCertificateMessageParser(@NonNull String cmsSignature, @NonNull byte[] cmsPayload) { raw = cmsSignature.getBytes(StandardCharsets.UTF_8); @@ -218,6 +219,14 @@ private void afterPropertiesSet() { } signingCertificate = certificateHolderCollection.iterator().next(); + // Try to extract detached CMS Signature + try { + signature = Base64.getEncoder().encodeToString(repackToDetachedCms(cmsSignedData).getEncoded()); + } catch (IOException | CMSException e) { + signature = null; + log.error("Failed to repack CMS to get detached signature."); + } + // Get signer information and verify signature if (cmsSignedData.getSignerInfos().size() != 1) { log.error("Signed Message contains more than 1 signer information"); @@ -229,7 +238,6 @@ private void afterPropertiesSet() { signatureVerified = signerInformation.verify( new JcaSimpleSignerInfoVerifierBuilder().build(signingCertificate) ); - signature = Base64.getEncoder().encodeToString(signerInformation.getSignature()); } catch (CMSException | OperatorCreationException | CertificateException e) { log.error("Failed to validate Signature"); } @@ -237,6 +245,23 @@ private void afterPropertiesSet() { parserState = ParserState.SUCCESS; } + /** + * Recreates a CMS without encapsulated Data. + * + * @param input input CMS Message + * @return CMS message without encapsulated data. + * @throws CMSException if repacking fails. + */ + private CMSSignedData repackToDetachedCms(CMSSignedData input) throws CMSException { + CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); + cmsGenerator.addCertificates(input.getCertificates()); + cmsGenerator.addSigners(input.getSignerInfos()); + cmsGenerator.addAttributeCertificates(input.getAttributeCertificates()); + cmsGenerator.addCRLs(input.getCRLs()); + + return cmsGenerator.generate(input.getSignedContent(), false); + } + public enum ParserState { IDLE, SUCCESS, diff --git a/src/test/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParserTest.java b/src/test/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParserTest.java index 25c76db..98c1458 100644 --- a/src/test/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParserTest.java +++ b/src/test/java/eu/europa/ec/dgc/signing/SignedCertificateMessageParserTest.java @@ -74,50 +74,56 @@ void setupTestData() throws Exception { @Test void parserShouldParseByteArray() throws IOException, CertificateEncodingException { - SignedCertificateMessageParser parser = new SignedCertificateMessageParser(Base64.getEncoder().encode(builder.build())); + SignedCertificateMessageParser parser = new SignedCertificateMessageParser( + Base64.getEncoder().encode(builder.build())); Assertions.assertEquals(SignedCertificateMessageParser.ParserState.SUCCESS, parser.getParserState()); Assertions.assertArrayEquals(payloadCertificate.getEncoded(), parser.getPayloadCertificate().getEncoded()); Assertions.assertArrayEquals(signingCertificate.getEncoded(), parser.getSigningCertificate().getEncoded()); Assertions.assertTrue(parser.isSignatureVerified()); - Assertions.assertNotNull(parser.getSignature()); + checkSignatureFromParser(parser.getSignature()); } @Test void parserShouldParseByteArrayWithDetachedPayload() throws IOException, CertificateEncodingException { + byte[] cms = Base64.getEncoder().encode(builder.build(true)); + SignedCertificateMessageParser parser = new SignedCertificateMessageParser( - Base64.getEncoder().encode(builder.build(true)), + cms, Base64.getEncoder().encode(payloadCertificate.getEncoded())); Assertions.assertEquals(SignedCertificateMessageParser.ParserState.SUCCESS, parser.getParserState()); Assertions.assertArrayEquals(payloadCertificate.getEncoded(), parser.getPayloadCertificate().getEncoded()); Assertions.assertArrayEquals(signingCertificate.getEncoded(), parser.getSigningCertificate().getEncoded()); Assertions.assertTrue(parser.isSignatureVerified()); - Assertions.assertNotNull(parser.getSignature()); + Assertions.assertEquals(new String(cms), parser.getSignature()); } @Test void parserShouldParseByteArrayWithDetachedPayloadAsString() throws IOException, CertificateEncodingException { + byte[] cms = Base64.getEncoder().encode(builder.build(true)); + SignedCertificateMessageParser parser = new SignedCertificateMessageParser( - Base64.getEncoder().encode(builder.build(true)), + cms, Base64.getEncoder().encodeToString(payloadCertificate.getEncoded())); Assertions.assertEquals(SignedCertificateMessageParser.ParserState.SUCCESS, parser.getParserState()); Assertions.assertArrayEquals(payloadCertificate.getEncoded(), parser.getPayloadCertificate().getEncoded()); Assertions.assertArrayEquals(signingCertificate.getEncoded(), parser.getSigningCertificate().getEncoded()); Assertions.assertTrue(parser.isSignatureVerified()); - Assertions.assertNotNull(parser.getSignature()); + checkSignatureFromParser(parser.getSignature()); } @Test void parserShouldParseString() throws IOException, CertificateEncodingException { - SignedCertificateMessageParser parser = new SignedCertificateMessageParser(builder.buildAsString()); + SignedCertificateMessageParser parser = new SignedCertificateMessageParser( + builder.buildAsString()); Assertions.assertEquals(SignedCertificateMessageParser.ParserState.SUCCESS, parser.getParserState()); Assertions.assertArrayEquals(payloadCertificate.getEncoded(), parser.getPayloadCertificate().getEncoded()); Assertions.assertArrayEquals(signingCertificate.getEncoded(), parser.getSigningCertificate().getEncoded()); Assertions.assertTrue(parser.isSignatureVerified()); - Assertions.assertNotNull(parser.getSignature()); + checkSignatureFromParser(parser.getSignature()); } @Test @@ -130,7 +136,7 @@ void parserShouldParseStringWithDetachedPayload() throws IOException, Certificat Assertions.assertArrayEquals(payloadCertificate.getEncoded(), parser.getPayloadCertificate().getEncoded()); Assertions.assertArrayEquals(signingCertificate.getEncoded(), parser.getSigningCertificate().getEncoded()); Assertions.assertTrue(parser.isSignatureVerified()); - Assertions.assertNotNull(parser.getSignature()); + checkSignatureFromParser(parser.getSignature()); } @Test @@ -274,5 +280,16 @@ void parserShouldDetectInvalidSignerInfoAmount() throws Exception { Assertions.assertEquals(SignedCertificateMessageParser.ParserState.FAILURE_CMS_SIGNER_INFO, parser.getParserState()); Assertions.assertFalse(parser.isSignatureVerified()); } + + private void checkSignatureFromParser(String signature) throws CertificateEncodingException, IOException { + SignedCertificateMessageParser parser = new SignedCertificateMessageParser( + signature, Base64.getEncoder().encodeToString(payloadCertificate.getEncoded())); + + Assertions.assertEquals(SignedCertificateMessageParser.ParserState.SUCCESS, parser.getParserState()); + Assertions.assertEquals(new X509CertificateHolder(payloadCertificate.getEncoded()), parser.getPayloadCertificate()); + Assertions.assertEquals(new X509CertificateHolder(signingCertificate.getEncoded()), parser.getSigningCertificate()); + Assertions.assertTrue(parser.isSignatureVerified()); + Assertions.assertEquals(signature, parser.getSignature()); + } }