Skip to content

Commit c92c131

Browse files
authored
tls_client_auth (#7)
1 parent 59d9344 commit c92c131

File tree

11 files changed

+157
-42
lines changed

11 files changed

+157
-42
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
148148
--grant-type urn:ietf:params:oauth:grant-type:jwt-bearer \
149149
--auth-method client_secret_basic \
150150
--scopes email \
151-
--signing-key https://pastebin.com/raw/WMkzhjhm \
151+
--signing-key https://raw.githubusercontent.com/cloudentity/oauth2c/master/data/key.json \
152152
--assertion '{"sub":"[email protected]"}'
153153
```
154154

@@ -192,8 +192,20 @@ oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
192192
``` sh
193193
oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
194194
--client-id 582af0afb0d74554aa7af47849edb222 \
195-
--signing-key https://pastebin.com/raw/WMkzhjhm \
195+
--signing-key https://raw.githubusercontent.com/cloudentity/oauth2c/master/data/key.json \
196196
--grant-type client_credentials \
197197
--auth-method private_key_jwt \
198198
--scopes introspect_tokens,revoke_tokens
199199
```
200+
201+
### TLS Client Auth
202+
203+
``` sh
204+
oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
205+
--client-id 3f07a8c2adea4c1ab353f3ca8e16b8fd \
206+
--tls-cert https://raw.githubusercontent.com/cloudentity/oauth2c/master/data/cert.pem \
207+
--tls-key https://raw.githubusercontent.com/cloudentity/oauth2c/master/data/key.pem \
208+
--grant-type client_credentials \
209+
--auth-method tls_client_auth \
210+
--scopes introspect_tokens,revoke_tokens
211+
```

cmd/log.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/cloudentity/oauth2c/internal/oauth2"
1313
"github.com/golang-jwt/jwt"
14+
"github.com/grantae/certinfo"
1415
"github.com/pterm/pterm"
1516
"github.com/tidwall/pretty"
1617
)
@@ -60,6 +61,13 @@ func LogRequest(r oauth2.Request) {
6061
for k, vs := range r.Form {
6162
pterm.Println(pterm.FgLightBlue.Sprintf(" %s: ", k) + strings.Join(vs, ", "))
6263
}
64+
65+
if r.Cert != nil {
66+
if info, err := certinfo.CertificateText(r.Cert); err == nil {
67+
pterm.Println()
68+
pterm.FgGray.Println(info)
69+
}
70+
}
6371
}
6472

6573
func LogRequestln(request oauth2.Request) {

cmd/oauth2.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"strconv"
1212
"strings"
13+
"time"
1314

1415
"github.com/cloudentity/oauth2c/internal/oauth2"
1516
"github.com/golang-jwt/jwt"
@@ -40,6 +41,9 @@ func init() {
4041
OAuth2Cmd.PersistentFlags().BoolVar(&cconfig.NoPKCE, "no-pkce", false, "disable proof key for code exchange (PKCE)")
4142
OAuth2Cmd.PersistentFlags().StringVar(&cconfig.Assertion, "assertion", "", "claims for jwt bearer assertion (standard claims such as iss, aud, iat, exp, jti are automatically generated)")
4243
OAuth2Cmd.PersistentFlags().StringVar(&cconfig.SigningKey, "signing-key", "", "path or url to signing key in jwks format")
44+
OAuth2Cmd.PersistentFlags().StringVar(&cconfig.TLSCert, "tls-cert", "", "path to tls cert pem file")
45+
OAuth2Cmd.PersistentFlags().StringVar(&cconfig.TLSKey, "tls-key", "", "path to tls key pem file")
46+
OAuth2Cmd.PersistentFlags().StringVar(&cconfig.TLSRootCA, "tls-root-ca", "", "path to tls root ca pem file")
4347
OAuth2Cmd.PersistentFlags().BoolVar(&cconfig.Insecure, "insecure", false, "allow insecure connections")
4448
}
4549

@@ -51,6 +55,7 @@ var OAuth2Cmd = &cobra.Command{
5155
var (
5256
config Config
5357
data []byte
58+
cert tls.Certificate
5459
err error
5560
)
5661

@@ -68,14 +73,31 @@ var OAuth2Cmd = &cobra.Command{
6873
cconfig.IssuerURL = strings.TrimSuffix(args[0], oauth2.OpenIDConfigurationPath)
6974
}
7075

71-
hc := &http.Client{
72-
Transport: &http.Transport{
73-
TLSClientConfig: &tls.Config{
74-
InsecureSkipVerify: cconfig.Insecure,
75-
},
76+
tr := &http.Transport{
77+
TLSClientConfig: &tls.Config{
78+
InsecureSkipVerify: cconfig.Insecure,
79+
MinVersion: tls.VersionTLS12,
7680
},
7781
}
7882

83+
hc := &http.Client{Timeout: 10 * time.Second, Transport: tr}
84+
85+
if cconfig.TLSCert != "" && cconfig.TLSKey != "" {
86+
if cert, err = oauth2.ReadKeyPair(cconfig.TLSCert, cconfig.TLSKey, hc); err != nil {
87+
pterm.Error.PrintOnError(err)
88+
os.Exit(1)
89+
}
90+
91+
tr.TLSClientConfig.Certificates = []tls.Certificate{cert}
92+
}
93+
94+
if cconfig.TLSRootCA != "" {
95+
if tr.TLSClientConfig.RootCAs, err = oauth2.ReadRootCA(cconfig.TLSRootCA, hc); err != nil {
96+
pterm.Error.PrintOnError(err)
97+
os.Exit(1)
98+
}
99+
}
100+
79101
if err := Authorize(cconfig, hc); err != nil {
80102
var oauth2Error *oauth2.Error
81103

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.19
55
require (
66
github.com/go-jose/go-jose/v3 v3.0.0
77
github.com/golang-jwt/jwt v3.2.2+incompatible
8+
github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b
89
github.com/imdario/mergo v0.3.13
910
github.com/lithammer/shortuuid/v4 v4.0.0
1011
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
2828
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
2929
github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw=
3030
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
31+
github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b h1:NGgE5ELokSf2tZ/bydyDUKrvd/jP8lrAoPNeBuMOTOk=
32+
github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b/go.mod h1:zT/uzhdQGTqlwTq7Lpbj3JoJQWfPfIJ1tE0OidAmih8=
3133
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
3234
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
3335
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=

internal/oauth2/oauth2.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package oauth2
33
import (
44
"context"
55
"crypto/sha256"
6+
"crypto/x509"
67
"encoding/base64"
78
"encoding/json"
89
"fmt"
@@ -38,9 +39,8 @@ const (
3839
ClientSecretPostAuthMethod string = "client_secret_post"
3940
ClientSecretJwtAuthMethod string = "client_secret_jwt"
4041
PrivateKeyJwtAuthMethod string = "private_key_jwt"
41-
// SelfSignedTLSAuthMethod string = "self_signed_tls_client_auth"
42-
// TLSClientAuthMethod string = "tls_client_auth"
43-
// NoneAuthMethod string = "none"
42+
SelfSignedTLSAuthMethod string = "self_signed_tls_client_auth"
43+
TLSClientAuthMethod string = "tls_client_auth"
4444
)
4545

4646
// client assertion types
@@ -69,6 +69,9 @@ type ClientConfig struct {
6969
RefreshToken string
7070
Assertion string
7171
SigningKey string
72+
TLSCert string
73+
TLSKey string
74+
TLSRootCA string
7275
}
7376

7477
func RequestAuthorization(addr string, cconfig ClientConfig, sconfig ServerConfig) (r Request, codeVerifier string, err error) {
@@ -233,10 +236,11 @@ func RequestToken(
233236
opts ...RequestTokenOption,
234237
) (request Request, response TokenResponse, err error) {
235238
var (
236-
req *http.Request
237-
resp *http.Response
238-
params RequestTokenParams
239-
body []byte
239+
req *http.Request
240+
resp *http.Response
241+
params RequestTokenParams
242+
endpoint = sconfig.TokenEndpoint
243+
body []byte
240244
)
241245

242246
for _, opt := range opts {
@@ -299,6 +303,15 @@ func RequestToken(
299303

300304
request.Form.Set("client_assertion_type", JwtBearerClientAssertion)
301305
request.Form.Set("client_assertion", clientAssertion)
306+
case TLSClientAuthMethod, SelfSignedTLSAuthMethod:
307+
endpoint = sconfig.MTLsEndpointAliases.TokenEndpoint
308+
request.Form.Set("client_id", cconfig.ClientID)
309+
310+
if tr, ok := hc.Transport.(*http.Transport); ok {
311+
if len(tr.TLSClientConfig.Certificates) > 0 {
312+
request.Cert, _ = x509.ParseCertificate(tr.TLSClientConfig.Certificates[0].Certificate[0])
313+
}
314+
}
302315
}
303316

304317
if params.RedirectURL != "" {
@@ -316,7 +329,7 @@ func RequestToken(
316329
if req, err = http.NewRequestWithContext(
317330
ctx,
318331
http.MethodPost,
319-
sconfig.TokenEndpoint,
332+
endpoint,
320333
strings.NewReader(request.Form.Encode()),
321334
); err != nil {
322335
return request, response, err

internal/oauth2/signing_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import (
1212
)
1313

1414
func TestReadKey(t *testing.T) {
15-
key, err := oauth2.ReadKey("./testdata/jwks-private.json", http.DefaultClient)
15+
key, err := oauth2.ReadKey("../../data/key.json", http.DefaultClient)
1616
require.NoError(t, err)
1717

1818
require.NotNil(t, key)
1919
}
2020

2121
func TestSignJWT(t *testing.T) {
22-
key, err := oauth2.ReadKey("./testdata/jwks-private.json", http.DefaultClient)
22+
key, err := oauth2.ReadKey("../../data/key.json", http.DefaultClient)
2323
require.NoError(t, err)
2424

2525
claims := oauth2.AssertionClaims(
@@ -33,7 +33,7 @@ func TestSignJWT(t *testing.T) {
3333
)
3434

3535
jwt, _, err := oauth2.SignJWT(claims, oauth2.JWKSigner(oauth2.ClientConfig{
36-
SigningKey: "./testdata/jwks-private.json",
36+
SigningKey: "../../data/key.json",
3737
}, http.DefaultClient))
3838
require.NoError(t, err)
3939

internal/oauth2/testdata/jwks-private.json

Lines changed: 0 additions & 23 deletions
This file was deleted.

internal/oauth2/tls.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package oauth2
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"strings"
11+
)
12+
13+
func ReadURL(location string, hc *http.Client) (data []byte, err error) {
14+
var resp *http.Response
15+
16+
if resp, err = hc.Get(location); err != nil {
17+
return nil, err
18+
}
19+
defer resp.Body.Close()
20+
21+
if data, err = io.ReadAll(resp.Body); err != nil {
22+
return nil, err
23+
}
24+
25+
if resp.StatusCode != http.StatusOK {
26+
return nil, fmt.Errorf("failed to read data from url %d: %s", resp.StatusCode, string(data))
27+
}
28+
29+
return data, nil
30+
}
31+
32+
func ReadKeyPair(cert string, key string, hc *http.Client) (keyPair tls.Certificate, err error) {
33+
if strings.HasPrefix(cert, "http") && strings.HasPrefix(key, "http") {
34+
var (
35+
certPEM []byte
36+
keyPEM []byte
37+
)
38+
39+
if certPEM, err = ReadURL(cert, hc); err != nil {
40+
return tls.Certificate{}, err
41+
}
42+
43+
if keyPEM, err = ReadURL(key, hc); err != nil {
44+
return tls.Certificate{}, err
45+
}
46+
47+
return tls.X509KeyPair(certPEM, keyPEM)
48+
}
49+
50+
return tls.LoadX509KeyPair(cert, key)
51+
}
52+
53+
func ReadRootCA(location string, hc *http.Client) (pool *x509.CertPool, err error) {
54+
var rootCA []byte
55+
56+
if pool, err = x509.SystemCertPool(); err != nil {
57+
return nil, err
58+
}
59+
60+
if strings.HasPrefix(location, "http") {
61+
if rootCA, err = ReadURL(location, hc); err != nil {
62+
return nil, err
63+
}
64+
} else {
65+
if rootCA, err = os.ReadFile(location); err != nil {
66+
return nil, err
67+
}
68+
}
69+
70+
pool.AppendCertsFromPEM(rootCA)
71+
72+
return pool, nil
73+
}

internal/oauth2/tracing.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package oauth2
22

3-
import "net/url"
3+
import (
4+
"crypto/x509"
5+
"net/url"
6+
)
47

58
type Request struct {
69
Method string
710
URL *url.URL
811
Headers map[string][]string
912
Form url.Values
1013
Key interface{}
14+
Cert *x509.Certificate
1115
}
1216

1317
func (r *Request) Get(key string) string {

0 commit comments

Comments
 (0)