@@ -28,6 +28,9 @@ package mgo
28
28
29
29
import (
30
30
"crypto/md5"
31
+ "crypto/x509"
32
+ "crypto/x509/pkix"
33
+ "encoding/asn1"
31
34
"encoding/hex"
32
35
"errors"
33
36
"fmt"
@@ -825,6 +828,15 @@ type Credential struct {
825
828
// Mechanism defines the protocol for credential negotiation.
826
829
// Defaults to "MONGODB-CR".
827
830
Mechanism string
831
+
832
+ // Certificate defines an x509 certificate for authentication at login,
833
+ // for reference please see, https://docs.mongodb.com/manual/tutorial/configure-x509-client-authentication/
834
+ // If providing a certificate:
835
+ // The Username field is populated from the cert and should not be set
836
+ // The Mechanism field should be MONGODB-X509 or not set.
837
+ // The Source field should be $external or not set.
838
+ // If not specified, the username will have to be set manually.
839
+ Certificate * x509.Certificate
828
840
}
829
841
830
842
// Login authenticates with MongoDB using the provided credential. The
@@ -847,6 +859,19 @@ func (s *Session) Login(cred *Credential) error {
847
859
defer socket .Release ()
848
860
849
861
credCopy := * cred
862
+ if cred .Certificate != nil && cred .Username != "" {
863
+ return errors .New ("failed to login, both certificate and credentials are given" )
864
+ }
865
+
866
+ if cred .Certificate != nil {
867
+ credCopy .Username , err = getRFC2253NameStringFromCert (cred .Certificate )
868
+ if err != nil {
869
+ return err
870
+ }
871
+ credCopy .Mechanism = "MONGODB-X509"
872
+ credCopy .Source = "$external"
873
+ }
874
+
850
875
if cred .Source == "" {
851
876
if cred .Mechanism == "GSSAPI" {
852
877
credCopy .Source = "$external"
@@ -5212,3 +5237,73 @@ func hasErrMsg(d []byte) bool {
5212
5237
}
5213
5238
return false
5214
5239
}
5240
+
5241
+ // getRFC2253NameStringFromCert converts from an ASN.1 structured representation of the certificate
5242
+ // to a UTF-8 string representation(RDN) and returns it.
5243
+ func getRFC2253NameStringFromCert (certificate * x509.Certificate ) (string , error ) {
5244
+ var RDNElements = pkix.RDNSequence {}
5245
+ _ , err := asn1 .Unmarshal (certificate .RawSubject , & RDNElements )
5246
+ return getRFC2253NameString (& RDNElements ), err
5247
+ }
5248
+
5249
+ // getRFC2253NameString converts from an ASN.1 structured representation of the RDNSequence
5250
+ // from the certificate to a UTF-8 string representation(RDN) and returns it.
5251
+ func getRFC2253NameString (RDNElements * pkix.RDNSequence ) string {
5252
+ var RDNElementsString = []string {}
5253
+ var replacer = strings .NewReplacer ("," , "\\ ," , "=" , "\\ =" , "+" , "\\ +" , "<" , "\\ <" , ">" , "\\ >" , ";" , "\\ ;" )
5254
+ //The elements in the sequence needs to be reversed when converting them
5255
+ for i := len (* RDNElements ) - 1 ; i >= 0 ; i -- {
5256
+ var nameAndValueList = make ([]string ,len ((* RDNElements )[i ]))
5257
+ for j , attribute := range (* RDNElements )[i ] {
5258
+ var shortAttributeName = rdnOIDToShortName (attribute .Type )
5259
+ if len (shortAttributeName ) <= 0 {
5260
+ nameAndValueList [j ] = fmt .Sprintf ("%s=%X" , attribute .Type .String (), attribute .Value .([]byte ))
5261
+ continue
5262
+ }
5263
+ var attributeValueString = attribute .Value .(string )
5264
+ // escape leading space or #
5265
+ if strings .HasPrefix (attributeValueString , " " ) || strings .HasPrefix (attributeValueString , "#" ) {
5266
+ attributeValueString = "\\ " + attributeValueString
5267
+ }
5268
+ // escape trailing space, unless it's already escaped
5269
+ if strings .HasSuffix (attributeValueString , " " ) && ! strings .HasSuffix (attributeValueString , "\\ " ) {
5270
+ attributeValueString = attributeValueString [:len (attributeValueString )- 1 ] + "\\ "
5271
+ }
5272
+
5273
+ // escape , = + < > # ;
5274
+ attributeValueString = replacer .Replace (attributeValueString )
5275
+ nameAndValueList [j ] = fmt .Sprintf ("%s=%s" , shortAttributeName , attributeValueString )
5276
+ }
5277
+
5278
+ RDNElementsString = append (RDNElementsString , strings .Join (nameAndValueList , "+" ))
5279
+ }
5280
+
5281
+ return strings .Join (RDNElementsString , "," )
5282
+ }
5283
+
5284
+ var oidsToShortNames = []struct {
5285
+ oid asn1.ObjectIdentifier
5286
+ shortName string
5287
+ }{
5288
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 3 }, "CN" },
5289
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 6 }, "C" },
5290
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 7 }, "L" },
5291
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 8 }, "ST" },
5292
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 10 }, "O" },
5293
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 11 }, "OU" },
5294
+ {asn1.ObjectIdentifier {2 , 5 , 4 , 9 }, "STREET" },
5295
+ {asn1.ObjectIdentifier {0 , 9 , 2342 , 19200300 , 100 , 1 , 25 }, "DC" },
5296
+ {asn1.ObjectIdentifier {0 , 9 , 2342 , 19200300 , 100 , 1 , 1 }, "UID" },
5297
+ }
5298
+
5299
+ // rdnOIDToShortName returns an short name of the given RDN OID. If the OID does not have a short
5300
+ // name, the function returns an empty string
5301
+ func rdnOIDToShortName (oid asn1.ObjectIdentifier ) string {
5302
+ for i := range oidsToShortNames {
5303
+ if oidsToShortNames [i ].oid .Equal (oid ) {
5304
+ return oidsToShortNames [i ].shortName
5305
+ }
5306
+ }
5307
+
5308
+ return ""
5309
+ }
0 commit comments