Socle V004 – TLS/HTTPS

Socle V004 - TLS/HTTPS

13 – TLS/HTTPS

Version : 4.0.0 Date : 2025-12-09

1. Introduction

Configuration du TLS/HTTPS pour sécuriser les communications HTTP du Socle V4.

2. Configuration Spring Boot

2.1 application.yml

server:
  port: ${HTTPS_PORT:8443}
  ssl:
    enabled: ${SSL_ENABLED:true}
    key-store: ${SSL_KEYSTORE:classpath:keystore.p12}
    key-store-password: ${SSL_KEYSTORE_PASSWORD:changeit}
    key-store-type: ${SSL_KEYSTORE_TYPE:PKCS12}
    key-alias: ${SSL_KEY_ALIAS:socle}

2.2 Variables d’environnement

Variable Description Défaut
SSL_ENABLED Activer SSL false
SSL_KEYSTORE Chemin du keystore classpath:keystore.p12
SSL_KEYSTORE_PASSWORD Mot de passe keystore changeit
SSL_KEYSTORE_TYPE Type de keystore PKCS12
SSL_KEY_ALIAS Alias de la clé socle

3. Génération des certificats

3.1 Certificat auto-signé (développement)

# Générer un keystore PKCS12 avec certificat auto-signé
keytool -genkeypair \
  -alias socle \
  -keyalg RSA \
  -keysize 2048 \
  -storetype PKCS12 \
  -keystore keystore.p12 \
  -validity 365 \
  -dname "CN=localhost,OU=Dev,O=MyCompany,L=Paris,C=FR" \
  -storepass changeit \
  -keypass changeit

# Exporter le certificat (pour les clients)
keytool -exportcert \
  -alias socle \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -file socle.crt

3.2 Avec Let’s Encrypt (production)

# Obtenir le certificat
certbot certonly --standalone -d myapp.example.com

# Convertir en PKCS12
openssl pkcs12 -export \
  -in /etc/letsencrypt/live/myapp.example.com/fullchain.pem \
  -inkey /etc/letsencrypt/live/myapp.example.com/privkey.pem \
  -out keystore.p12 \
  -name socle \
  -passout pass:changeit

3.3 Avec CA interne

# Générer CSR
keytool -certreq \
  -alias socle \
  -keystore keystore.p12 \
  -file socle.csr \
  -storepass changeit

# Après signature par la CA, importer le certificat
keytool -importcert \
  -alias socle \
  -keystore keystore.p12 \
  -file signed-cert.crt \
  -storepass changeit

# Importer la chaîne CA
keytool -importcert \
  -alias ca-root \
  -keystore keystore.p12 \
  -file ca-root.crt \
  -storepass changeit

4. Configuration avancée

4.1 Mutual TLS (mTLS)

server:
  ssl:
    enabled: true
    key-store: ${SSL_KEYSTORE:keystore.p12}
    key-store-password: ${SSL_KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    # Trust store pour vérifier les clients
    trust-store: ${SSL_TRUSTSTORE:truststore.p12}
    trust-store-password: ${SSL_TRUSTSTORE_PASSWORD}
    trust-store-type: PKCS12
    # Exiger certificat client
    client-auth: ${SSL_CLIENT_AUTH:need}  # none, want, need

4.2 Protocoles et Ciphers

server:
  ssl:
    enabled-protocols: TLSv1.3,TLSv1.2
    ciphers:
      - TLS_AES_256_GCM_SHA384
      - TLS_AES_128_GCM_SHA256
      - TLS_CHACHA20_POLY1305_SHA256
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

5. HTTP + HTTPS (dual port)

5.1 Configuration

@Configuration
public class TlsConfiguration {

    @Value("${server.http.port:8080}")
    private int httpPort;

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(createHttpConnector());
        return tomcat;
    }

    private Connector createHttpConnector() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setScheme("http");
        connector.setPort(httpPort);
        connector.setSecure(false);
        return connector;
    }
}

5.2 Redirection HTTP → HTTPS

@Configuration
public class HttpsRedirectConfiguration {

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };

        tomcat.addAdditionalTomcatConnectors(httpToHttpsRedirectConnector());
        return tomcat;
    }

    private Connector httpToHttpsRedirectConnector() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(false);
        connector.setRedirectPort(8443);
        return connector;
    }
}

6. Client HTTPS

6.1 OkHttpClient avec TLS

@Configuration
public class HttpClientConfiguration {

    @Value("${ssl.truststore:#{null}}")
    private Resource trustStore;

