Skip to content

Commit

Permalink
Add String CMS Message Builder And Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
f11h authored Jun 22, 2021
2 parents bb274f8 + e4b9b54 commit 842924f
Show file tree
Hide file tree
Showing 14 changed files with 1,255 additions and 348 deletions.
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ To create the signed message the following call is required:
```java
String signedMessaged=new SignedCertificateMessageBuilder()
.withSigningCertificate(signingCert,signingCertPrivateKey)
.withPayloadCertificate(inputCert)
.withPayload(inputCert)
.buildAsString();
```

Expand All @@ -232,9 +232,9 @@ It is also possible to create a detached signature. Just pass the boolean value
or ```buildAsString()``` method.

```java
String detachedSignature=new SignedCertificateMessageBuilder()
String detachedSignature = new SignedCertificateMessageBuilder()
.withSigningCertificate(signingCert,signingCertPrivateKey)
.withPayloadCertificate(inputCert)
.withPayload(inputCert)
.buildAsString(true);
```

Expand All @@ -245,13 +245,13 @@ When a certificate is received it needs to be "unpacked". To do so the SignedCer
Simply instantiate ```SignedCertificateMessageParser``` with the base64 encoded String.

```java
SignedCertificateMessageParser parser=new SignedCertificateMessageParser(inputString);
SignedCertificateMessageParser parser = new SignedCertificateMessageParser(inputString);
```

The constructor accepts different formats as incoming message. Also detached signatures are accepted.

```java
SignedCertificateMessageParser parser=new SignedCertificateMessageParser(payloadByteArray,signatureString);
SignedCertificateMessageParser parser = new SignedCertificateMessageParser(payloadByteArray, signatureString);
```

All combinations of String and byte[] as parameter for signature and payload are possible. Please be aware that the
Expand All @@ -260,7 +260,7 @@ payload will be always base64 encoded (even if it is passed as byte[]).
The parser will immediately parse the message. The result can be obtained from

```java
parser.getParserState()
parser.getParserState();
```

If the state is ```SUCCESS``` the syntax of the message was correct and the certificate could be parsed.
Expand All @@ -269,14 +269,14 @@ Also the parser checks if the signature of the CMS message was created by the em
result of this check just read the property

```java
parser.isSignatureVerified()
parser.isSignatureVerified();
```

The signer certificate and the containing certificate can be accessed by

```java
parser.getSigningCertificate()
parser.getPayloadCertificate()
parser.getSigningCertificate();
parser.getPayload();
```

