17 "github.com/mjl-/bstore"
19 "github.com/mjl-/mox/mlog"
20 "github.com/mjl-/mox/mox-"
21 "github.com/mjl-/mox/smtp"
24// TLSPublicKey is a public key for use with TLS client authentication based on the
25// public key of the certificate.
26type TLSPublicKey struct {
27 // Raw-url-base64-encoded Subject Public Key Info of certificate.
29 Created time.Time `bstore:"nonzero,default now"`
30 Type string // E.g. "rsa-2048", "ecdsa-p256", "ed25519"
32 // Descriptive name to identify the key, e.g. the device where key is used.
33 Name string `bstore:"nonzero"`
35 // If set, new immediate authenticated TLS connections are not moved to
36 // "authenticated" state. For clients that don't understand it, and will try an
37 // authenticate command anyway.
40 CertDER []byte `bstore:"nonzero"`
41 Account string `bstore:"nonzero"` // Key authenticates this account.
42 LoginAddress string `bstore:"nonzero"` // Must belong to account.
45// AuthDB and AuthDBTypes are exported for ../backup.go.
47var AuthDBTypes = []any{TLSPublicKey{}}
50func Init(ctx context.Context) error {
52 return fmt.Errorf("already initialized")
54 pkglog := mlog.New("store", nil)
55 p := mox.DataDirPath("auth.db")
56 os.MkdirAll(filepath.Dir(p), 0770)
57 opts := bstore.Options{Timeout: 5 * time.Second, Perm: 0660, RegisterLogger: pkglog.Logger}
59 AuthDB, err = bstore.Open(ctx, p, &opts, AuthDBTypes...)
63// Close closes auth.db.
66 return fmt.Errorf("not open")
73// ParseTLSPublicKeyCert parses a certificate, preparing a TLSPublicKey for
74// insertion into the database. Caller must set fields that are not in the
75// certificat, such as Account and LoginAddress.
76func ParseTLSPublicKeyCert(certDER []byte) (TLSPublicKey, error) {
77 cert, err := x509.ParseCertificate(certDER)
79 return TLSPublicKey{}, fmt.Errorf("parsing certificate: %v", err)
81 name := cert.Subject.CommonName
82 if name == "" && cert.SerialNumber != nil {
83 name = fmt.Sprintf("serial %x", cert.SerialNumber.Bytes())
86 buf := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
87 fp := base64.RawURLEncoding.EncodeToString(buf[:])
89 switch k := cert.PublicKey.(type) {
93 return TLSPublicKey{}, fmt.Errorf("rsa keys smaller than 2048 bits not accepted")
95 typ = "rsa-" + fmt.Sprintf("%d", bits)
96 case *ecdsa.PublicKey:
97 typ = "ecdsa-" + strings.ReplaceAll(strings.ToLower(k.Params().Name), "-", "")
98 case ed25519.PublicKey:
101 return TLSPublicKey{}, fmt.Errorf("public key type %T not implemented", cert.PublicKey)
104 return TLSPublicKey{Fingerprint: fp, Type: typ, Name: name, CertDER: certDER}, nil
107// TLSPublicKeyList returns tls public keys. If accountOpt is empty, keys for all
108// accounts are returned.
109func TLSPublicKeyList(ctx context.Context, accountOpt string) ([]TLSPublicKey, error) {
110 q := bstore.QueryDB[TLSPublicKey](ctx, AuthDB)
111 if accountOpt != "" {
112 q.FilterNonzero(TLSPublicKey{Account: accountOpt})
117// TLSPublicKeyGet retrieves a single tls public key by fingerprint.
118// If absent, bstore.ErrAbsent is returned.
119func TLSPublicKeyGet(ctx context.Context, fingerprint string) (TLSPublicKey, error) {
120 pubKey := TLSPublicKey{Fingerprint: fingerprint}
121 err := AuthDB.Get(ctx, &pubKey)
125// TLSPublicKeyAdd adds a new tls public key.
127// Caller is responsible for checking the account and email address are valid.
128func TLSPublicKeyAdd(ctx context.Context, pubKey *TLSPublicKey) error {
129 if err := checkTLSPublicKeyAddress(pubKey.LoginAddress); err != nil {
132 return AuthDB.Insert(ctx, pubKey)
135// TLSPublicKeyUpdate updates an existing tls public key.
137// Caller is responsible for checking the account and email address are valid.
138func TLSPublicKeyUpdate(ctx context.Context, pubKey *TLSPublicKey) error {
139 if err := checkTLSPublicKeyAddress(pubKey.LoginAddress); err != nil {
142 return AuthDB.Update(ctx, pubKey)
145func checkTLSPublicKeyAddress(addr string) error {
146 a, err := smtp.ParseAddress(addr)
148 return fmt.Errorf("parsing login address %q: %v", addr, err)
150 if a.String() != addr {
151 return fmt.Errorf("login address %q must be specified in canonical form %q", addr, a.String())
156// TLSPublicKeyRemove removes a tls public key.
157func TLSPublicKeyRemove(ctx context.Context, fingerprint string) error {
158 k := TLSPublicKey{Fingerprint: fingerprint}
159 return AuthDB.Delete(ctx, &k)
162// TLSPublicKeyRemoveForAccount removes all tls public keys for an account.
163func TLSPublicKeyRemoveForAccount(ctx context.Context, account string) error {
164 q := bstore.QueryDB[TLSPublicKey](ctx, AuthDB)
165 q.FilterNonzero(TLSPublicKey{Account: account})