@@ -28,6 +28,9 @@ package mgo
2828
2929import (
3030 "crypto/md5"
31+ "crypto/x509"
32+ "crypto/x509/pkix"
33+ "encoding/asn1"
3134 "encoding/hex"
3235 "errors"
3336 "fmt"
@@ -825,6 +828,15 @@ type Credential struct {
825828 // Mechanism defines the protocol for credential negotiation.
826829 // Defaults to "MONGODB-CR".
827830 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
828840}
829841
830842// Login authenticates with MongoDB using the provided credential. The
@@ -847,6 +859,19 @@ func (s *Session) Login(cred *Credential) error {
847859 defer socket .Release ()
848860
849861 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+
850875 if cred .Source == "" {
851876 if cred .Mechanism == "GSSAPI" {
852877 credCopy .Source = "$external"
@@ -5212,3 +5237,73 @@ func hasErrMsg(d []byte) bool {
52125237 }
52135238 return false
52145239}
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