diff --git a/internal/ldap/dn.go b/internal/ldap/dn.go index 2bd46d8c..878ffbca 100644 --- a/internal/ldap/dn.go +++ b/internal/ldap/dn.go @@ -5,6 +5,7 @@ package ldap import ( "bytes" "crypto/x509/pkix" + "encoding/asn1" enchex "encoding/hex" "errors" "fmt" @@ -12,14 +13,14 @@ import ( ) var attributeTypeNames = map[string]string{ - "2.5.4.6": "C", - "2.5.4.10": "O", - "2.5.4.11": "OU", "2.5.4.3": "CN", "2.5.4.5": "SERIALNUMBER", + "2.5.4.6": "C", "2.5.4.7": "L", "2.5.4.8": "ST", "2.5.4.9": "STREET", + "2.5.4.10": "O", + "2.5.4.11": "OU", "2.5.4.17": "POSTALCODE", // FIXME: Add others. "0.9.2342.19200300.100.1.25": "DC", @@ -44,7 +45,7 @@ type DN struct { } // FromCertSubject takes a pkix.Name from a cert and returns a DN -// that uses the same set. +// that uses the same set. Does not support multi value RDNs. func FromCertSubject(subject pkix.Name) (*DN, error) { dn := &DN{ RDNs: make([]*RelativeDN, 0), @@ -73,6 +74,53 @@ func FromCertSubject(subject pkix.Name) (*DN, error) { return dn, nil } +// FromRawCertSubject takes a raw subject from a certificate +// and uses asn1.Unmarshal to get the individual RDNs in the +// original order, including multi-value RDNs. +func FromRawCertSubject(rawSubject []byte) (*DN, error) { + dn := &DN{ + RDNs: make([]*RelativeDN, 0), + } + var rdns pkix.RDNSequence + _, err := asn1.Unmarshal(rawSubject, &rdns) + if err != nil { + return nil, err + } + + for i := len(rdns) - 1; i >= 0; i-- { + rdn := rdns[i] + if len(rdn) == 0 { + continue + } + + r := &RelativeDN{} + attrs := make([]*AttributeTypeAndValue, 0) + for j := len(rdn) - 1; j >= 0; j-- { + atv := rdn[j] + + typeName := "" + name := atv.Type.String() + typeName, ok := attributeTypeNames[name] + if !ok { + return nil, fmt.Errorf("invalid type name: %+v", name) + } + value, ok := atv.Value.(string) + if !ok { + return nil, fmt.Errorf("invalid type value: %+v", atv.Value) + } + attr := &AttributeTypeAndValue{ + Type: typeName, + Value: value, + } + attrs = append(attrs, attr) + } + r.Attributes = attrs + dn.RDNs = append(dn.RDNs, r) + } + + return dn, nil +} + // ParseDN returns a distinguishedName or an error. // The function respects https://tools.ietf.org/html/rfc4514 func ParseDN(str string) (*DN, error) { @@ -181,6 +229,29 @@ func (d *DN) Equal(other *DN) bool { return true } +// RDNsMatch returns true if the individual RDNs of the DNs +// are the same regardless of ordering. +func (d *DN) RDNsMatch(other *DN) bool { + if len(d.RDNs) != len(other.RDNs) { + return false + } + +CheckNextRDN: + for _, irdn := range d.RDNs { + for _, ordn := range other.RDNs { + if (len(irdn.Attributes) == len(ordn.Attributes)) && + (irdn.hasAllAttributes(ordn.Attributes) && ordn.hasAllAttributes(irdn.Attributes)) { + // Found the RDN, check if next one matches. + continue CheckNextRDN + } + } + + // Could not find a matching individual RDN, auth fails. + return false + } + return true +} + // AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" diff --git a/server/auth.go b/server/auth.go index 7043b2cc..2356c334 100644 --- a/server/auth.go +++ b/server/auth.go @@ -419,7 +419,7 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo } else if hasUsers { // Check if we are tls verify and are mapping users from the client_certificate. if tlsMap { - authorized := checkClientTLSCertSubject(c, func(u string, certRDN *ldap.DN, _ bool) (string, bool) { + authorized := checkClientTLSCertSubject(c, func(u string, certDN *ldap.DN, _ bool) (string, bool) { // First do literal lookup using the resulting string representation // of RDNSequence as implemented by the pkix package from Go. if u != "" { @@ -431,23 +431,36 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo return usr.Username, ok } - if certRDN == nil { + if certDN == nil { return "", false } - // Look through the accounts for an RDN that is equal to the one + // Look through the accounts for a DN that is equal to the one // presented by the certificate. + dns := make(map[*User]*ldap.DN) for _, usr := range s.users { if !c.connectionTypeAllowed(usr.AllowedConnectionTypes) { continue } // TODO: Use this utility to make a full validation pass // on start in case tlsmap feature is being used. - inputRDN, err := ldap.ParseDN(usr.Username) + inputDN, err := ldap.ParseDN(usr.Username) if err != nil { continue } - if inputRDN.Equal(certRDN) { + if inputDN.Equal(certDN) { + user = usr + return usr.Username, true + } + + // In case it did not match exactly, then collect the DNs + // and try to match later in case the DN was reordered. + dns[usr] = inputDN + } + + // Check in case the DN was reordered. + for usr, inputDN := range dns { + if inputDN.RDNsMatch(certDN) { user = usr return usr.Username, true } @@ -724,8 +737,9 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool { // the domain components in case there are any. rdn := cert.Subject.ToRDNSequence().String() - // Match that follows original order from the subject takes precedence. - dn, err := ldap.FromCertSubject(cert.Subject) + // Match using the raw subject to avoid ignoring attributes. + // https://github.com/golang/go/issues/12342 + dn, err := ldap.FromRawCertSubject(cert.RawSubject) if err == nil { if match, ok := fn("", dn, false); ok { c.Debugf("Using DistinguishedNameMatch for auth [%q]", match) diff --git a/test/configs/certs/rdns/client-e.key b/test/configs/certs/rdns/client-e.key new file mode 100644 index 00000000..e746f973 --- /dev/null +++ b/test/configs/certs/rdns/client-e.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC0a5HVNplJPJal +AXRm8ncYRs9Qpe3q0oW6V0f3URX0smnirK0TjjTdAnsBXAj0Cr9QN7ZIiJ0vAp6z +ovB8bFslWZ1g7oi/bRodNiIpkc28oyUY9vLvFxFiMfBdtDATljxEeBQUbPTXugR0 +QBKFbPvNVRAYxfvagq+DlA5/bX6Src7viNbOViOYPhjgcOGknaBLTpoLd9+q0QFO +NDQ3c7T6UA6eqd8o6Yb3NMxYmiWfDkDM3OV+GvUdH7DTAdcW9iHDn5Xj4bj09j/p +9wtrIVmsVpygMEvf4HDSYTt+MOCoF5W7EKqy09doThc5HPTSQIllQ2N/Yx23lqI9 +FNEXJgfHAgMBAAECggEAUmdzQyvd1TpsH89LSB3kUV0+ITq4MPGYjKSCxS3u1kWK +4TI3FuBzuqIAZn2PxU8HVG8tvXFQQYFz1N5N8rZW5vdIT1aDdNMzAzaPYecrTcZC +EmXwTU1+7hebDmFXOAr9WdRyb2XYapOWpzYAf5poY78/S+FZh9L6sSE1gfQTxJAD +qf1pFAj2HMNQ3EbEG8Lg0Cu67GuMp1E0aYZTjbE75do/kukTA4KCjVFmis8zz0K4 +KNqWMvv1e4lnv70AgIhxBXjYewiHgwWhp+6Hku3NszkjJyqG6DiENKb05K2G9s7c +1xhZ9rIJjg2IXtKPsLx2uBA9uBqAztXcbhcQC7PiUQKBgQDe4/38NAD6rqk++HDx +SnJQs6N5rGyLWEav0r684kpXMqvtvct8sBb1mptM+92D1XOFLP2sbhi9Yfs8xdIn +P0nmO10qO4hDz40NdqfVUookFfZGW0MLnCfAxqSUAfBvzIrrDiVpcQWmm9h7tfXy +ToZuz9FCAEQmAinaHk04w677OQKBgQDPOIcOWY5iBPZyPLTNbZm1mM9RzZSWNiW+ +da39DvB6opCo0WZ9HckzzuCsDAM6HpgPmsBQXyA5ZP6j/JaP4tEsoEjNEiSZUXaZ ++ztOM5IDOv61Lnq3WczZxZAVNSkJMNGYAYA6sDnUVDhRLhlY4g8aQ0huWIWdW55g +ANm5mwYa/wKBgDLVpuC1b5+85CbTfNbbVtUnE1q1w4/IU17YXt4vcisPCH1Rcy59 +7s6XM2JMc0oVDaLLDxQbjBLtXOKQb4y5933F/kqah0qH9LCkZkTV7WGrjJ6hQ9pL +BBoIdBK5mn+1E93mPQweVd6Y3rfgWTapSCnPxfcannBYv/jaPlx67NapAoGAax5X +gm19EuJp20fSVtcvPBaQJUNWagf3nusKU+RjH6Hlkb8dcdPx7Fwm/AkBqguio35l +p6Zk7AZvM6og0qR3aNA6kfes/6yC2LpsP9Kcyhq3DEXInftHz9M21h+y5NNdpWwx +MyVh34bhzeU8qRvCntrlGFWeTGfOCOanpjCjCVUCgYBRkcm9q7Y9GeDfyy4XEFM6 +UQM2SNn97Ytq/1B3YvCNC2SCQVXDWFuwNDLeoTWSw+vhuIDdIV8+5tx8w2Av+1MB +3tClcyZFNUYOTwEi1N9l41cPuvPsRthlA/10c+C/y/C1KDQzlwmvi6pzG+r8xp2s +OGuZfFNwdq/hPA5HZ91Sxg== +-----END PRIVATE KEY----- diff --git a/test/configs/certs/rdns/client-e.pem b/test/configs/certs/rdns/client-e.pem new file mode 100644 index 00000000..b04bbe53 --- /dev/null +++ b/test/configs/certs/rdns/client-e.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIUKMTp3zRsyUOYWPKNF1VeFwIIiNIwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHTkFUUyBDQTAeFw0yMDExMTUyMTQzMzVaFw0yNTExMTQy +MTQzMzVaMIGGMREwDwYDVQQDDAhKb2huIERvZTEPMA0GA1UEAwwGMTIzNDU2MQ0w +CwYDVQQDDARqZG9lMQ4wDAYDVQQLDAVVc2VyczEWMBQGA1UECwwNT3JnYW5pYyBV +bml0czEUMBIGCgmSJomT8ixkARkWBGFjbWUxEzARBgoJkiaJk/IsZAEZFgNjb20w +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0a5HVNplJPJalAXRm8ncY +Rs9Qpe3q0oW6V0f3URX0smnirK0TjjTdAnsBXAj0Cr9QN7ZIiJ0vAp6zovB8bFsl +WZ1g7oi/bRodNiIpkc28oyUY9vLvFxFiMfBdtDATljxEeBQUbPTXugR0QBKFbPvN +VRAYxfvagq+DlA5/bX6Src7viNbOViOYPhjgcOGknaBLTpoLd9+q0QFONDQ3c7T6 +UA6eqd8o6Yb3NMxYmiWfDkDM3OV+GvUdH7DTAdcW9iHDn5Xj4bj09j/p9wtrIVms +VpygMEvf4HDSYTt+MOCoF5W7EKqy09doThc5HPTSQIllQ2N/Yx23lqI9FNEXJgfH +AgMBAAGjNjA0MDIGA1UdEQQrMCmCCWxvY2FsaG9zdIILZXhhbXBsZS5jb22CD3d3 +dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEApeGRn3qIEpO65GxEWJMI +VfgUSRTsTLTGgqLfKXORlp5/75hJGbR39Al3rac2OogHc0hnR3cUL4KaoXqN/W3S +lH/JcAqiqblkFmRmUkl5YmthWUgiTtm+To37j/w2owZkoSItpLaYkK8bj035Z6CH +ehnuvZmOLQ4vpjwhew7Vawaz4UmoF0GugsgwybppH+j3hX9RkSo298jja5cRzV24 +iA9jIEFZdzHPYJ/cIK6NW2tu+1ZC61ol6pVif3irBanME/IjEoqFnvwv76MiGvqb +T1OTRUf2ny2etK8pgcOcXzsP4P+PqMdZohrqH8aQRQ24rd6LclcarNEAQ5rGo/nu +qg== +-----END CERTIFICATE----- diff --git a/test/configs/certs/rdns/client-f.key b/test/configs/certs/rdns/client-f.key new file mode 100644 index 00000000..c3dd4743 --- /dev/null +++ b/test/configs/certs/rdns/client-f.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDReroKzpObO1KW +SiPX3bcu6Z20oE53dBGURo58kEcZAErRSkqiieoS/d1xWiVnmTc4oIsJmrIIyNk4 +rO3l3Mu2lCGfVxhNtFhHrPCdtFctXYRu3c7QAcQCr/HjEzcq2oEtQD83wA16D6PH +4PmDfpMhJ0N43FvbDoFOSA61NxxnFkeOyamy6D4QnrIYBxpTvIHdEKCbt2oZsnyD +jlmpZdkkTuW51SNXlxz1o++g0+D9R/nzhnojkU4kVVFe0njlLnWrAuN0c2Y4FQuw +P1rsMOlUHzpdyVc+KT/Kl33oaEr5pxZujhazDCag47SZgPal9EFyS7VfpyMy457m +XEGUHoczAgMBAAECggEAeG6kcv4c4owSiREK1lpDrJbm8iePtSFn0eVWmcqg9YCz +guvBSP0dM9n76+U1x//QPaAfD2B+popCSFEzXIm6HLfBNMhv0oyyjFKi6yf5Tr2L +G+otsmyxchIRcMllWB/TUF61eanSlbBUKt/u02h70f2uztdxf9kxAf5vZkPO8nxT +JSWdkYM4aha56TGFrSxAgLFpmz5rHcp8CH54C0GD4GSafL56hdOV3lfGH2OqwBRf +DrXVR857JvTPyoSlkuUTAtQbGTrLVyvIRSbkYHZOFk9JeNkEHl1j1wuvLf5+BHk4 +olEb4vGjWnuy2EDAStL6QROuyPlQW+Ngd0YPUiiwEQKBgQD+gzh5V1Wp/gRc706a +9D22njMmNd/9qJL9n80GJnpRNrb8Cp9vXf50SeFMFBlMy6VJbnh9qRcGtI9fvUae +ifmWiM08+oFwyAO4HcSY182TDZMokvARy37XtR7OZVkDootoLz+zlHa0s2eLyIa8 +NcUzJI5OEeQeq+o+UXlgZ3FbRwKBgQDStCGlm6ptY9RDCDhV2ruep3j5G/V3T8Bm +93gbHp+2EBQfUu9fSY1h2K/6bcZff4Thqcd5TtF/zpG0DQ7jseYcg9nwlKzUF0w8 +4tYCpQJj+4cHjx9bHNsDLw/7a92jesU4kjah7Tt8JUmEt8lYdXYTzokE/2hs7Ay2 +NsevLgIStQKBgQDJdsusWXKI5ndDrXaWeAGl3eJ1O64710XLl8QuOyUVxm7gYfRE +rq2uFZFOrJY+UPFciCK+rat5dlILogMVmfhErbNwsobl5J31DzNBHYov/k3fjziT +jXaxf0CMdnMYyoD5jnUpTLsOXPj5EFl/AD1CN4yhxc3Cbak1fT7MDfYQHwKBgAPN +qpnRsIbe+XLoUBQEqcRYY4+jmI+5ydBSAUIEEH/51FMobRe8PSgaADs2BhGtPJnS +Nb6T1KZI9UpZvf4QNQYovyNfm6sMbJzgv1o23k8tuCdDxx4e7DknfVNdhBeyXKMD +yKatoJhCGAykQKcvH52F6eVEMv9cV3JmlL4tx23NAoGBAJCrkp0nZhYdeIGNAOqW +vHgHhnXxsqiK+yZ+V4tAUR7c0tr+l7xWxFlVZiXax3bXEMfoyzS3yGlEQoFANvVw +afN3t/pIox6IYChUrJiRHy58rSOGEEhgWfVOQ9xD1qLfd1aDCtroFjTvbsRYv7Vg +mvMiw5rle3jP3qtjDkg+9U9T +-----END PRIVATE KEY----- diff --git a/test/configs/certs/rdns/client-f.pem b/test/configs/certs/rdns/client-f.pem new file mode 100644 index 00000000..b35785c8 --- /dev/null +++ b/test/configs/certs/rdns/client-f.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOjCCAiKgAwIBAgIUKMTp3zRsyUOYWPKNF1VeFwIIiNMwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHTkFUUyBDQTAeFw0yMDExMTUyMzQ4NTVaFw0yNTExMTQy +MzQ4NTVaMGQxEzARBgoJkiaJk/IsZAEZFgNvcmcxFzAVBgoJkiaJk/IsZAEZFgdP +cGVuU1NMMSEwDAYDVQQKDAV1c2VyczARBgoJkiaJk/IsZAEZFgNERVYxETAPBgNV +BAMMCEpvaG4gRG9lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Xq6 +Cs6TmztSlkoj1923LumdtKBOd3QRlEaOfJBHGQBK0UpKoonqEv3dcVolZ5k3OKCL +CZqyCMjZOKzt5dzLtpQhn1cYTbRYR6zwnbRXLV2Ebt3O0AHEAq/x4xM3KtqBLUA/ +N8ANeg+jx+D5g36TISdDeNxb2w6BTkgOtTccZxZHjsmpsug+EJ6yGAcaU7yB3RCg +m7dqGbJ8g45ZqWXZJE7ludUjV5cc9aPvoNPg/Uf584Z6I5FOJFVRXtJ45S51qwLj +dHNmOBULsD9a7DDpVB86XclXPik/ypd96GhK+acWbo4WswwmoOO0mYD2pfRBcku1 +X6cjMuOe5lxBlB6HMwIDAQABozYwNDAyBgNVHREEKzApgglsb2NhbGhvc3SCC2V4 +YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAAtb +cxttYaief3XTMyFa2/dqF6JO47pTCYuCF1i3jL6sPokm2k0L4aCJZTthuUwHBppK +fWWGEfERpD1GnEJPW65BRZYFEYFdLEGvT8u9UAnuwbZK/rGSp6K/P2bhCrWZt2qy +eA/WQNnWDJ9mXAH6nrJCDPd1ReFr/gidvtw7PJI7pqvu6/oi0H5VpR/RWRHZieWd +UplTd2yt3vLaBX592oybfaA5bGQ/lbNaxvZniwUjmR069EvyW5WCGCR0+AON1NxP +y5x22lf2HxvxHVDxUb1FTHYvRduy0zbpmYKpjfRXS2IY6fto246Xhv90NBDzgWP1 +K3erVOvmsj6nNnfcE/A= +-----END CERTIFICATE----- diff --git a/test/tls_test.go b/test/tls_test.go index 17ceac39..45b0f7d1 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -1540,7 +1540,10 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) { users = [ { user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=example,DC=com", permissions = { subscribe = { deny = ">" }} } + + # This should take precedence since it is in the RFC2253 order. { user = "DC=com,DC=example,CN=*.example.com,O=NATS,OU=NATS,L=Los Angeles,ST=CA,C=US" } + { user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US", permissions = { subscribe = { deny = ">" }} } ] @@ -1553,6 +1556,209 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) { nil, nil, }, + { + "connect with tls and RDN includes multiple CN elements", + ` + port: -1 + %s + + authorization { + users = [ + { user = "DC=com,DC=acme,OU=Organic Units,OU=Users,CN=jdoe,CN=123456,CN=John Doe" } + ] + } + `, + // + // OpenSSL: -subj "/CN=John Doe/CN=123456/CN=jdoe/OU=Users/OU=Organic Units/DC=acme/DC=com" + // Go: CN=jdoe,OU=Users+OU=Organic Units + // RFC2253: DC=com,DC=acme,OU=Organic Units,OU=Users,CN=jdoe,CN=123456,CN=John Doe + // + nats.ClientCert("./configs/certs/rdns/client-e.pem", "./configs/certs/rdns/client-e.key"), + nil, + nil, + }, + { + "connect with tls and DN includes a multi value RDN", + ` + port: -1 + %s + + authorization { + users = [ + { user = "CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + nil, + nil, + }, + { + "connect with tls and DN includes a multi value RDN but there is no match", + ` + port: -1 + %s + + authorization { + users = [ + { user = "CN=John Doe,DC=DEV,DC=OpenSSL,DC=org" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + errors.New("nats: Authorization Violation"), + nil, + }, + { + "connect with tls and DN includes a multi value RDN that are reordered", + ` + port: -1 + %s + + authorization { + users = [ + { user = "CN=John Doe,O=users+DC=DEV,DC=OpenSSL,DC=org" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + nil, + nil, + }, + } { + t.Run(test.name, func(t *testing.T) { + content := fmt.Sprintf(test.config, ` + tls { + cert_file: "configs/certs/rdns/server.pem" + key_file: "configs/certs/rdns/server.key" + ca_file: "configs/certs/rdns/ca.pem" + timeout: 5 + verify_and_map: true + } + `) + conf := createConfFile(t, []byte(content)) + defer os.Remove(conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + test.certs, + nats.RootCAs("./configs/certs/rdns/ca.pem"), + ) + if test.err == nil && err != nil { + t.Errorf("Expected to connect, got %v", err) + } else if test.err != nil && err == nil { + t.Errorf("Expected error on connect") + } else if test.err != nil && err != nil { + // Error on connect was expected + if test.err.Error() != err.Error() { + t.Errorf("Expected error %s, got: %s", test.err, err) + } + return + } + defer nc.Close() + + nc.Subscribe("ping", func(m *nats.Msg) { + m.Respond([]byte("pong")) + }) + nc.Flush() + + _, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond) + if test.rerr != nil && err == nil { + t.Errorf("Expected error getting response") + } else if test.rerr == nil && err != nil { + t.Errorf("Expected response") + } + }) + } +} + +func TestTLSClientAuthWithRDNSequenceReordered(t *testing.T) { + for _, test := range []struct { + name string + config string + certs nats.Option + err error + rerr error + }{ + { + "connect with tls and DN includes a multi value RDN that are reordered", + ` + port: -1 + %s + + authorization { + users = [ + { user = "DC=org,DC=OpenSSL,O=users+DC=DEV,CN=John Doe" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + nil, + nil, + }, + { + "connect with tls and DN includes a multi value RDN that are reordered but not equal RDNs", + ` + port: -1 + %s + + authorization { + users = [ + { user = "DC=org,DC=OpenSSL,O=users,CN=John Doe" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + errors.New("nats: Authorization Violation"), + nil, + }, + { + "connect with tls and DN includes a multi value RDN that are reordered but not equal RDNs", + ` + port: -1 + %s + + authorization { + users = [ + { user = "DC=OpenSSL, DC=org, O=users, CN=John Doe" } + ] + } + `, + // + // OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn + // Go: CN=John Doe,O=users + // RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org + // + nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"), + errors.New("nats: Authorization Violation"), + nil, + }, } { t.Run(test.name, func(t *testing.T) { content := fmt.Sprintf(test.config, `