@@ -72,6 +72,9 @@ static int verify_cb(int ok, X509_STORE_CTX *ctx);
72
72
static int openssl_verify_peer_name_matches_certificate_name (PGconn * conn ,
73
73
ASN1_STRING * name ,
74
74
char * * store_name );
75
+ static int openssl_verify_peer_name_matches_certificate_ip (PGconn * conn ,
76
+ ASN1_OCTET_STRING * addr_entry ,
77
+ char * * store_name );
75
78
static void destroy_ssl_system (void );
76
79
static int initialize_SSL (PGconn * conn );
77
80
static PostgresPollingStatusType open_client_SSL (PGconn * );
@@ -509,6 +512,56 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
509
512
return pq_verify_peer_name_matches_certificate_name (conn , (const char * ) namedata , len , store_name );
510
513
}
511
514
515
+ /*
516
+ * OpenSSL-specific wrapper around
517
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
518
+ * ASN1_OCTET_STRING into a plain C string.
519
+ */
520
+ static int
521
+ openssl_verify_peer_name_matches_certificate_ip (PGconn * conn ,
522
+ ASN1_OCTET_STRING * addr_entry ,
523
+ char * * store_name )
524
+ {
525
+ int len ;
526
+ const unsigned char * addrdata ;
527
+
528
+ /* Should not happen... */
529
+ if (addr_entry == NULL )
530
+ {
531
+ appendPQExpBufferStr (& conn -> errorMessage ,
532
+ libpq_gettext ("SSL certificate's address entry is missing\n" ));
533
+ return -1 ;
534
+ }
535
+
536
+ /*
537
+ * GEN_IPADD is an OCTET STRING containing an IP address in network byte
538
+ * order.
539
+ */
540
+ #ifdef HAVE_ASN1_STRING_GET0_DATA
541
+ addrdata = ASN1_STRING_get0_data (addr_entry );
542
+ #else
543
+ addrdata = ASN1_STRING_data (addr_entry );
544
+ #endif
545
+ len = ASN1_STRING_length (addr_entry );
546
+
547
+ return pq_verify_peer_name_matches_certificate_ip (conn , addrdata , len , store_name );
548
+ }
549
+
550
+ static bool
551
+ is_ip_address (const char * host )
552
+ {
553
+ struct in_addr dummy4 ;
554
+ #ifdef HAVE_INET_PTON
555
+ struct in6_addr dummy6 ;
556
+ #endif
557
+
558
+ return inet_aton (host , & dummy4 )
559
+ #ifdef HAVE_INET_PTON
560
+ || (inet_pton (AF_INET6 , host , & dummy6 ) == 1 )
561
+ #endif
562
+ ;
563
+ }
564
+
512
565
/*
513
566
* Verify that the server certificate matches the hostname we connected to.
514
567
*
@@ -522,6 +575,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
522
575
STACK_OF (GENERAL_NAME ) * peer_san ;
523
576
int i ;
524
577
int rc = 0 ;
578
+ char * host = conn -> connhost [conn -> whichhost ].host ;
579
+ int host_type ;
580
+ bool check_cn = true;
581
+
582
+ Assert (host && host [0 ]); /* should be guaranteed by caller */
583
+
584
+ /*
585
+ * We try to match the NSS behavior here, which is a slight departure from
586
+ * the spec but seems to make more intuitive sense:
587
+ *
588
+ * If connhost contains a DNS name, and the certificate's SANs contain any
589
+ * dNSName entries, then we'll ignore the Subject Common Name entirely;
590
+ * otherwise, we fall back to checking the CN. (This behavior matches the
591
+ * RFC.)
592
+ *
593
+ * If connhost contains an IP address, and the SANs contain iPAddress
594
+ * entries, we again ignore the CN. Otherwise, we allow the CN to match,
595
+ * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
596
+ * client MUST NOT seek a match for a reference identifier of CN-ID if the
597
+ * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
598
+ * application-specific identifier types supported by the client.")
599
+ *
600
+ * NOTE: Prior versions of libpq did not consider iPAddress entries at
601
+ * all, so this new behavior might break a certificate that has different
602
+ * IP addresses in the Subject CN and the SANs.
603
+ */
604
+ if (is_ip_address (host ))
605
+ host_type = GEN_IPADD ;
606
+ else
607
+ host_type = GEN_DNS ;
525
608
526
609
/*
527
610
* First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +620,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
537
620
for (i = 0 ; i < san_len ; i ++ )
538
621
{
539
622
const GENERAL_NAME * name = sk_GENERAL_NAME_value (peer_san , i );
623
+ char * alt_name = NULL ;
540
624
541
- if (name -> type == GEN_DNS )
625
+ if (name -> type == host_type )
542
626
{
543
- char * alt_name ;
627
+ /*
628
+ * This SAN is of the same type (IP or DNS) as our host name,
629
+ * so don't allow a fallback check of the CN.
630
+ */
631
+ check_cn = false;
632
+ }
544
633
634
+ if (name -> type == GEN_DNS )
635
+ {
545
636
(* names_examined )++ ;
546
637
rc = openssl_verify_peer_name_matches_certificate_name (conn ,
547
638
name -> d .dNSName ,
548
639
& alt_name );
640
+ }
641
+ else if (name -> type == GEN_IPADD )
642
+ {
643
+ (* names_examined )++ ;
644
+ rc = openssl_verify_peer_name_matches_certificate_ip (conn ,
645
+ name -> d .iPAddress ,
646
+ & alt_name );
647
+ }
549
648
550
- if (alt_name )
551
- {
552
- if (!* first_name )
553
- * first_name = alt_name ;
554
- else
555
- free (alt_name );
556
- }
649
+ if (alt_name )
650
+ {
651
+ if (!* first_name )
652
+ * first_name = alt_name ;
653
+ else
654
+ free (alt_name );
557
655
}
656
+
558
657
if (rc != 0 )
658
+ {
659
+ /*
660
+ * Either we hit an error or a match, and either way we should
661
+ * not fall back to the CN.
662
+ */
663
+ check_cn = false;
559
664
break ;
665
+ }
560
666
}
561
667
sk_GENERAL_NAME_pop_free (peer_san , GENERAL_NAME_free );
562
668
}
563
669
564
670
/*
565
- * If there is no subjectAltName extension of type dNSName , check the
671
+ * If there is no subjectAltName extension of the matching type , check the
566
672
* Common Name.
567
673
*
568
674
* (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
569
- * dNSName is present, the CN must be ignored.)
675
+ * dNSName is present, the CN must be ignored. We break this rule if host
676
+ * is an IP address; see the comment above.)
570
677
*/
571
- if (* names_examined == 0 )
678
+ if (check_cn )
572
679
{
573
680
X509_NAME * subject_name ;
574
681
@@ -581,10 +688,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
581
688
NID_commonName , -1 );
582
689
if (cn_index >= 0 )
583
690
{
691
+ char * common_name = NULL ;
692
+
584
693
(* names_examined )++ ;
585
694
rc = openssl_verify_peer_name_matches_certificate_name (conn ,
586
695
X509_NAME_ENTRY_get_data (X509_NAME_get_entry (subject_name , cn_index )),
587
- first_name );
696
+ & common_name );
697
+
698
+ if (common_name )
699
+ {
700
+ if (!* first_name )
701
+ * first_name = common_name ;
702
+ else
703
+ free (common_name );
704
+ }
588
705
}
589
706
}
590
707
}
0 commit comments