-
Notifications
You must be signed in to change notification settings - Fork 137
/
confirm_flagship.go
102 lines (90 loc) · 2.83 KB
/
confirm_flagship.go
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package oauth
import (
"crypto/sha256"
"encoding/base32"
"io"
"time"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/job"
"github.com/cozy/cozy-stack/model/settings"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/hkdf"
)
var macConfig = crypto.MACConfig{
Name: "confirm-flagship",
MaxAge: 0,
MaxLen: 256,
}
var totpOptions = totp.ValidateOpts{
Period: 30, // 30s
Skew: 10, // 30s +- 10*30s = [-5min; 5,5min]
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA256,
}
// SendConfirmFlagshipCode sends by mail a code to the owner of the instance.
// It returns the generated token which can be used to check the code.
func SendConfirmFlagshipCode(inst *instance.Instance, clientID string) ([]byte, error) {
token, code, err := GenerateConfirmCode(inst, clientID)
if err != nil {
return nil, err
}
publicName, _ := settings.PublicName(inst)
msg, err := job.NewMessage(map[string]interface{}{
"mode": "noreply",
"template_name": "confirm_flagship",
"template_values": map[string]interface{}{
"Code": code,
"PublicName": publicName,
},
})
if err != nil {
return nil, err
}
_, err = job.System().PushJob(inst, &job.JobRequest{
WorkerType: "sendmail",
Message: msg,
})
if err != nil {
return nil, err
}
return token, nil
}
// CheckFlagshipCode returns true if the code is correct and can be used to set
// the flagship flag on the given OAuth client.
func CheckFlagshipCode(inst *instance.Instance, clientID string, token []byte, code string) bool {
salt, err := crypto.DecodeAuthMessage(macConfig, inst.SessionSecret(), token, nil)
if err != nil {
return false
}
input := []byte(clientID + "-" + string(inst.SessionSecret()))
h := hkdf.New(sha256.New, input, salt, nil)
key := make([]byte, 32)
_, err = io.ReadFull(h, key)
if err != nil {
return false
}
secret := base32.StdEncoding.EncodeToString(key)
ok, err := totp.ValidateCustom(code, secret, time.Now().UTC(), totpOptions)
return ok && err == nil
}
// GenerateConfirmCode generate a 6-digits code and the token to check it. They
// can be used to manually confirm that an OAuth client is the flagship app.
func GenerateConfirmCode(inst *instance.Instance, clientID string) ([]byte, string, error) {
salt := crypto.GenerateRandomBytes(sha256.Size)
token, err := crypto.EncodeAuthMessage(macConfig, inst.SessionSecret(), salt, nil)
if err != nil {
return nil, "", err
}
input := []byte(clientID + "-" + string(inst.SessionSecret()))
h := hkdf.New(sha256.New, input, salt, nil)
key := make([]byte, 32)
_, err = io.ReadFull(h, key)
if err != nil {
return nil, "", err
}
secret := base32.StdEncoding.EncodeToString(key)
code, err := totp.GenerateCodeCustom(secret, time.Now().UTC(), totpOptions)
return token, code, err
}