-
-
Notifications
You must be signed in to change notification settings - Fork 17
/
auth.go
150 lines (141 loc) · 4.68 KB
/
auth.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package service
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"time"
"github.com/hashicorp/go-multierror"
"github.com/plgd-dev/device/v2/pkg/net/coap"
"github.com/plgd-dev/device/v2/schema/plgdtime"
"github.com/plgd-dev/go-coap/v3/message/codes"
"github.com/plgd-dev/hub/v2/coap-gateway/uri"
"github.com/plgd-dev/hub/v2/pkg/net/grpc"
pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt"
pkgX509 "github.com/plgd-dev/hub/v2/pkg/security/x509"
)
type Interceptor = func(ctx context.Context, code codes.Code, path string) (context.Context, error)
func newAuthInterceptor() Interceptor {
return func(ctx context.Context, _ codes.Code, path string) (context.Context, error) {
switch path {
case uri.RefreshToken, uri.SignUp, uri.SignIn, plgdtime.ResourceURI:
return ctx, nil
}
e := ctx.Value(&authCtxKey)
if e == nil {
return ctx, errors.New("invalid authorization context")
}
authCtx := e.(*authorizationContext)
err := authCtx.IsValid()
if err != nil {
return ctx, err
}
return grpc.CtxWithIncomingToken(grpc.CtxWithToken(ctx, authCtx.GetAccessToken()), authCtx.GetAccessToken()), nil
}
}
func (s *Service) ValidateToken(ctx context.Context, token string) (pkgJwt.Claims, error) {
ctx, cancel := context.WithTimeout(ctx, s.config.APIs.COAP.KeepAlive.Timeout)
defer cancel()
m, err := s.jwtValidator.ParseWithContext(ctx, token)
if err != nil {
return nil, err
}
return pkgJwt.Claims(m), nil
}
func (s *Service) verifyDeviceID(tlsDeviceID string, claim pkgJwt.Claims) (string, error) {
jwtDeviceID, err := claim.GetDeviceID(s.config.APIs.COAP.Authorization.DeviceIDClaim)
if err != nil {
return "", fmt.Errorf("cannot get device id claim from access token: %w", err)
}
if s.config.APIs.COAP.Authorization.DeviceIDClaim != "" && jwtDeviceID == "" {
return "", fmt.Errorf("access token doesn't contain the required device id claim('%v')", s.config.APIs.COAP.Authorization.DeviceIDClaim)
}
if !s.config.APIs.COAP.TLS.IsEnabled() || !s.config.APIs.COAP.TLS.Embedded.ClientCertificateRequired {
return jwtDeviceID, nil
}
if tlsDeviceID == "" {
return "", errors.New("certificate of device doesn't contain device id")
}
if s.config.APIs.COAP.Authorization.DeviceIDClaim != "" && jwtDeviceID != tlsDeviceID {
return "", fmt.Errorf("access token issued to the device ('%v') used by the different device ('%v')", jwtDeviceID, tlsDeviceID)
}
return tlsDeviceID, nil
}
func (s *Service) VerifyAndResolveDeviceID(tlsDeviceID, paramDeviceID string, claim pkgJwt.Claims) (string, error) {
deviceID, err := s.verifyDeviceID(tlsDeviceID, claim)
if err != nil {
return "", err
}
if deviceID == "" {
return paramDeviceID, nil
}
return deviceID, nil
}
func verifyChain(chain []*x509.Certificate, capool *x509.CertPool, identityPropertiesRequired bool) error {
if len(chain) == 0 {
return errors.New("certificate chain is empty")
}
certificate := chain[0]
intermediateCAPool := x509.NewCertPool()
for i := 1; i < len(chain); i++ {
intermediateCAPool.AddCert(chain[i])
}
_, err := certificate.Verify(x509.VerifyOptions{
Roots: capool,
Intermediates: intermediateCAPool,
CurrentTime: time.Now(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
return err
}
// verify EKU manually
ekuHasClient := false
ekuHasServer := false
for _, eku := range certificate.ExtKeyUsage {
if eku == x509.ExtKeyUsageClientAuth {
ekuHasClient = true
}
if eku == x509.ExtKeyUsageServerAuth {
ekuHasServer = true
}
}
if !ekuHasClient {
return errors.New("the extended key usage field in the device certificate does not contain client authentication")
}
if !ekuHasServer {
return errors.New("the extended key usage field in the device certificate does not contain server authentication")
}
if !identityPropertiesRequired {
return nil
}
_, err = coap.GetDeviceIDFromIdentityCertificate(certificate)
if err != nil {
return fmt.Errorf("the device ID is not part of the certificate's common name: %w", err)
}
return nil
}
func MakeGetConfigForClient(tlsCfg *tls.Config, identityPropertiesRequired bool) tls.Config {
return tls.Config{
GetCertificate: tlsCfg.GetCertificate,
MinVersion: tlsCfg.MinVersion,
ClientAuth: tlsCfg.ClientAuth,
ClientCAs: tlsCfg.ClientCAs,
VerifyPeerCertificate: func(_ [][]byte, chains [][]*x509.Certificate) error {
var errs *multierror.Error
for _, chain := range chains {
err := verifyChain(chain, tlsCfg.ClientCAs, identityPropertiesRequired)
if err == nil {
return nil
}
errs = multierror.Append(errs, err)
}
err := errors.New("empty chains")
if errs.ErrorOrNil() != nil {
err = errs
}
return pkgX509.NewError(chains, err)
},
}
}