-
-
Notifications
You must be signed in to change notification settings - Fork 17
/
auth.go
141 lines (133 loc) · 4.29 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
package service
import (
"context"
"crypto/tls"
"crypto/x509"
"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, code 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, fmt.Errorf("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) error {
jwtDeviceID := claim.DeviceID(s.config.APIs.COAP.Authorization.DeviceIDClaim)
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() {
return nil
}
if !s.config.APIs.COAP.TLS.Embedded.ClientCertificateRequired {
return nil
}
if tlsDeviceID == "" {
return fmt.Errorf("certificate of device doesn't contains device id")
}
if s.config.APIs.COAP.Authorization.DeviceIDClaim == "" {
return nil
}
if jwtDeviceID != tlsDeviceID {
return fmt.Errorf("access token issued to the device ('%v') used by the different device ('%v')", jwtDeviceID, tlsDeviceID)
}
return nil
}
func verifyChain(chain []*x509.Certificate, capool *x509.CertPool, identityPropertiesRequired bool) error {
if len(chain) == 0 {
return fmt.Errorf("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 fmt.Errorf("the extended key usage field in the device certificate does not contain client authentication")
}
if !ekuHasServer {
return fmt.Errorf("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(rawCerts [][]byte, chains [][]*x509.Certificate) error {
var errors *multierror.Error
for _, chain := range chains {
err := verifyChain(chain, tlsCfg.ClientCAs, identityPropertiesRequired)
if err == nil {
return nil
}
errors = multierror.Append(errors, err)
}
err := fmt.Errorf("empty chains")
if errors.ErrorOrNil() != nil {
err = errors
}
return pkgX509.NewError(chains, err)
},
}
}