/
android_safety_net.go
124 lines (112 loc) · 3.43 KB
/
android_safety_net.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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package oauth
import (
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/pkg/config/config"
"github.com/cozy/cozy-stack/pkg/logger"
jwt "github.com/golang-jwt/jwt/v5"
)
// checkSafetyNetAttestation will check an attestation made by the SafetyNet API.
// Cf https://developer.android.com/training/safetynet/attestation#use-response-server
func (c *Client) checkSafetyNetAttestation(inst *instance.Instance, req AttestationRequest) error {
store := GetStore()
if ok := store.CheckAndClearChallenge(inst, c.ID(), req.Challenge); !ok {
return errors.New("invalid challenge")
}
token, err := jwt.Parse(req.Attestation, safetyNetKeyFunc)
if err != nil {
return fmt.Errorf("cannot parse attestation: %s", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return errors.New("invalid claims type")
}
inst.Logger().Debugf("checkSafetyNetAttestation claims = %#v", claims)
nonce, ok := claims["nonce"].(string)
if !ok || len(nonce) == 0 {
return errors.New("missing nonce")
}
if req.Challenge != nonce {
return errors.New("invalid nonce")
}
if err := checkSafetyNetPackageName(claims); err != nil {
return err
}
if err := checkSafetyNetCertificateDigest(claims); err != nil {
return err
}
return nil
}
func checkSafetyNetPackageName(claims jwt.MapClaims) error {
packageName, ok := claims["apkPackageName"].(string)
if !ok || len(packageName) == 0 {
return errors.New("missing apkPackageName")
}
names := config.GetConfig().Flagship.APKPackageNames
for _, name := range names {
if name == packageName {
return nil
}
}
return fmt.Errorf("%s is not the package name of the flagship app", packageName)
}
func checkSafetyNetCertificateDigest(claims jwt.MapClaims) error {
certDigest, ok := claims["apkCertificateDigestSha256"].([]interface{})
if !ok || len(certDigest) == 0 {
return errors.New("missing apkCertificateDigestSha256")
}
digests := config.GetConfig().Flagship.APKCertificateDigests
for _, digest := range digests {
if digest == certDigest[0] {
return nil
}
}
logger.WithNamespace("oauth").
Debugf("Invalid certificate digest, expected %s, got %s", digests[0], certDigest[0])
return errors.New("invalid certificate digest")
}
func safetyNetKeyFunc(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
x5c, ok := token.Header["x5c"].([]interface{})
if !ok || len(x5c) == 0 {
return nil, errors.New("missing certification")
}
certs := make([]*x509.Certificate, 0, len(x5c))
for _, raw := range x5c {
rawStr, ok := raw.(string)
if !ok {
return nil, errors.New("missing certification")
}
buf, err := base64.StdEncoding.DecodeString(rawStr)
if err != nil {
return nil, fmt.Errorf("error decoding cert as base64: %s", err)
}
cert, err := x509.ParseCertificate(buf)
if err != nil {
return nil, fmt.Errorf("error parsing cert: %s", err)
}
certs = append(certs, cert)
}
intermediates := x509.NewCertPool()
for _, cert := range certs {
intermediates.AddCert(cert)
}
opts := x509.VerifyOptions{
DNSName: "attest.android.com",
Intermediates: intermediates,
}
if _, err := certs[0].Verify(opts); err != nil {
return nil, err
}
rsaKey, ok := certs[0].PublicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.New("invalid certification")
}
return rsaKey, nil
}