Skip to content

Commit c9d46d4

Browse files
james/dt4a auth (#2149)
1 parent 76a133b commit c9d46d4

File tree

7 files changed

+899
-35
lines changed

7 files changed

+899
-35
lines changed

ee/localserver/certs.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package localserver
22

3+
import (
4+
"crypto/ecdsa"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
39
// These are the hardcoded certificates
10+
411
const (
512
k2EccServerCert = `-----BEGIN PUBLIC KEY-----
613
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmAO4tYINU14/i0LvONs1IXVwaFnF
@@ -16,4 +23,146 @@ GINysvEwYrNoGjASt+nqzlFesagt+2A/4W7JR16nE91mbCHn+HV6x+H8gw==
1623
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwowFsPUaOC61LAfDz1hLnsuSDfEx
1724
SC4TSfHtbHHv3lx2/Bfu+H0szXYZ75GF/qZ5edobq3UkABN6OaFnnJId3w==
1825
-----END PUBLIC KEY-----`
26+
27+
dt4aCertsJWK = `
28+
{
29+
"LJDJWSE7WZEVTIWYZXFEGO4UWU": {
30+
"crv": "P-256",
31+
"kid": "LJDJWSE7WZEVTIWYZXFEGO4UWU",
32+
"kty": "EC",
33+
"x": "RqXvX6ByCw5XzYtqvt_xMSJwBaA9aoPH-Mc3yQ2HGhE",
34+
"y": "RGellUkwDt2v0HUdCqKs8WLBa4bWQ4ZaKPfAesGdjoA"
35+
},
36+
"O7OHTURDFFHHNNV6NIKCPCZG3U": {
37+
"crv": "P-256",
38+
"kid": "O7OHTURDFFHHNNV6NIKCPCZG3U",
39+
"kty": "EC",
40+
"x": "e4MUHZOCk7Jas0wKLZZj0BTUnfzqilXV7EykZelq8kw",
41+
"y": "Pep2Saf8RTYH0KaIH4Kh3AcdsgATh8pr2jUBviULwWo"
42+
},
43+
"EQFDLBCEORHJRLZMSUAKAUWEIQ": {
44+
"crv": "P-256",
45+
"kid": "EQFDLBCEORHJRLZMSUAKAUWEIQ",
46+
"kty": "EC",
47+
"x": "J3XQzYvsix7WQV1g-N7IgFA_J-Fja2_R7QcqMr1WU3g",
48+
"y": "b9YrgvnWeJxjZ5HX2ERau5PcJDKDnrSDcyLJpuNiuMc"
49+
},
50+
"PCA7GIDALBFYZCWK4MS3G4IXEU": {
51+
"crv": "P-256",
52+
"kid": "PCA7GIDALBFYZCWK4MS3G4IXEU",
53+
"kty": "EC",
54+
"x": "HMLGLwgkb9NY_x3br_nIhQxkj-XzUcEue6NsPWyAe0g",
55+
"y": "Agd56jMwBjKEHSmXDlII5fSrYGJcEZQXj_W8xhcp5Bg"
56+
},
57+
"XTZV3GJH5NDA3GN5TYYHQS2LY4": {
58+
"crv": "P-256",
59+
"kid": "XTZV3GJH5NDA3GN5TYYHQS2LY4",
60+
"kty": "EC",
61+
"x": "fJoVe2dIOdHfbqUF_e0Jx1DdWwx1qawdN7KV_gS1lgc",
62+
"y": "VSrX5h6qrHm0KhfJeX_sR73AsSGDuYMq6wd36eK9zr0"
63+
},
64+
"SZ5KQFFBRBAJ7BWXMQK6SNT7VY": {
65+
"crv": "P-256",
66+
"kid": "SZ5KQFFBRBAJ7BWXMQK6SNT7VY",
67+
"kty": "EC",
68+
"x": "yh2yfAiySH1by8-Zys0fDoxWxLvKzsBidjVv7H-JOQs",
69+
"y": "yvwU9oucAqdbR-Cnu6AM3Wo8eH1rA14qs9GKZGrcBDM"
70+
},
71+
"CGCIR2MFPJG53ANG3UZQHOARUE": {
72+
"crv": "P-256",
73+
"kid": "CGCIR2MFPJG53ANG3UZQHOARUE",
74+
"kty": "EC",
75+
"x": "kqCHC5TWP0AfB-Is4YqkuG01yHXr90MRIHsWl5cA1bo",
76+
"y": "O7Dr6cV5c48f3s-7b7nC9emlhrpZaQKGbW0p_fgO12o"
77+
},
78+
"EVNNXZX7ARDXTHVDPIIH7UBLIM": {
79+
"crv": "P-256",
80+
"kid": "EVNNXZX7ARDXTHVDPIIH7UBLIM",
81+
"kty": "EC",
82+
"x": "UYlZvyQwqDdth2C0EdgB7aQz7oXxfft2GwxfsNoeq1Q",
83+
"y": "9HRC4q-57_VjEl-BxLj8xCdSq8-GRCq9APc9b48RiEg"
84+
},
85+
"UQB2V5K4SVHKXJYDK52ESEOITU": {
86+
"crv": "P-256",
87+
"kid": "UQB2V5K4SVHKXJYDK52ESEOITU",
88+
"kty": "EC",
89+
"x": "iGmuezNq8h1xFrctHA4IZ8w4uNs8FnEGM2H58-u9rLg",
90+
"y": "fdnegLFLCEEFLxlQIirUnLLBC3W3hiWdSdcoyfezWmc"
91+
},
92+
"CDTTGMTBNZDKNLJJRVGZG5EJWI": {
93+
"crv": "P-256",
94+
"kid": "CDTTGMTBNZDKNLJJRVGZG5EJWI",
95+
"kty": "EC",
96+
"x": "dpfhbnwobCJ1mn67aa-MtpK_HWGgACP2QIRi5sIQHLw",
97+
"y": "AI6PPzaV80SlaT2YJpO0jR-s_p6V4l3mE21_HmN8btU"
98+
},
99+
"QKTPGJABLNC67JF7LNBYA67H6U": {
100+
"crv": "P-256",
101+
"kid": "QKTPGJABLNC67JF7LNBYA67H6U",
102+
"kty": "EC",
103+
"x": "K_iuK_QTiyUGKnNwQawfNNZaK2r_74LPJ-Dh8E3q2Eg",
104+
"y": "cQtwUMjR1v6yCUkuJUQ8giG8i0094EjBgI4ZvBSlu7I"
105+
},
106+
"EIXVCJMWU5EOHO7KFZRTXJFDPM": {
107+
"crv": "P-256",
108+
"kid": "EIXVCJMWU5EOHO7KFZRTXJFDPM",
109+
"kty": "EC",
110+
"x": "m41RPfNro2eTO4QvrlcS4YvFGyfSipKYSWvxFCJmd9s",
111+
"y": "-LcFdVaFWLKpQwxIJtg_Wbt96nSW6UflcHFccukyfYY"
112+
},
113+
"4XAYQZYLERCODIA466B6N5CVTU": {
114+
"crv": "P-256",
115+
"kid": "4XAYQZYLERCODIA466B6N5CVTU",
116+
"kty": "EC",
117+
"x": "ToQ1gpeYXel8NOj_nPaxoQWnTWPYFdSLFu9WyRPSeCI",
118+
"y": "6I1ce5IWh-sShpy6zWuEPe1xfxXfZKGPKLkjmgFlF8g"
119+
},
120+
"QZ4LYXGF2FBPVPOUXOB6JRG7FU": {
121+
"crv": "P-256",
122+
"kid": "QZ4LYXGF2FBPVPOUXOB6JRG7FU",
123+
"kty": "EC",
124+
"x": "3UcmjOw5gySrmdRhPi79uPrBc4wMK82lk_2ZjHhd8AM",
125+
"y": "9KyEHTauwzUZMhO9bbfdLXgBurzik_U6hOAvAdEmMOY"
126+
},
127+
"XEEA7W43BVHCNG3GXWOKXOCQUI": {
128+
"crv": "P-256",
129+
"kid": "XEEA7W43BVHCNG3GXWOKXOCQUI",
130+
"kty": "EC",
131+
"x": "9NunJuqdPEvmXid4XykAvMT7oVD2VoJAesvt-OGyVqI",
132+
"y": "c3A92ku97Gs_BU0ADNl0lGJ-h0TFvShYZgNQMMBf0T0"
133+
},
134+
"PUIDTW7VXFHN5CV6DBY4KVTIFE": {
135+
"crv": "P-256",
136+
"kid": "PUIDTW7VXFHN5CV6DBY4KVTIFE",
137+
"kty": "EC",
138+
"x": "8Y6AMk7wwVZfzqqGbFIdRWKQVm9mvjX1M-4lp7LBBGw",
139+
"y": "uYFFX6PSycR-7EHSy1LXllg44tWZ5iFGfHJplIWcmYQ"
140+
}
141+
}`
19142
)
143+
144+
func dt4aKeys() (map[string]*ecdsa.PublicKey, error) {
145+
dt4aKeyMap := make(map[string]*ecdsa.PublicKey)
146+
147+
certMap := make(map[string]json.RawMessage)
148+
if err := json.Unmarshal([]byte(dt4aCertsJWK), &certMap); err != nil {
149+
return nil, err
150+
}
151+
152+
for k, v := range certMap {
153+
154+
var j jwk
155+
if err := json.Unmarshal(v, &j); err != nil {
156+
return nil, fmt.Errorf("failed to unmarshal JWK: %w", err)
157+
}
158+
159+
pubKey, err := j.ecdsaPubKey()
160+
if err != nil {
161+
return nil, fmt.Errorf("failed to convert JWK to ECDSA public key: %w", err)
162+
}
163+
164+
dt4aKeyMap[k] = pubKey
165+
}
166+
167+
return dt4aKeyMap, nil
168+
}

ee/localserver/jwk_keys.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package localserver
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"encoding/base64"
7+
"errors"
8+
"fmt"
9+
"math/big"
10+
"strings"
11+
)
12+
13+
// jwk is a JSON Web Key (JWK) structure for representing public keys,
14+
// this a partial implementation using the stdlib for only the bits we care about,
15+
// RFC https://datatracker.ietf.org/doc/html/rfc7517
16+
type jwk struct {
17+
Curve string `json:"crv"`
18+
X string `json:"x"`
19+
Y string `json:"y"`
20+
KeyID string `json:"kid"`
21+
}
22+
23+
const (
24+
curveP256 string = "P-256"
25+
curveP384 string = "P-384"
26+
curveP521 string = "P-521"
27+
)
28+
29+
func parseEllipticCurve(str string) (elliptic.Curve, error) {
30+
switch strings.ToUpper(str) {
31+
case curveP256:
32+
return elliptic.P256(), nil
33+
case curveP384:
34+
return elliptic.P384(), nil
35+
case curveP521:
36+
return elliptic.P521(), nil
37+
default:
38+
return &elliptic.CurveParams{}, fmt.Errorf("unsupported curve: %s", str)
39+
}
40+
}
41+
42+
// ecdsaPubKey converts jwk in to ecdsa public key
43+
func (j *jwk) ecdsaPubKey() (*ecdsa.PublicKey, error) {
44+
curve, err := parseEllipticCurve(j.Curve)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
// Decode the x and y coordinates using base64 URL decoding (unpadded).
50+
xBytes, err := base64.RawURLEncoding.DecodeString(j.X)
51+
if err != nil {
52+
return nil, fmt.Errorf("error decoding x coordinate: %v", err)
53+
}
54+
yBytes, err := base64.RawURLEncoding.DecodeString(j.Y)
55+
if err != nil {
56+
return nil, fmt.Errorf("error decoding y coordinate: %v", err)
57+
}
58+
59+
// Convert the bytes into big.Int values.
60+
x := new(big.Int).SetBytes(xBytes)
61+
y := new(big.Int).SetBytes(yBytes)
62+
63+
// Construct the ECDSA public key.
64+
pubKey := &ecdsa.PublicKey{
65+
Curve: curve,
66+
X: x,
67+
Y: y,
68+
}
69+
70+
// this is a little weird, but it's the recommended way to validate a public key,
71+
// under the hood it calls Curve.IsOnCurve(...), but if you call that directly
72+
// you get deprecated warnings
73+
if _, err := pubKey.ECDH(); err != nil {
74+
return nil, fmt.Errorf("invalid public key: %w", err)
75+
}
76+
77+
return pubKey, nil
78+
}
79+
80+
// x25519PubKey converts jwk in to x25519 key (*[32]byte)
81+
func (j *jwk) x25519PubKey() (*[32]byte, error) {
82+
// Decode the "x" coordinate using base64 URL decoding (unpadded).
83+
xBytes, err := base64.RawURLEncoding.DecodeString(j.X)
84+
if err != nil {
85+
return nil, fmt.Errorf("error decoding x coordinate: %v", err)
86+
}
87+
88+
// X25519 public keys should be 32 bytes.
89+
if len(xBytes) != 32 {
90+
return nil, errors.New("invalid x coordinate length for X25519, expected 32 bytes")
91+
}
92+
93+
// Copy the bytes into a fixed size array.
94+
var pubKey [32]byte
95+
copy(pubKey[:], xBytes)
96+
97+
return &pubKey, nil
98+
}

ee/localserver/server.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,23 @@ func New(ctx context.Context, k types.Knapsack, presenceDetector presenceDetecto
104104
// /v0/cmd left for transition period
105105
mux.Handle("/v1/cmd", ecKryptoMiddleware.Wrap(ls.munemoCheckHandler(ecAuthedMux)))
106106

107+
trustedDt4aKeys, err := dt4aKeys()
108+
if err != nil {
109+
return nil, fmt.Errorf("loading dt4a keys %w", err)
110+
}
111+
112+
ztaAuthMiddleware := &ztaAuthMiddleware{
113+
counterPartyKeys: trustedDt4aKeys,
114+
slogger: k.Slogger().With("component", "dt4a_auth_middleware"),
115+
}
116+
107117
// In the future, we will want to make this authenticated; for now, it is not authenticated.
118+
// TODO: make this authenticated or remove
108119
mux.Handle("/zta", ls.requestZtaInfoHandler())
109120

121+
// mux.Handle("/zta", ztaAuthMiddleware.Wrap(ls.requestZtaInfoHandler()))
122+
mux.Handle("/v3/dt4a", ztaAuthMiddleware.Wrap(ls.requestZtaInfoHandler()))
123+
110124
// uncomment to test without going through middleware
111125
// for example:
112126
// curl localhost:40978/query --data '{"query":"select * from kolide_launcher_info"}'

ee/localserver/zta.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ func (ls *localServer) requestZtaInfoHandlerFunc(w http.ResponseWriter, r *http.
4141
r, span := traces.StartHttpRequestSpan(r, "path", r.URL.Path)
4242
defer span.End()
4343

44-
if r.Method != http.MethodGet {
45-
w.WriteHeader(http.StatusMethodNotAllowed)
46-
return
47-
}
48-
4944
// Validate origin. We expect to either have the origin set to an allowlisted value, or to be
5045
// present but empty, or to be missing. We will not allow a request with a nonempty origin
5146
// that is not in the allowlist.

0 commit comments

Comments
 (0)