Also a detached signature can be gained from parsed message
Expand All @@ -285,6 +285,20 @@ Also a detached signature can be gained from parsed message
parser.getSignature()
```

#### Signing and Parsing of other data types

It is also possible to sign and parse CMS messages with other data types.
Therefore, implementations of ```SignedMessageBuilder``` and ```SignedMessageParser``` are required.

The DGC-Lib currently contains Implementations for the following classes:

| Data Type | Parser Implementation | Builder Implementation |
| --- | --- | --- |
| java.lang.String | SignedStringMessageParser | SignedStringMessageBuilder |
| org.bouncycastle.cert.X509CertificateHolder | SignedCertificateMessageParser | SignedCertificateMessageBuilder |

The usage of Parser and Builder can be seen in the chapter above. Basic usage is equal for all implementations of Parser and Builder.

### Utils

### Certificate Utils
Expand Down Expand Up @@ -363,12 +377,9 @@ The following channels are available for discussions, feedback, and support requ

| Type | Channel |
| ------------------------- | ------------------------------------------------------ |
| **DGC Gateway
issues** | <a href="https://github.com/eu-digital-green-certificates/dgc-gateway/issues" title="Open Issues"><img src="https://img.shields.io/github/issues/eu-digital-green-certificates/dgc-gateway?style=flat"></a> |
| **DGC Lib
issues** | <a href="/../../issues" title="Open Issues"><img src="https://img.shields.io/github/issues/eu-digital-green-certificates/dgc-lib?style=flat"></a> |
| **Other
requests** | <a href="mailto:[email protected]" title="Email DGC Team"><img src="https://img.shields.io/badge/email-DGC%20team-green?logo=mail.ru&style=flat-square&logoColor=white"></a> |
| **DGC Gateway issues** | <a href="https://github.com/eu-digital-green-certificates/dgc-gateway/issues" title="Open Issues"><img src="https://img.shields.io/github/issues/eu-digital-green-certificates/dgc-gateway?style=flat"></a> |
| **DGC Lib issues** | <a href="/../../issues" title="Open Issues"><img src="https://img.shields.io/github/issues/eu-digital-green-certificates/dgc-lib?style=flat"></a> |
| **Other requests** | <a href="mailto:[email protected]" title="Email DGC Team"><img src="https://img.shields.io/badge/email-DGC%20team-green?logo=mail.ru&style=flat-square&logoColor=white"></a> |

## How to contribute

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,134 +21,37 @@
package eu.europa.ec.dgc.signing;

import java.io.IOException;
import java.security.PrivateKey;
import java.security.Security;
import java.util.Base64;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

/**
* Utility to build a CMS signed message containing a DER encoded X509 certificate.
*/
@Slf4j
@NoArgsConstructor
public class SignedCertificateMessageBuilder {
public class SignedCertificateMessageBuilder
extends SignedMessageBuilder<X509CertificateHolder, SignedCertificateMessageBuilder> {

private X509CertificateHolder payloadCertificate;

private X509CertificateHolder signingCertificate;

private PrivateKey signingCertificatePrivateKey;
@Override
byte[] convertToBytes(X509CertificateHolder payload) throws IOException {
return payload.getEncoded();
}

/**
* Add a signing certificate to MessageBuilder instance.
*
* @param certificate X509 Certificate to sign the message with
* @param privateKey Private key for given X509 Certificate.
*/
public SignedCertificateMessageBuilder withSigningCertificate(
X509CertificateHolder certificate, PrivateKey privateKey) {
signingCertificate = certificate;
signingCertificatePrivateKey = privateKey;
@Override
SignedCertificateMessageBuilder getThis() {
return this;
}

/**
* Add a payload certificate to MessageBuilder instance.
*
* @param certificate X509 certificate for payload.
* @deprecated Use .withPayload(X509CertificateHolder) instead
*/
@Deprecated
public SignedCertificateMessageBuilder withPayloadCertificate(X509CertificateHolder certificate) {
payloadCertificate = certificate;
return this;
return withPayload(certificate);
}

/**
* <p>Builds the CMS signed certificate message.</p>
* <p>payloadCertificate and SigningCertificate needs to be set previously.</p>
*
* @param detached flag whether only the signature should be returned (detached signature)
* @return Bytes of signed CMS message.
*/
public byte[] build(boolean detached) {
Security.addProvider(new BouncyCastleProvider());

if (payloadCertificate == null || signingCertificate == null || signingCertificatePrivateKey == null) {
throw new RuntimeException("Message Builder is not ready");
}

byte[] messageBytes;
CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator();

try {
DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build();

String signingAlgorithmName =
new DefaultAlgorithmNameFinder().getAlgorithmName(signingCertificate.getSignatureAlgorithm());

ContentSigner contentSigner =
new JcaContentSignerBuilder(signingAlgorithmName).build(signingCertificatePrivateKey);

SignerInfoGenerator signerInfoGenerator = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
.build(contentSigner, signingCertificate);

signedDataGenerator.addSignerInfoGenerator(signerInfoGenerator);

signedDataGenerator.addCertificate(signingCertificate);

CMSSignedData signedData = signedDataGenerator.generate(
new CMSProcessableByteArray(payloadCertificate.getEncoded()), !detached);

messageBytes = signedData.getEncoded();
} catch (OperatorCreationException | CMSException | IOException e) {
throw new RuntimeException("Failed to create signed message");
}

return messageBytes;
}

/**
* <p>Builds the CMS signed certificate message.</p>
* <p>payloadCertificate and SigningCertificate needs to be set previously.</p>
*
* @return Bytes of signed CMS message.
*/
public byte[] build() {
return build(false);
}

/**
* <p>Builds the CMS signed certificate message.</p>
* <p>payloadCertificate and SigningCertificate needs to be set previously.</p>
*
* @param detached flag whether only the signature should be returned (detached signature)
* @return Base64 encoded String of CMS message.
*/
public String buildAsString(boolean detached) {
return Base64.getEncoder().encodeToString(build(detached));
}

/**
* <p>Builds the CMS signed certificate message.</p>
* <p>payloadCertificate and SigningCertificate needs to be set previously.</p>
*
* @return Base64 encoded String of signed CMS message.
*/
public String buildAsString() {
return Base64.getEncoder().encodeToString(build(false));
}
}
Loading

0 comments on commit 842924f

Please sign in to comment.