From c2fd5031e08e2f0b2e82c2f29235693866be51be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Lindqvist?= <bjourne@gmail.com>
Date: Sat, 14 Sep 2013 21:18:13 +0200
Subject: [PATCH] io.sockets.secure, io.sockets.secure.openssl: improved host
 name verification that takes into account a certificates subject alternative
 names.

---
 .../secure/openssl/openssl-docs.factor        | 21 +++++++++++++++
 .../secure/openssl/openssl-tests.factor       |  4 +++
 .../io/sockets/secure/openssl/openssl.factor  | 26 ++++++++++++-------
 basis/io/sockets/secure/secure-docs.factor    |  4 +--
 basis/io/sockets/secure/secure.factor         |  6 ++---
 5 files changed, 47 insertions(+), 14 deletions(-)
 create mode 100644 basis/io/sockets/secure/openssl/openssl-docs.factor
 create mode 100644 basis/io/sockets/secure/openssl/openssl-tests.factor

diff --git a/basis/io/sockets/secure/openssl/openssl-docs.factor b/basis/io/sockets/secure/openssl/openssl-docs.factor
new file mode 100644
index 0000000000..bc66781b62
--- /dev/null
+++ b/basis/io/sockets/secure/openssl/openssl-docs.factor
@@ -0,0 +1,21 @@
+USING: help.markup help.syntax ;
+IN: io.sockets.secure.openssl
+
+
+HELP: subject-name
+{ $values { "certificate" "an SSL peer certificate" } }
+{ $description "The subject name of a certificate." } ;
+
+HELP: subject-names-match?
+{ $values { "host" "a host name" } { "subject" "a subject name" } }
+{ $description "True if the host name matches the subject name." }
+{ $examples
+    { $code
+        "\"www.google.se\" \"*.google.se\" subject-names-match?"
+        "t"
+    }
+} ;
+
+HELP: alternative-dns-names
+{ $values { "certificate" "an SSL peer certificate" } }
+{ $description "Alternative subject names for the certificate." } ;
diff --git a/basis/io/sockets/secure/openssl/openssl-tests.factor b/basis/io/sockets/secure/openssl/openssl-tests.factor
new file mode 100644
index 0000000000..d39a787c72
--- /dev/null
+++ b/basis/io/sockets/secure/openssl/openssl-tests.factor
@@ -0,0 +1,4 @@
+USING: accessors http.client kernel tools.test ;
+IN: io.sockets.secure.openssl.tests
+
+[ 200 ] [ "https://www.google.se" http-get drop code>> ] unit-test
diff --git a/basis/io/sockets/secure/openssl/openssl.factor b/basis/io/sockets/secure/openssl/openssl.factor
index a0eae9e717..a1891cdad9 100644
--- a/basis/io/sockets/secure/openssl/openssl.factor
+++ b/basis/io/sockets/secure/openssl/openssl.factor
@@ -1,11 +1,11 @@
 ! Copyright (C) 2007, 2008, Slava Pestov, Elie CHAFTARI.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors byte-arrays kernel sequences namespaces math
+USING: accessors byte-arrays classes.struct kernel sequences namespaces math
 math.order combinators init alien alien.c-types alien.data
 alien.strings libc continuations destructors summary splitting
 assocs random math.parser locals unicode.case openssl
 openssl.libcrypto openssl.libssl io.backend io.ports io.pathnames
-io.encodings.8-bit.latin1 io.timeouts io.sockets.secure ;
+io.encodings.8-bit.latin1 io.encodings.utf8 io.timeouts io.sockets.secure ;
 IN: io.sockets.secure.openssl
 
 GENERIC: ssl-method ( symbol -- method )
@@ -175,25 +175,33 @@ M: ssl-handle dispose*
     [ 256 X509_NAME_get_text_by_NID ] keep
     swap -1 = [ drop f ] [ latin1 alien>string ] if ;
 
-: common-name ( certificate -- host )
+: subject-name ( certificate -- host )
     X509_get_subject_name x509name>string ;
 
 : issuer-name ( certificate -- issuer )
     X509_get_issuer_name x509name>string ;
 
-: common-names-match? ( expected actual -- ? )
+: name-stack>seq ( name-stack -- seq )
+    dup sk_num iota [ sk_value GENERAL_NAME_st memory>struct ] with map ;
+
+: alternative-dns-names ( certificate -- dns-names )
+    NID_subject_alt_name f f X509_get_ext_d2i [ name-stack>seq ] [ { } ] if*
+    [ type>> GEN_DNS = ] filter
+    [ d>> dNSName>> data>> utf8 alien>string ] map ;
+
+: subject-names-match? ( host subject -- ? )
     [ >lower ] bi@ "*." ?head [ tail? ] [ = ] if ;
 
-: check-common-name ( host ssl-handle -- )
-    SSL_get_peer_certificate common-name
-    2dup common-names-match?
-    [ 2drop ] [ common-name-verify-error ] if ;
+: check-subject-name ( host ssl-handle -- )
+    SSL_get_peer_certificate [ alternative-dns-names ] [ subject-name ] bi
+    suffix 2dup [ subject-names-match? ] with filter empty?
+    [ subject-name-verify-error ] [ 2drop ] if ;
 
 M: openssl check-certificate ( host ssl -- )
     current-secure-context config>> verify>> [
         handle>>
         [ nip check-verify-result ]
-        [ check-common-name ]
+        [ check-subject-name ]
         2bi
     ] [ 2drop ] if ;
 
diff --git a/basis/io/sockets/secure/secure-docs.factor b/basis/io/sockets/secure/secure-docs.factor
index 139e036241..82624a3f53 100644
--- a/basis/io/sockets/secure/secure-docs.factor
+++ b/basis/io/sockets/secure/secure-docs.factor
@@ -126,8 +126,8 @@ HELP: premature-close
 HELP: certificate-verify-error
 { $error-description "Thrown if certificate verification failed. The " { $snippet "result" } " slot contains an object identifying the low-level error that occurred." } ;
 
-HELP: common-name-verify-error
-{ $error-description "Thrown during certificate verification if the host name on the certificate does not match the host name the socket was connected to. This indicates a potential man-in-the-middle attack. The " { $slot "expected" } " and " { $slot "got" } " slots contain the mismatched host names." } ;
+HELP: subject-name-verify-error
+{ $error-description "Thrown during certificate verification if the subject names on the certificate does not match the host name the socket was connected to. This indicates a potential man-in-the-middle attack. The " { $slot "expected" } " and " { $slot "got" } " slots contain the mismatched host names." } ;
 
 HELP: upgrade-on-non-socket
 { $error-description "Thrown if " { $link send-secure-handshake } " or " { $link accept-secure-handshake } " is called with the " { $link input-stream } " and " { $link output-stream } " variables not set to a socket. This error can also indicate that the connection has already been upgraded to a secure connection." } ;
diff --git a/basis/io/sockets/secure/secure.factor b/basis/io/sockets/secure/secure.factor
index c0d0598adb..fdee739070 100644
--- a/basis/io/sockets/secure/secure.factor
+++ b/basis/io/sockets/secure/secure.factor
@@ -77,10 +77,10 @@ ERROR: certificate-verify-error result ;
 M: certificate-verify-error summary
     drop "Certificate verification failed" ;
 
-ERROR: common-name-verify-error expected got ;
+ERROR: subject-name-verify-error expected got ;
 
-M: common-name-verify-error summary
-    drop "Common name verification failed" ;
+M: subject-name-verify-error summary
+    drop "Subject name verification failed" ;
 
 ERROR: upgrade-on-non-socket ;