summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go118
1 files changed, 38 insertions, 80 deletions
diff --git a/main.go b/main.go
index 44501c0..965e72c 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
11 "net/http/httputil" 11 "net/http/httputil"
12 "net/url" 12 "net/url"
13 "strings" 13 "strings"
14 "time"
14) 15)
15 16
16const ( 17const (
@@ -19,6 +20,8 @@ const (
19 RFP_COOKIE_NAME string = "sso_rfp" 20 RFP_COOKIE_NAME string = "sso_rfp"
20) 21)
21 22
23// TODO: Enable https checks in HTTP client
24
22// acr_values can be mfa or selective_mfa (mfa only for external users) 25// acr_values can be mfa or selective_mfa (mfa only for external users)
23// mfa amr values: 26// mfa amr values:
24// pas - password 27// pas - password
@@ -34,6 +37,9 @@ type ProxyConfig struct {
34 UpstreamURL string 37 UpstreamURL string
35 ListenOn string 38 ListenOn string
36 TrustedCACert string 39 TrustedCACert string
40 PKISubject string // TODO: Should be same as IDP w/out scheme and port
41 ClockSkew time.Duration
42 MaxLiftetime time.Duration
37 IsOptional bool 43 IsOptional bool
38 RequestMFA bool 44 RequestMFA bool
39 AllowedMFAMethods []string // An OR set 45 AllowedMFAMethods []string // An OR set
@@ -45,7 +51,7 @@ type IdPConfig struct {
45 AuthorizationEndpoint string `json:"authorization_endpoint"` 51 AuthorizationEndpoint string `json:"authorization_endpoint"`
46 Issuer string `json:"issuer"` 52 Issuer string `json:"issuer"`
47 JwksUri string `json:"jwks_uri"` 53 JwksUri string `json:"jwks_uri"`
48 SupportedGrantTypes []string `json:"grant_types_supported"` 54 GrantTypes []string `json:"grant_types_supported"`
49 IdTokenSigningAlgs []string `json:"id_token_signing_alg_values_supported"` 55 IdTokenSigningAlgs []string `json:"id_token_signing_alg_values_supported"`
50 ResponseModes []string `json:"response_modes_supported"` 56 ResponseModes []string `json:"response_modes_supported"`
51 ResponseTypes []string `json:"response_types_supported"` 57 ResponseTypes []string `json:"response_types_supported"`
@@ -53,6 +59,7 @@ type IdPConfig struct {
53 SubjectTypes []string `json:"subject_types_supported"` 59 SubjectTypes []string `json:"subject_types_supported"`
54} 60}
55 61
62// TODO: Optimization to fetch only if expired (per http headers)
56func FetchIdPConfig(h CautiousHTTPClient, idp_url string) (*IdPConfig, error) { 63func FetchIdPConfig(h CautiousHTTPClient, idp_url string) (*IdPConfig, error) {
57 u, err := url.Parse(idp_url) 64 u, err := url.Parse(idp_url)
58 if err != nil { 65 if err != nil {
@@ -70,7 +77,7 @@ func FetchIdPConfig(h CautiousHTTPClient, idp_url string) (*IdPConfig, error) {
70} 77}
71 78
72// TODO: Optimization to fetch only if expired (per http headers) 79// TODO: Optimization to fetch only if expired (per http headers)
73func FetchJWKS(h CautiousHTTPClient, jwks_url string) (map[string]jose.JSONWebKey, error) { 80func FetchJWKS(h CautiousHTTPClient, jwks_url string, val KeyValidator) (map[string]jose.JSONWebKey, error) {
74 var jwks jose.JSONWebKeySet 81 var jwks jose.JSONWebKeySet
75 err := h.GetJSON(jwks_url, &jwks) 82 err := h.GetJSON(jwks_url, &jwks)
76 if err != nil { 83 if err != nil {
@@ -80,20 +87,15 @@ func FetchJWKS(h CautiousHTTPClient, jwks_url string) (map[string]jose.JSONWebKe
80 keys := make(map[string]jose.JSONWebKey, len(jwks.Keys)) 87 keys := make(map[string]jose.JSONWebKey, len(jwks.Keys))
81 88
82 for _, k := range jwks.Keys { 89 for _, k := range jwks.Keys {
83 keys[k.KeyID] = k 90 err = val.Validate(k)
91 if err == nil {
92 keys[k.KeyID] = k
93 }
84 } 94 }
85 95
86 return keys, nil 96 return keys, nil
87} 97}
88 98
89func URLMustParse(u string) *url.URL {
90 o, err := url.Parse(u)
91 if err != nil {
92 panic(err)
93 }
94 return o
95}
96
97func GenerateNonce() (string, error) { 99func GenerateNonce() (string, error) {
98 nonce := make([]byte, NONCE_SIZE) 100 nonce := make([]byte, NONCE_SIZE)
99 n, err := rand.Read(nonce) 101 n, err := rand.Read(nonce)
@@ -103,10 +105,6 @@ func GenerateNonce() (string, error) {
103 return hex.EncodeToString(nonce), nil 105 return hex.EncodeToString(nonce), nil
104} 106}
105 107
106func CompareUpper(lhs, rhs string) bool {
107 return strings.ToUpper(lhs) == strings.ToUpper(rhs)
108}
109
110// TODO 108// TODO
111// Cookie rules 109// Cookie rules
112// Secure 110// Secure
@@ -117,28 +115,6 @@ func SetCookie() {
117} 115}
118 116
119// TODO 117// TODO
120// Fetch (connect timeout 1s, read timeout 30s, read size 1M)
121func DownloadCertificate() {
122}
123
124// TODO
125// Fetch (connect timeout 1s, read timeout 30s, read size 1M)
126func DownloadCRL() {
127}
128
129// TODO
130// Cert validation
131// Validate cert not in CRL
132// Validate cert chains to trusted CA cert (ship with proxy)
133// Validate CRL signed by trusted CA
134// Cert "Subject CN " must match exactly PKI setting (ex: foo-pki.foo.com)
135// Current time bust be within "Validity Not Before" and "Validity Not After" in cert +- 5 minutes
136// Cert Key length >= 2048
137// Certificate usage must include "digitalSignature"
138func ValidateCertificate() {
139}
140
141// TODO
142func MakeClientID(r *http.Request) string { 118func MakeClientID(r *http.Request) string {
143 if strings.Contains(r.Host, ":") { 119 if strings.Contains(r.Host, ":") {
144 return r.Host 120 return r.Host
@@ -167,42 +143,6 @@ func SetTokenCookieAndRedirect(w http.ResponseWriter, r *http.Request, token str
167} 143}
168 144
169// TODO 145// TODO
170// Occasionally refresh IDP config (per HTTP caching headers)
171//
172// Fetch ${IDP_HOST}/.well-known/openid-configuration
173// - validate certificate chains to a trusted root
174// - validate scopes_supported contains "openid"
175// - validate response_types_supported contains "id_token"
176// - validate grant_types_supported contains "implicit"
177// - validate id_token_signing_alg_values_supported contains a supported signing type (see below)
178// - Cache authorization_endpoint for redirecting users
179//
180// Fetch jwks_uri endpoint
181// - Build key map indexed by kid for all keys that are suppored by our rules
182// - kty == RSA
183// - alg header must be one of [PS256, PS385, PS512]
184// - pem decode x5c and validate the certificate chain as below
185// - validate first item of x5c matches n and e
186func RefreshIDPConfig() {
187}
188
189// TODO
190// If x5u exists in header
191// Fetch cert from x5u URL
192// Get CRL from cert, fetch (connect timeout 1s, read timeout 30s, read size 1M)
193//
194// exp claim has passed +- 5 minutes
195// iat claim is greater than 24 hours +- 5 minutes
196// aud claim is exact match for client_id
197// iss claim is exact match for idp (ex: foo.example.com)
198// if other aud claims validate that they are known
199// nonce in JWT must be SHA256 of rfp cookie value
200// Validate cert
201// alg jwt header must be one of [PS256, PS385, PS512]
202// typ jwt header must be JWS
203// validate jwt signature
204// validate amr claim contains requested acr values (selective_mfa will be just mfa)
205// validate acr claim is the same as requested acr_values
206func ValidateJWT(jwt, rfp string) bool { 146func ValidateJWT(jwt, rfp string) bool {
207 return true 147 return true
208} 148}
@@ -318,33 +258,51 @@ func LoginController(w http.ResponseWriter, r *http.Request) {
318// user will be redirected back to the main page for the site (/) 258// user will be redirected back to the main page for the site (/)
319func parseConfig() *ProxyConfig { 259func parseConfig() *ProxyConfig {
320 return &ProxyConfig{ 260 return &ProxyConfig{
321 IDProviderURL: "", 261 IDProviderURL: "http://mcrute-virt:9993",
322 ClientID: "", 262 ClientID: "test.crute.me:443",
323 UpstreamURL: "http://localhost:9991/", 263 UpstreamURL: "http://localhost:9991/",
324 ListenOn: ":9992", 264 ListenOn: ":9992",
325 TrustedCACert: "", 265 TrustedCACert: "/home/mcrute/oidc_project/test_ca/ca_cert.pem",
326 IsOptional: false, 266 IsOptional: false,
267 PKISubject: "Crute OpenID Signing 1",
268 MaxLiftetime: 24 * time.Hour,
269 ClockSkew: 5 * time.Minute,
327 } 270 }
328} 271}
329 272
330func main() { 273func main() {
274 cfg := parseConfig()
331 h := NewCautiousHTTPClient() 275 h := NewCautiousHTTPClient()
332 276
333 idpc, err := FetchIdPConfig(h, "http://mcrute-virt:9993") 277 v := NewKeyValidator(cfg.PKISubject)
278 v.LoadRootPEM(cfg.TrustedCACert)
279
280 idpc, err := FetchIdPConfig(h, cfg.IDProviderURL)
334 if err != nil { 281 if err != nil {
335 fmt.Printf("%s\n", err) 282 fmt.Printf("%s\n", err)
336 return 283 return
337 } 284 }
338 285
339 jwks, err := FetchJWKS(h, idpc.JwksUri) 286 jwks, err := FetchJWKS(h, idpc.JwksUri, v)
340 if err != nil { 287 if err != nil {
341 fmt.Printf("%s\n", err) 288 fmt.Printf("%s\n", err)
342 return 289 return
343 } 290 }
344 fmt.Printf("%+v\n", jwks) 291
292 jv := NewJWSValidator(jwks, idpc.Issuer, cfg.ClientID, cfg.ClockSkew, cfg.MaxLiftetime)
293
294 nonce := "ofspmfjuvoswhhde"
295 raw_jwt := "eyJ0eXAiOiJKV1MiLCJhbGciOiJQUzI1NiIsImtpZCI6IjEifQ.eyJub25jZSI6IjM0MjlhMjAyYzU4ZDkyYjQwNjNjOWM4MWM2MjQyNGRlNzBkMmIzZDQ4MmVlNDFhOTdjYmNhZjEwZDk5MWFiOTMiLCJpc3MiOiJpZHAuY3J1dGUubWU6NDQzIiwiaWF0IjoxNTA0NTc2Mzc0LCJuYmYiOjE1MDQ1NzYzNzQsImV4cCI6MTUwNDY2Mjc3NCwic3ViIjoibWNydXRlIiwiYXVkIjoidGVzdC5jcnV0ZS5tZTo0NDMifQ.iizlNfY1Vg7d-XRmgyYuhpNkNrOGaT9OOgO0HdjBozOWMvKzBTtATbIfoWOrNH6DiFY1as8uy3I1Pxnkrb8Ti8_cLDQeLxOv9klAbnebeuPI_wtZ0iwSUnSWaYzN6I6sqcEjHX3fibFvAQhO5dNDzSwONjw4AvcdpZKh579FO1sAvIw-1DmMyPSUun7rbC0Kf1Jtdlr3q7tOp3wdI_erkstxCNPwyuv7X1J7uetsu0BeJS25C2DxeB03BPEIUoo_C1xvcqikfSLLpoFcyToYiS-R9o-WpRjGid_yug65J5ALn2aM3vhe9rRbydKVm_omGL8-Etj06zbqM0Y6OrJUgA"
296 claims, err := jv.Validate(raw_jwt, nonce)
297 if err != nil {
298 fmt.Printf("Error validating: %s\n", err)
299 return
300 }
301
302 fmt.Printf("Valid JWT for: %+v\n", claims.Subject)
303
345 return 304 return
346 305
347 cfg := parseConfig()
348 cfg.reverseProxy = httputil.NewSingleHostReverseProxy(URLMustParse(cfg.UpstreamURL)) 306 cfg.reverseProxy = httputil.NewSingleHostReverseProxy(URLMustParse(cfg.UpstreamURL))
349 307
350 if cfg.IsOptional { 308 if cfg.IsOptional {