55
55
#include "php_phongo.h"
56
56
#include "php_bson.h"
57
57
#include "php_array.h"
58
+ #include "src/contrib/php-ssl.h"
58
59
59
60
#undef MONGOC_LOG_DOMAIN
60
61
#define MONGOC_LOG_DOMAIN "PHONGO"
@@ -796,6 +797,97 @@ ssize_t phongo_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int3
796
797
return rval ;
797
798
} /* }}} */
798
799
800
+ #if PHP_VERSION_ID < 50600
801
+ int php_mongo_verify_hostname (const char * hostname , X509 * cert TSRMLS_DC )
802
+ {
803
+ if (php_mongo_matches_san_list (cert , hostname ) == SUCCESS ) {
804
+ return SUCCESS ;
805
+ }
806
+
807
+ if (php_mongo_matches_common_name (cert , hostname TSRMLS_CC ) == SUCCESS ) {
808
+ return SUCCESS ;
809
+ }
810
+
811
+ return FAILURE ;
812
+ }
813
+
814
+ int php_phongo_peer_verify (php_stream * stream , X509 * cert , const char * hostname , bson_error_t * error TSRMLS_DC )
815
+ {
816
+ zval * * verify_peer_name ;
817
+
818
+ /* This option is available since PHP 5.6.0 */
819
+ if (php_stream_context_get_option (stream -> context , "ssl" , "verify_peer_name" , & verify_peer_name ) == SUCCESS && zend_is_true (* verify_peer_name )) {
820
+ zval * * zhost = NULL ;
821
+ const char * peer ;
822
+
823
+ if (php_stream_context_get_option (stream -> context , "ssl" , "peer_name" , & zhost ) == SUCCESS ) {
824
+ convert_to_string_ex (zhost );
825
+ peer = Z_STRVAL_PP (zhost );
826
+ } else {
827
+ peer = hostname ;
828
+ }
829
+
830
+ if (php_mongo_verify_hostname (peer , cert TSRMLS_CC ) == FAILURE ) {
831
+ bson_set_error (error , MONGOC_ERROR_STREAM , MONGOC_ERROR_STREAM_CONNECT , "Remote certificate SubjectAltName or CN does not match '%s'" , hostname );
832
+ return false;
833
+ }
834
+ }
835
+
836
+ return true;
837
+ }
838
+ #endif
839
+
840
+ bool php_phongo_ssl_verify (php_stream * stream , const char * hostname , bson_error_t * error TSRMLS_DC )
841
+ {
842
+ zval * * zcert ;
843
+ zval * * verify_expiry ;
844
+ int resource_type ;
845
+ X509 * cert ;
846
+ int type ;
847
+
848
+ if (!stream -> context ) {
849
+ return true;
850
+ }
851
+
852
+ if (!(php_stream_context_get_option (stream -> context , "ssl" , "peer_certificate" , & zcert ) == SUCCESS && Z_TYPE_PP (zcert ) == IS_RESOURCE )) {
853
+ bson_set_error (error , MONGOC_ERROR_STREAM , MONGOC_ERROR_STREAM_CONNECT , "Could not capture certificate of %s" , hostname );
854
+ return false;
855
+ }
856
+
857
+
858
+
859
+ zend_list_find (Z_LVAL_PP (zcert ), & resource_type );
860
+ cert = (X509 * )zend_fetch_resource (zcert TSRMLS_CC , -1 , "OpenSSL X.509" , & type , 1 , resource_type );
861
+
862
+ if (!cert ) {
863
+ bson_set_error (error , MONGOC_ERROR_STREAM , MONGOC_ERROR_STREAM_CONNECT , "Could not get certificate of %s" , hostname );
864
+ return false;
865
+ }
866
+
867
+ #if PHP_VERSION_ID < 50600
868
+ if (!php_phongo_peer_verify (stream , cert , hostname , error TSRMLS_CC )) {
869
+ return false;
870
+ }
871
+ #endif
872
+
873
+ if (php_stream_context_get_option (stream -> context , "ssl" , "verify_expiry" , & verify_expiry ) == SUCCESS && zend_is_true (* verify_expiry )) {
874
+ time_t current = time (NULL );
875
+ time_t valid_from = php_mongo_asn1_time_to_time_t (X509_get_notBefore (cert ) TSRMLS_CC );
876
+ time_t valid_until = php_mongo_asn1_time_to_time_t (X509_get_notAfter (cert ) TSRMLS_CC );
877
+
878
+ if (valid_from > current ) {
879
+ bson_set_error (error , MONGOC_ERROR_STREAM , MONGOC_ERROR_STREAM_CONNECT , "Certificate is not valid yet on %s" , hostname );
880
+ return false;
881
+ }
882
+ if (current > valid_until ) {
883
+ bson_set_error (error , MONGOC_ERROR_STREAM , MONGOC_ERROR_STREAM_CONNECT , "Certificate has expired on %s" , hostname );
884
+ return false;
885
+ }
886
+ }
887
+
888
+ return true;
889
+ }
890
+
799
891
mongoc_stream_t * phongo_stream_initiator (const mongoc_uri_t * uri , const mongoc_host_list_t * host , void * user_data , bson_error_t * error ) /* {{{ */
800
892
{
801
893
php_phongo_stream_socket * base_stream = NULL ;
@@ -880,6 +972,14 @@ mongoc_stream_t* phongo_stream_initiator(const mongoc_uri_t *uri, const mongoc_h
880
972
zend_replace_error_handling (EH_THROW , php_phongo_sslconnectionexception_ce , & error_handling TSRMLS_CC );
881
973
882
974
mongoc_log (MONGOC_LOG_LEVEL_DEBUG , MONGOC_LOG_DOMAIN , "Enabling SSL" );
975
+
976
+ /* Capture the server certificate so we can do further verification */
977
+ if (stream -> context ) {
978
+ zval capture ;
979
+ ZVAL_BOOL (& capture , 1 );
980
+ php_stream_context_set_option (stream -> context , "ssl" , "capture_peer_cert" , & capture );
981
+ }
982
+
883
983
if (php_stream_xport_crypto_setup (stream , PHONGO_CRYPTO_METHOD , NULL TSRMLS_CC ) < 0 ) {
884
984
zend_restore_error_handling (& error_handling TSRMLS_CC );
885
985
php_stream_free (stream , PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR );
@@ -896,11 +996,21 @@ mongoc_stream_t* phongo_stream_initiator(const mongoc_uri_t *uri, const mongoc_h
896
996
return NULL ;
897
997
}
898
998
999
+ if (!php_phongo_ssl_verify (stream , host -> host , error TSRMLS_CC )) {
1000
+ zend_restore_error_handling (& error_handling TSRMLS_CC );
1001
+ php_stream_pclose (stream );
1002
+ efree (dsn );
1003
+ return NULL ;
1004
+ }
1005
+
899
1006
zend_restore_error_handling (& error_handling TSRMLS_CC );
900
1007
}
901
1008
efree (dsn );
902
1009
903
1010
1011
+ /* We only need the context really for SSL initialization, safe to remove now */
1012
+ php_stream_context_set (stream , NULL );
1013
+
904
1014
base_stream = ecalloc (1 , sizeof (php_phongo_stream_socket ));
905
1015
base_stream -> stream = stream ;
906
1016
base_stream -> uri = uri ;
0 commit comments