    @Value("${ssl.truststore-password:changeit}")
    private String trustStorePassword;

    @Bean
    public OkHttpClient secureHttpClient() throws Exception {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        if (trustStore != null && trustStore.exists()) {
            KeyStore ks = KeyStore.getInstance("PKCS12");
            try (InputStream is = trustStore.getInputStream()) {
                ks.load(is, trustStorePassword.toCharArray());
            }

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(ks);

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

            builder.sslSocketFactory(sslContext.getSocketFactory(),
                (X509TrustManager) tmf.getTrustManagers()[0]);
        }

        return builder
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
    }
}

6.2 Bypass SSL pour développement (NON RECOMMANDÉ)

// UNIQUEMENT POUR LE DÉVELOPPEMENT - NE PAS UTILISER EN PRODUCTION
public OkHttpClient insecureClient() throws Exception {
    TrustManager[] trustAllCerts = new TrustManager[]{
        new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) {}
            public void checkServerTrusted(X509Certificate[] chain, String authType) {}
            public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
        }
    };

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustAllCerts, new SecureRandom());

    return new OkHttpClient.Builder()
        .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
        .hostnameVerifier((hostname, session) -> true)
        .build();
}

7. Docker avec TLS

7.1 Dockerfile

FROM eclipse-temurin:21-jre

WORKDIR /app

# Copier le certificat
COPY keystore.p12 /app/certs/keystore.p12

# Copier l'application
COPY target/socle-v004-4.0.0.jar app.jar

ENV SSL_ENABLED=true
ENV SSL_KEYSTORE=/app/certs/keystore.p12

EXPOSE 8443

ENTRYPOINT ["java", "-jar", "app.jar"]

7.2 docker-compose.yml

version: '3.8'

services:
  socle-app:
    image: socle-v4:latest
    environment:
      - SSL_ENABLED=true
      - SSL_KEYSTORE=/app/certs/keystore.p12
      - SSL_KEYSTORE_PASSWORD_FILE=/run/secrets/ssl_password
    ports:
      - "8443:8443"
    volumes:
      - ./certs:/app/certs:ro
    secrets:
      - ssl_password

secrets:
  ssl_password:
    file: ./secrets/ssl_password.txt

8. Kubernetes avec TLS

8.1 Secret pour le certificat

apiVersion: v1
kind: Secret
metadata:
  name: socle-tls
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-cert>
  tls.key: <base64-encoded-key>

8.2 Ingress avec TLS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: socle-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - socle.example.com
      secretName: socle-tls
  rules:
    - host: socle.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: socle-service
                port:
                  number: 8080

8.3 cert-manager

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: socle-cert
spec:
  secretName: socle-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - socle.example.com

9. Vérification

9.1 Test avec curl

# Test HTTPS
curl -v https://localhost:8443/admin/health

# Avec certificat client (mTLS)
curl -v --cert client.crt --key client.key https://localhost:8443/admin/health

# Ignorer la vérification (dev only)
curl -vk https://localhost:8443/admin/health

9.2 Test avec openssl

# Vérifier le certificat du serveur
openssl s_client -connect localhost:8443 -showcerts

# Vérifier les protocoles supportés
openssl s_client -connect localhost:8443 -tls1_3

# Vérifier les ciphers
openssl s_client -connect localhost:8443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

10. Troubleshooting

Erreur: PKIX path building failed

Le certificat du serveur n’est pas trusté.

# Importer le certificat dans le truststore Java
keytool -importcert \
  -alias server-cert \
  -file server.crt \
  -keystore $JAVA_HOME/lib/security/cacerts \
  -storepass changeit

Erreur: Handshake failure

Incompatibilité de protocole ou cipher.

# Vérifier les protocoles
openssl s_client -connect host:port -tls1_2
openssl s_client -connect host:port -tls1_3

Erreur: Certificate expired

Renouveler le certificat et recréer le keystore.

11. Bonnes pratiques

DO

  • Utiliser TLS 1.2 minimum, TLS 1.3 recommandé
  • Renouveler les certificats avant expiration
  • Utiliser des clés RSA 2048 bits minimum ou ECDSA 256 bits
  • Activer HSTS en production
  • Utiliser cert-manager en Kubernetes

DON’T

  • Ne pas utiliser de certificats auto-signés en production
  • Ne pas désactiver la vérification des certificats
  • Ne pas stocker les mots de passe en clair
  • Ne pas utiliser TLS 1.0 ou 1.1 (dépréciés)

12. Références

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *