Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implémenter le Certificate Pinning #36

Open
3 of 4 tasks
NicolasBuquet opened this issue Dec 2, 2024 · 5 comments
Open
3 of 4 tasks

Implémenter le Certificate Pinning #36

NicolasBuquet opened this issue Dec 2, 2024 · 5 comments
Assignees
Labels
enhancement New feature or request

Comments

@NicolasBuquet
Copy link
Collaborator

NicolasBuquet commented Dec 2, 2024

Le Certificate Pinning est habituellement implémenté au niveau du fichier de configuration Info.plist de l'application iOS, sous l'entrée AppTransportSecurity (NSAppTransportSecurity) : https://developer.apple.com/news/?id=g9ejcf8y

Toutefois, cette prise en compte automatique ne fonctionne qu'avec les frameworks de haut niveau (comme URLSession).

La couche réseau dans Element X est implémentée au niveau du SDK Rust et se base sur la crate reqwest :

Cette couche Rust ne doit certainement pas faire appel à URLSession mais doit se baser plutôt sur une couche plus bas niveau (type CFNetwork).

Hors, les réglages de AppTransportSecurity ne fonctionnent pas sur les couches bas niveau telles Network ou CFNetwork : https://developer.apple.com/documentation/Security/preventing-insecure-network-connections

On ne va donc pas pouvoir se baser dessus pour implémenter automatiquement le certificate pinning dans Tchap X.

Toutefois, le Rust SDK propose 2 méthodes :

Ces 2 méthodes doivent nous permettre de réactiver le Certificate Pinning en désactivant les certicats fournis par le système puis en activant uniquement le certificat que Tchap X autorise.

Ces méthodes devront êrte exposées par l'interface ffi: du Rust SDK (cela ne semble pas être le cas actuellement).

Attention

Les appels Matrix sont gérés par le Rust SDK. Mais les autres appels réseau comme le web (pour accéder à lA FAQ) et les webviews n'interagissent pas avec le Rust SDK. pour ces appels, il faudra donc mettre en place le Certicate Pinning via AppTransportSecurity.

L'idéal ensuite, serait de pouvoir lire l'entrée AppTransportSecurity à l'initialisation du client HTTPS de l'application, pour utiliser le certificat renseigné et l'injecter dans le Rust SDK.

TODO

  • exposer les 2 méthodes dans l'interface ffi: du Rust SDK
  • renseigner le ou les certificats de confiance au niveau de AppTransportSecurity
  • mettre en place un feature flag pour activer le certificate pinning
  • lire les certificats et les injecter dans le client HTTPS Rust à son initialisation

NB:

La même vérification doit être faite sur Android qui semble présenter le même souci : https://developer.android.com/privacy-and-security/security-ssl (tchapgouv/tchap-x-android#1)

@NicolasBuquet
Copy link
Collaborator Author

NicolasBuquet commented Dec 9, 2024

Les 2 méthodes :

  • disable_built_in_root_certificates
  • add_root_certificates

sont déjà exposées à Swift côte iOS dans MatrixRustSDK/Sources/MatrixRustSDK/matrix_sdk_ffi.swift dans la class ClientBuilder :

    /**
     * Don't trust any system root certificates, only trust the certificates
     * provided through
     * [`add_root_certificates`][ClientBuilder::add_root_certificates].
     */
open func disableBuiltInRootCertificates() -> ClientBuilder {
    return try!  FfiConverterTypeClientBuilder.lift(try! rustCall() {
    uniffi_matrix_sdk_ffi_fn_method_clientbuilder_disable_built_in_root_certificates(self.uniffiClonePointer(),$0
    )
})
}
open func addRootCertificates(certificates: [Data]) -> ClientBuilder {
    return try!  FfiConverterTypeClientBuilder.lift(try! rustCall() {
    uniffi_matrix_sdk_ffi_fn_method_clientbuilder_add_root_certificates(self.uniffiClonePointer(),
        FfiConverterSequenceData.lower(certificates),$0
    )
})
}

L'ajout de certificats racines est relayé au module reqwest: https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.add_root_certificate

Le certificat doit être au format DER ou PEM : https://docs.rs/reqwest/latest/reqwest/tls/struct.Certificate.html

@yostyle

@NicolasBuquet
Copy link
Collaborator Author

NicolasBuquet commented Dec 17, 2024

Un article très intéressant sur le certificate pinning sur iOS : https://securevale.blog/articles/deep-dive-into-certificate-pinning-on-ios/

Sur Tchap Android, les fichiers .cer fournis pour le certificate pinning sont au format DER.

