diff -up m2crypto-0.16/M2Crypto/SSL/Checker.py.multisubject m2crypto-0.16/M2Crypto/SSL/Checker.py --- m2crypto-0.16/M2Crypto/SSL/Checker.py.multisubject 2006-03-20 20:26:28.000000000 +0100 +++ m2crypto-0.16/M2Crypto/SSL/Checker.py 2008-01-06 08:16:08.000000000 +0100 @@ -1,11 +1,11 @@ """ -M2Crypto.SSL.Checker +SSL peer certificate checking routines -Copyright (c) 2004-2005 Open Source Applications Foundation. +Copyright (c) 2004-2007 Open Source Applications Foundation. All rights reserved. """ -from M2Crypto import util, EVP +from M2Crypto import util, EVP, m2 import re class SSLVerificationError(Exception): @@ -75,12 +75,12 @@ class Checker: if self.host: hostValidationPassed = False + self.useSubjectAltNameOnly = False - # XXX subjectAltName might contain multiple fields - # subjectAltName=DNS:somehost + # subjectAltName=DNS:somehost[, ...]* try: subjectAltName = peerCert.get_ext('subjectAltName').get_value() - if not self._match(self.host, subjectAltName, True): + if not self._splitSubjectAltName(self.host, subjectAltName): raise WrongHost(expectedHost=self.host, actualHost=subjectAltName, fieldName='subjectAltName') @@ -88,30 +88,74 @@ class Checker: except LookupError: pass - # commonName=somehost - if not hostValidationPassed: - try: - commonName = peerCert.get_subject().CN - if not self._match(self.host, commonName): - raise WrongHost(expectedHost=self.host, - actualHost=commonName, - fieldName='commonName') - except AttributeError: + # commonName=somehost[, ...]* + if not self.useSubjectAltNameOnly and not hostValidationPassed: + hasCommonName = False + commonNames = '' + for entry in peerCert.get_subject().get_entries_by_nid(m2.NID_commonName): + hasCommonName = True + commonName = entry.get_data().as_text() + if not commonNames: + commonNames = commonName + else: + commonNames += ',' + commonName + if self._match(self.host, commonName): + hostValidationPassed = True + break + + if not hasCommonName: raise WrongCertificate('no commonName in peer certificate') + if not hostValidationPassed: + raise WrongHost(expectedHost=self.host, + actualHost=commonNames, + fieldName='commonName') + return True - def _match(self, host, certHost, subjectAltName=False): + def _splitSubjectAltName(self, host, subjectAltName): """ >>> check = Checker() - >>> check._match(host='my.example.com', certHost='DNS:my.example.com', subjectAltName=True) + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com') + True + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:*.example.com') + True + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*.example.com') + True + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com') + False + >>> check.useSubjectAltNameOnly + True + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, othername:<unsupported>') + False + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.org') + False + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.com') True - >>> check._match(host='my.example.com', certHost='DNS:*.example.com', subjectAltName=True) + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com, DNS:my.example.org') True - >>> check._match(host='my.example.com', certHost='DNS:m*.example.com', subjectAltName=True) + >>> check.useSubjectAltNameOnly True - >>> check._match(host='my.example.com', certHost='DNS:m*ample.com', subjectAltName=True) + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='') False + >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='othername:<unsupported>') + False + >>> check.useSubjectAltNameOnly + False + """ + self.useSubjectAltNameOnly = False + for certHost in subjectAltName.split(','): + certHost = certHost.lower().strip() + if certHost[:4] == 'dns:': + self.useSubjectAltNameOnly = True + if self._match(host, certHost[4:]): + return True + return False + + + def _match(self, host, certHost): + """ + >>> check = Checker() >>> check._match(host='my.example.com', certHost='my.example.com') True >>> check._match(host='my.example.com', certHost='*.example.com') @@ -131,17 +175,12 @@ class Checker: >>> check._match(host='1234', certHost='1234') True """ - # XXX See RFC 2818 and 3280 for matching rules, this is not - # XXX yet complete. + # XXX See RFC 2818 and 3280 for matching rules, this is may not + # XXX yet be complete. host = host.lower() certHost = certHost.lower() - if subjectAltName: - if certHost[:4] != 'dns:': - return False - certHost = certHost[4:] - if host == certHost: return True diff -up m2crypto-0.16/M2Crypto/X509.py.multisubject m2crypto-0.16/M2Crypto/X509.py --- m2crypto-0.16/M2Crypto/X509.py.multisubject 2006-04-12 07:50:51.000000000 +0200 +++ m2crypto-0.16/M2Crypto/X509.py 2008-01-06 08:21:16.000000000 +0100 @@ -173,6 +173,8 @@ class X509_Name_Entry: def set_object(self, asn1obj): return m2.x509_name_entry_set_object( self.x509_name_entry, asn1obj._ptr() ) + def get_data(self): + return ASN1.ASN1_String(m2.x509_name_entry_get_data(self.x509_name_entry)) def create_by_txt( self, field, type, entry, len): return m2.x509_name_entry_create_by_txt( self.x509_name_entry._ptr(), field, type, entry, len ) @@ -241,6 +243,15 @@ class X509_Name: def __len__(self): return m2.x509_name_entry_count(self.x509_name) + + def __getitem__(self, idx): + if not 0 <= idx < self.entry_count(): + raise IndexError("index out of range") + return X509_Name_Entry(m2.x509_name_get_entry(self.x509_name, idx)) + + def __iter__(self): + for i in xrange(self.entry_count()): + yield self[i] def _ptr(self): #assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error" @@ -253,6 +264,20 @@ class X509_Name: def entry_count( self ): return m2.x509_name_entry_count( self.x509_name ) + def get_entries_by_nid(self, nid): + ret = [] + lastpos = -1 + + while True: + lastpos = m2.x509_name_get_index_by_nid(self.x509_name, nid, + lastpos) + if lastpos == -1: + break + + ret.append(self[lastpos]) + + return ret + def as_text(self, indent=0, flags=m2.XN_FLAG_COMPAT): """ as_text returns the name as a string. diff -up m2crypto-0.16/SWIG/_x509.i.multisubject m2crypto-0.16/SWIG/_x509.i --- m2crypto-0.16/SWIG/_x509.i.multisubject 2006-05-02 23:00:53.000000000 +0200 +++ m2crypto-0.16/SWIG/_x509.i 2008-01-06 08:16:08.000000000 +0100 @@ -132,6 +132,8 @@ extern int X509_NAME_add_entry_by_NID(X5 extern int X509_NAME_print_ex(BIO *, X509_NAME *, int, unsigned long); %rename(x509_name_print_ex_fp) X509_NAME_print_ex_fp; extern int X509_NAME_print_ex_fp(FILE *, X509_NAME *, int, unsigned long); +%rename(x509_name_get_index_by_nid) X509_NAME_get_index_by_NID; +extern int X509_NAME_get_index_by_NID(X509_NAME *, int, int); %rename(x509_name_entry_new) X509_NAME_ENTRY_new; extern X509_NAME_ENTRY *X509_NAME_ENTRY_new( void );