1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
package logic
import (
"crypto/x509"
"encoding/pem"
"fmt"
"rss-kakapo/dal"
"rss-kakapo/shared"
"sort"
)
// MigrateEncryptedPrivKeys rewrites every account's private key that is still
// stored encrypted as a plain PEM block, decrypting it with the passphrase
// from the secrets file.
//
// Keys used to be encrypted with birb_privkey_passphrase, which bought almost
// nothing and cost a secret that could never be lost (see
// keyStore.MakeKeyPair for why it went). Those keys are an instance's
// accounts: they cannot be regenerated, since other servers have cached their
// public halves, so they are converted rather than replaced.
//
// It runs at every startup and does nothing — without reading a key, without
// touching the database — once no key is encrypted, which is the state right
// after the first run. The passphrase is only needed while there is something
// to convert, and nothing but this reads it any more.
//
// Each key is written on its own, so a key that cannot be decrypted (a
// passphrase that was changed under the instance, say) leaves the rest
// converted and is named in the error.
func MigrateEncryptedPrivKeys(cfg *shared.Config, repo dal.IRepo, logger shared.ILogger) error {
keys, err := repo.ListPrivKeys()
if err != nil {
return err
}
var encrypted []string
for handle, privKey := range keys {
block, _ := pem.Decode([]byte(privKey))
if block != nil && x509.IsEncryptedPEMBlock(block) {
encrypted = append(encrypted, handle)
}
}
if len(encrypted) == 0 {
return nil
}
sort.Strings(encrypted)
pass := []byte(cfg.Secrets.BirbPrivKeyPass)
logger.Printf("Converting %d encrypted private key(s) to plain storage", len(encrypted))
var failed []string
for _, handle := range encrypted {
block, _ := pem.Decode([]byte(keys[handle]))
keyBytes, err := x509.DecryptPEMBlock(block, pass)
if err != nil {
logger.Errorf("Failed to decrypt private key of '%s': %v", handle, err)
failed = append(failed, handle)
continue
}
// Parsed before it is stored: a wrong passphrase can decrypt to
// garbage without erroring (the format authenticates nothing), and
// storing that would lose the key for good.
if _, err := x509.ParsePKCS1PrivateKey(keyBytes); err != nil {
logger.Errorf("Decrypted private key of '%s' is not a key: %v", handle, err)
failed = append(failed, handle)
continue
}
plain := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
if err := repo.SetPrivKey(handle, string(plain)); err != nil {
return fmt.Errorf("storing private key of '%s': %w", handle, err)
}
}
if len(failed) != 0 {
return fmt.Errorf("could not decrypt the private key of %v; is birb_privkey_passphrase the one they were encrypted with?", failed)
}
logger.Printf("Converted %d private key(s); birb_privkey_passphrase is no longer needed", len(encrypted))
return nil
}
|