Changeset 307 in code for trunk/service.go


Ignore:
Timestamp:
Jun 2, 2020, 9:24:22 AM (5 years ago)
Author:
fox.cpp
Message:

Implement upstream SASL EXTERNAL support

Closes: https://todo.sr.ht/~emersion/soju/47

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/service.go

    r279 r307  
    22
    33import (
     4        "crypto"
     5        "crypto/ecdsa"
     6        "crypto/ed25519"
     7        "crypto/elliptic"
     8        "crypto/rand"
     9        "crypto/rsa"
     10        "crypto/sha1"
     11        "crypto/sha256"
     12        "crypto/x509"
     13        "crypto/x509/pkix"
     14        "encoding/hex"
     15        "errors"
    416        "flag"
    517        "fmt"
    618        "io/ioutil"
     19        "math/big"
    720        "strings"
     21        "time"
    822
    923        "github.com/google/shlex"
     
    120134                        },
    121135                },
     136                "certfp": {
     137                        children: serviceCommandSet{
     138                                "generate": {
     139                                        usage:  "[-key-type rsa|ecdsa|ed25519] [-bits N] <network name>",
     140                                        desc:   "generate a new self-signed certificate, defaults to using RSA-3072 key",
     141                                        handle: handleServiceCertfpGenerate,
     142                                },
     143                                "fingerprint": {
     144                                        usage:  "<network name>",
     145                                        desc:   "show fingerprints of certificate associated with the network",
     146                                        handle: handleServiceCertfpFingerprints,
     147                                },
     148                                "reset": {
     149                                        usage:  "<network name>",
     150                                        desc:   "disable SASL EXTERNAL authentication and remove stored certificate",
     151                                        handle: handleServiceCertfpReset,
     152                                },
     153                        },
     154                },
    122155                "change-password": {
    123156                        usage:  "<new password>",
     
    126159                },
    127160        }
     161}
     162
     163func handleServiceCertfpGenerate(dc *downstreamConn, params []string) error {
     164        fs := newFlagSet()
     165        keyType := fs.String("key-type", "rsa", "key type to generate (rsa, ecdsa, ed25519)")
     166        bits := fs.Int("bits", 3072, "size of key to generate, meaningful only for RSA")
     167
     168        if err := fs.Parse(params); err != nil {
     169                return err
     170        }
     171
     172        if len(fs.Args()) != 1 {
     173                return errors.New("exactly one argument is required")
     174        }
     175
     176        net := dc.user.getNetwork(fs.Arg(0))
     177        if net == nil {
     178                return fmt.Errorf("unknown network %q", fs.Arg(0))
     179        }
     180
     181        var (
     182                privKey crypto.PrivateKey
     183                pubKey  crypto.PublicKey
     184        )
     185        switch *keyType {
     186        case "rsa":
     187                key, err := rsa.GenerateKey(rand.Reader, *bits)
     188                if err != nil {
     189                        return err
     190                }
     191                privKey = key
     192                pubKey = key.Public()
     193        case "ecdsa":
     194                key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
     195                if err != nil {
     196                        return err
     197                }
     198                privKey = key
     199                pubKey = key.Public()
     200        case "ed25519":
     201                var err error
     202                pubKey, privKey, err = ed25519.GenerateKey(rand.Reader)
     203                if err != nil {
     204                        return err
     205                }
     206        }
     207
     208        // Using PKCS#8 allows easier extension for new key types.
     209        privKeyBytes, err := x509.MarshalPKCS8PrivateKey(privKey)
     210        if err != nil {
     211                return err
     212        }
     213
     214        notBefore := time.Now()
     215        // Lets make a fair assumption nobody will use the same cert for more than 20 years...
     216        notAfter := notBefore.Add(24 * time.Hour * 365 * 20)
     217        serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
     218        serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
     219        if err != nil {
     220                return err
     221        }
     222        cert := &x509.Certificate{
     223                SerialNumber: serialNumber,
     224                Subject:      pkix.Name{CommonName: "soju auto-generated certificate"},
     225                NotBefore:    notBefore,
     226                NotAfter:     notAfter,
     227                KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
     228                ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
     229        }
     230        derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, pubKey, privKey)
     231        if err != nil {
     232                return err
     233        }
     234
     235        net.SASL.External.CertBlob = derBytes
     236        net.SASL.External.PrivKeyBlob = privKeyBytes
     237        net.SASL.Mechanism = "EXTERNAL"
     238
     239        if err := dc.srv.db.StoreNetwork(net.Username, &net.Network); err != nil {
     240                return err
     241        }
     242
     243        sendServicePRIVMSG(dc, "certificate generated")
     244
     245        sha1Sum := sha1.Sum(derBytes)
     246        sendServicePRIVMSG(dc, "SHA-1 fingerprint: "+hex.EncodeToString(sha1Sum[:]))
     247        sha256Sum := sha256.Sum256(derBytes)
     248        sendServicePRIVMSG(dc, "SHA-256 fingerprint: "+hex.EncodeToString(sha256Sum[:]))
     249
     250        return nil
     251}
     252
     253func handleServiceCertfpFingerprints(dc *downstreamConn, params []string) error {
     254        if len(params) != 1 {
     255                return fmt.Errorf("expected exactly one argument")
     256        }
     257
     258        net := dc.user.getNetwork(params[0])
     259        if net == nil {
     260                return fmt.Errorf("unknown network %q", params[0])
     261        }
     262
     263        sha1Sum := sha1.Sum(net.SASL.External.CertBlob)
     264        sendServicePRIVMSG(dc, "SHA-1 fingerprint: "+hex.EncodeToString(sha1Sum[:]))
     265        sha256Sum := sha256.Sum256(net.SASL.External.CertBlob)
     266        sendServicePRIVMSG(dc, "SHA-256 fingerprint: "+hex.EncodeToString(sha256Sum[:]))
     267        return nil
     268}
     269
     270func handleServiceCertfpReset(dc *downstreamConn, params []string) error {
     271        if len(params) != 1 {
     272                return fmt.Errorf("expected exactly one argument")
     273        }
     274
     275        net := dc.user.getNetwork(params[0])
     276        if net == nil {
     277                return fmt.Errorf("unknown network %q", params[0])
     278        }
     279
     280        net.SASL.External.CertBlob = nil
     281        net.SASL.External.PrivKeyBlob = nil
     282
     283        if net.SASL.Mechanism == "EXTERNAL" {
     284                net.SASL.Mechanism = ""
     285        }
     286        if err := dc.srv.db.StoreNetwork(dc.user.Username, &net.Network); err != nil {
     287                return err
     288        }
     289
     290        sendServicePRIVMSG(dc, "certificate reset")
     291        return nil
    128292}
    129293
Note: See TracChangeset for help on using the changeset viewer.