Java Spring Boot Library for CSC v2 Remote Digital Signing
Standards & Compliance
OpenCSC implements the Cloud Signature Consortium (CSC) API v2 specification, ensuring interoperability with any CSC-compliant remote signing service worldwide.
The Cloud Signature Consortium (CSC) defines an open, interoperable API standard for remote electronic signatures. OpenCSC fully implements the CSC API v2 specification, including credentials management, authorization, and signature operations.
Cloud Signature ConsortiumCompliant with the European Telecommunications Standards Institute (ETSI) specification for Protocols for remote digital signature creation. This standard defines the protocols for server-side signature creation, ensuring legal validity under eIDAS regulation.
ETSI TS 119 432 SpecificationFeatures
A comprehensive, production-ready library with auto-configuration, resilience, and extensibility built in.
Full implementation of credentials/list, credentials/info, credentials/authorize, credentials/getChallenge, and signatures/signHash endpoints.
Automatic client_credentials flow with proactive token refresh. Supports separate Authorization Servers (Keycloak, etc.).
Sign single or batch hashes with automatic SCAL1/SCAL2 detection and SAD lifecycle management.
Two-phase external signing via Apache PDFBox. Optional dependency — only activated when PDFBox is on the classpath.
Bouncy Castle-powered CMS signature container builder with full certificate chain support.
Zero boilerplate setup. Just add the dependency and configure properties. Conditional bean creation based on classpath and config.
Pluggable audit listener SPI. Receive events for token acquisition, credential authorization, signing, and errors.
Optional Resilience4j integration with configurable retry policies and circuit breaker thresholds.
Automatic detection and handling of SCAL levels. Hashes are included in authorize requests only for SCAL2 credentials.
Architecture
A facade pattern with clear separation of concerns. Each layer has a single responsibility and can be extended independently.
Quick Start
Add the dependency, configure your CSC server details, and start signing.
<dependency>
<groupId>com.icebrown.opencsc</groupId>
<artifactId>opencsc</artifactId>
<version>1.0.1</version>
</dependency>
<!-- For PDF signing, also add: -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.3</version>
</dependency>
opencsc:
base-url: "https://your-csc-server.com/csc/v2"
oauth2:
token-path: "/oauth2/token"
client-id: "your-client-id"
client-secret: "your-client-secret"
scope: "service"
pdf:
enabled: true
Required when your credential uses explicit authorization (PIN/OTP):
@Bean
public AuthDataProvider authDataProvider() {
return context -> {
// context.getCredentialId() — credential being authorized
// context.getRequiredObjects() — auth types (PIN, OTP, etc.)
// context.getCorrelationId() — correlation ID for tracing
return List.of(new AuthObject("PIN", "123456"));
};
}
@Autowired
CscRemoteSigningFacade cscFacade;
// List credentials
CredentialsListResponse creds = cscFacade.listCredentials(
CredentialsListRequest.builder().authInfo(true).build());
// Sign hash (authorize + sign in one call)
SignHashResponse result = cscFacade.authorizeAndSignHash(
"credentialId",
List.of("base64EncodedHash"),
"2.16.840.1.101.3.4.2.1", // SHA-256
"1.2.840.113549.1.1.11", // SHA256withRSA
"optional-client-data");
// Sign PDF
PdfSigningResult pdfResult = cscFacade.signPdf(
PdfSigningRequest.builder()
.pdfBytes(pdfBytes)
.credentialID("credentialId")
.reason("Contract signing")
.build());
byte[] signedPdf = pdfResult.getSignedPdfBytes();
Configuration
All properties are prefixed with opencsc.* and support Spring Boot's configuration binding.
| Property | Default | Description |
|---|---|---|
| base-url | (required) | CSC Resource Server base URL |
| auth-server-url | null | OAuth2 Authorization Server URL (falls back to base-url) |
| connect-timeout | 5s | HTTP connection timeout |
| read-timeout | 30s | HTTP read timeout |
| Property | Default | Description |
|---|---|---|
| oauth2.token-path | /oauth2/token | Token endpoint path |
| oauth2.client-id | (required) | OAuth2 client ID |
| oauth2.client-secret | (required) | OAuth2 client secret |
| oauth2.scope | null | OAuth2 scope |
| oauth2.token-expiry-skew | 30 | Seconds before expiry to trigger refresh |
| oauth2.refresh-check-interval | 30000 | Milliseconds between proactive refresh checks |
| Property | Default | Description |
|---|---|---|
| credentials.list-path | /credentials/list | Credentials list endpoint |
| credentials.info-path | /credentials/info | Credentials info endpoint |
| credentials.authorize-path | /credentials/authorize | Credentials authorize endpoint |
| credentials.get-challenge-path | /credentials/getChallenge | Get challenge endpoint |
| signatures.sign-hash-path | /signatures/signHash | Sign hash endpoint |
| Property | Default | Description |
|---|---|---|
| retry.enabled | true | Enable retry on failed API calls |
| retry.max-attempts | 3 | Maximum retry attempts |
| retry.wait-duration | 500ms | Wait between retries |
| circuit-breaker.enabled | true | Enable circuit breaker |
| circuit-breaker.failure-rate-threshold | 50 | Failure rate % to open circuit |
| Property | Default | Description |
|---|---|---|
| pdf.enabled | true | Enable PDF signing (requires PDFBox) |
| pdf.default-hash-algorithm | 2.16.840...2.1 | Default hash algorithm OID (SHA-256) |
| pdf.signature-reserved-space-bytes | 32768 | Reserved bytes for CMS container in PDF |
Extension Points
Implement these interfaces and register as Spring beans to customize behavior.
Supply PIN/OTP for explicit-mode credentials
@Bean
public AuthDataProvider authDataProvider() {
return context -> {
String credId = context.getCredentialId();
List<String> required = context.getRequiredObjects();
return List.of(
new AuthObject("PIN", "123456")
);
};
}
Receive audit events for monitoring and logging
@Bean
public CscAuditListener auditListener() {
return event -> switch (event) {
case TokenAcquiredEvent e ->
log.info("Token acquired");
case HashSignedEvent e ->
log.info("Signed {} hashes",
e.hashCount());
case PdfSignedEvent e ->
log.info("PDF signed");
case CscApiErrorEvent e ->
log.error("Error: {}",
e.errorMessage());
default ->
log.info("Event: {}", event);
};
}