On peut voir leur contenu avec la commande : openssl x509 -in certignaservicesrootca.cer -inform der -text -noout

La crate Rust reqwest accepte le format de certificat DER : https://docs.rs/reqwest/latest/reqwest/tls/struct.Certificate.html

Pour convertir le format DER en format PEM : openssl x509 -inform der -in source.(cer|der) -out destination.pem

Le PEM est un format encodé en Base64 qui le rend stockable dans la structure plist du fichier info.plist.
On peut donc le stocker sous cette forme dans une clé PEM à côté de la clé SPKI-SHA256-BASE64 correspondant au domaine à sécuriser.

Screenshot 2024-12-17 at 11 14 43

Puis pour générer le hash attendu par NSAppTransportSecurity à partir du fichier PEM : cat source.pem | openssl x509 -inform pem -noout -outform pem -pubkey | openssl pkey -pubin -inform pem -outform der | openssl dgst -sha256 -binary | openssl enc -base64

@NicolasBuquet
Copy link
Collaborator Author

NicolasBuquet commented Dec 17, 2024

L'ajout de certificats passe par l'appel de la méthode addRootCertificates(Vec<CertificateBytes>) : https://github.com/matrix-org/matrix-rust-sdk/blob/c50358366fe7f25606cb5494071391c39f74f43a/bindings/matrix-sdk-ffi/src/client_builder.rs#L429

Cet appel ajoute le certificat dans la propriété additional_root_certificates qui estde type Vec<Certificate> : https://github.com/matrix-org/matrix-rust-sdk/blob/c50358366fe7f25606cb5494071391c39f74f43a/crates/matrix-sdk/src/http_client/native.rs#L128

Lors de l'appel à la méthode build du Builder (https://github.com/matrix-org/matrix-rust-sdk/blob/c50358366fe7f25606cb5494071391c39f74f43a/bindings/matrix-sdk-ffi/src/client_builder.rs#L502), les certificats vont être intégrés comme suit :

        for certificate in builder.additional_root_certificates {
            // We don't really know what type of certificate we may get here, so let's try
            // first one type, then the other.
            match Certificate::from_der(&certificate) {
                Ok(cert) => {
                    certificates.push(cert);
                }
                Err(der_error) => {
                    let cert = Certificate::from_pem(&certificate).map_err(|pem_error| {
                        ClientBuildError::Generic {
                            message: format!("Failed to add a root certificate as DER ({der_error:?}) or PEM ({pem_error:?})"),
                        }
                    })?;
                    certificates.push(cert);
                }
            }
        }

À la ligne match Certificate::from_der(&certificate), on voit bien que c'est le contenu du fichier DER (ou PEM plus bas) qui est attendu sous forme de suite de bytes.

C'est le binding qui manipule le type Certificate. Nous n'avons pas besoin d'y avoir accès.

Attention : fournir plusieurs certificats nécessite d'activer le backend rustls-tls : https://docs.rs/reqwest/latest/src/reqwest/async_impl/client.rs.html#1430-1433

Mais je ne vois pas encore comment appeler la méthode use_rustls_tls() qui ne semble pas appeler dans le Matrix-Rust-SDK.

Voir cette issue : seanmonstar/reqwest#2011
(Il semble que seul le backend rustls-tls ait le code pour parser PLUSIEURS certificats en une fois)

@NicolasBuquet
Copy link
Collaborator Author

NicolasBuquet commented Dec 23, 2024

Actuellement, en ajoutant le certificat PEM issu de Tchap Android, l'application n'arrive pas à se connecter au serveur d'accueil.

simulator_screenshot_562CF1E0-16B3-4CA1-B240-7C5B0D891237

Le certificat fourni dans Tchap Android Legacy est-il un ROOT Certificate ou un Leaf Certificate ? (@yostyle) -> c'est le root ca de certigna

Il faudrait peut-être faire des tests en Rust directement ?

C'est peut-être lié au fait que add_root_certificate semble ne pas fonctionner si aucun store certificate n'est configuré :

@NicolasBuquet
Copy link
Collaborator Author

NicolasBuquet commented Dec 23, 2024

Attention, sur Tchap Android Legacy, on n'utilise pas le Certificate Pinning mais du Trust anchor.

Et le Certificate Pinning est plus restrictif que le Trusted CA.
Dans le cas du renouvellement légitime du CA Root, le Trusted CA ne dira rien (ce qui est normal), alors que le Certificate Pinning pourra réagir (il est lié à un Certificat particulier et est plus sécurisé encore) : https://security.stackexchange.com/a/51353

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: En étude
Development

When branches are created from issues, their pull requests are automatically linked.

1 participant