this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

partial progress

+53 -54
+1 -1
atproto/auth/cmd/atp-oauth-demo/main.go
··· 285 285 RefreshToken: tokenResp.RefreshToken, 286 286 DpopAuthServerNonce: info.DpopAuthServerNonce, 287 287 DpopHostNonce: info.DpopAuthServerNonce, // XXX 288 - DpopKeyMultibase: info.DpopKeyMultibase, 288 + DpopPrivateKeyMultibase: info.DpopPrivateKeyMultibase, 289 289 } 290 290 s.AuthStore.SaveSession(authSess) 291 291
+11
atproto/auth/oauth/jwt_signing.go
··· 2 2 3 3 import ( 4 4 "crypto" 5 + "fmt" 5 6 6 7 atcrypto "github.com/bluesky-social/indigo/atproto/crypto" 7 8 "github.com/golang-jwt/jwt/v5" ··· 89 90 func toES256(sig []byte) []byte { 90 91 return sig[:64] 91 92 } 93 + 94 + func keySigningMethod(key atcrypto.PrivateKey) (jwt.SigningMethod, error) { 95 + switch key.(type) { 96 + case *atcrypto.PrivateKeyP256: 97 + return signingMethodES256, nil 98 + case *atcrypto.PrivateKeyK256: 99 + return signingMethodES256K, nil 100 + } 101 + return nil, fmt.Errorf("unknown key type: %T", key) 102 + }
+41 -53
atproto/auth/oauth/oauth.go
··· 16 16 "github.com/google/go-querystring/query" 17 17 ) 18 18 19 + var JWT_EXPIRATION_DURATION = 30 * time.Second 20 + 21 + // Service-level client. Used to establish and refrsh OAuth sessions, but is not itself account or session specific, and can not be used directly to make API calls on behalf of a user. 22 + type OAuthClient struct { 23 + Client *http.Client 24 + Resolver *Resolver 25 + Config *ClientConfig 26 + } 27 + 19 28 type ClientConfig struct { 20 29 ClientID string 21 30 CallbackURL string 22 - PrivateKey crypto.PrivateKey 23 - KeyID string 31 + 32 + UserAgent string 24 33 25 - // TODO: better name (and default?) for this JWT expiration value 26 - TTL time.Duration 34 + // For confidential clients, the private client assertion key. Note that while an interface is used here, only P-256 is allowed by the current specification. 35 + PrivateKey crypto.PrivateKey 36 + 37 + // ID for current client assertion key (should be provided if PrivateKey is) 38 + KeyID *string 39 + } 40 + 41 + func (conf *ClientConfig) IsConfidential() bool { 42 + return conf.PrivateKey != nil && conf.KeyID != nil 27 43 } 28 44 29 45 func NewClientConfig(clientID, callbackURL string) ClientConfig { 30 46 c := ClientConfig{ 31 47 ClientID: clientID, 32 48 CallbackURL: callbackURL, 33 - //PrivateKey 34 - KeyID: "0", 35 - TTL: 30 * time.Second, 36 49 } 37 50 return c 38 51 } 39 52 40 - // Returns public JWK corresponding to the client's private attestation key. 53 + // Returns public JWK corresponding to the client's (private) attestation key. 41 54 // 42 55 // If the client does not have a key (eg, a non-confidential client), returns an error. 43 56 func (c *ClientConfig) PublicJWK() (*crypto.JWK, error) { 44 - if c.PrivateKey == nil { 57 + if c.PrivateKey == nil || c.KeyID == nil { 45 58 return nil, fmt.Errorf("non-confidential client has no public JWK") 46 59 } 47 60 pub, err := c.PrivateKey.PublicKey() ··· 52 65 if err != nil { 53 66 return nil, err 54 67 } 55 - jwk.KeyID = strPtr(c.KeyID) 68 + jwk.KeyID = c.KeyID 56 69 return jwk, nil 57 70 } 58 71 ··· 74 87 return jwks 75 88 } 76 89 77 - func (c *ClientConfig) signingMethod() (jwt.SigningMethod, error) { 78 - switch c.PrivateKey.(type) { 79 - case *crypto.PrivateKeyP256: 80 - return signingMethodES256, nil 81 - case *crypto.PrivateKeyK256: 82 - return signingMethodES256K, nil 83 - } 84 - return nil, fmt.Errorf("unknown clientSecretKey type") 85 - } 86 - 87 90 // Returns a ClientMetadata struct with the required fields populated based on this client configuration. Clients may want to populate additional metadata fields on top of this response. 88 91 // 89 92 // TODO: confidential clients currently must provide JWKSUri after the fact ··· 100 103 RedirectURIs: []string{c.CallbackURL}, 101 104 DpopBoundAccessTokens: true, 102 105 } 103 - if c.PrivateKey != nil { 106 + if c.IsConfidential() { 104 107 m.TokenEndpointAuthMethod = strPtr("private_key_jwt") 105 - m.TokenEndpointAuthSigningAlg = strPtr("ES256") 108 + m.TokenEndpointAuthSigningAlg = strPtr("ES256") // XXX 106 109 // TODO: what is the correct format for in-line JWKS? 107 110 //m.JWKS = c.JWKS() 108 111 } ··· 150 153 Nonce *string `json:"nonce,omitempty"` 151 154 } 152 155 153 - func (c *ClientConfig) NewAssertionJWT(authURL string) (string, error) { 154 - if c.PrivateKey == nil { 156 + func (conf *ClientConfig) NewAssertionJWT(authURL string) (string, error) { 157 + if !conf.IsConfidential() { 155 158 return "", fmt.Errorf("non-confidential client") 156 159 } 157 160 claims := clientAssertionClaims{ 158 161 RegisteredClaims: jwt.RegisteredClaims{ 159 - Issuer: c.ClientID, 160 - Subject: c.ClientID, 162 + Issuer: conf.ClientID, 163 + Subject: conf.ClientID, 161 164 Audience: []string{authURL}, 162 165 ID: randomNonce(), 163 166 IssuedAt: jwt.NewNumericDate(time.Now()), 164 167 }, 165 168 } 166 169 167 - signingMethod, err := c.signingMethod() 170 + signingMethod, err := keySigningMethod(conf.PrivateKey) 168 171 if err != nil { 169 172 return "", err 170 173 } 171 174 172 175 token := jwt.NewWithClaims(signingMethod, claims) 173 - token.Header["kid"] = c.KeyID 174 - return token.SignedString(c.PrivateKey) 176 + token.Header["kid"] = conf.KeyID 177 + return token.SignedString(conf.PrivateKey) 175 178 } 176 179 177 180 func NewDPoPJWT(httpMethod, url, dpopNonce string, privKey crypto.PrivateKey) (string, error) { 178 - 179 - // TODO: argument? config? 180 - ttl := 30 * time.Second 181 181 182 182 claims := dpopClaims{ 183 183 HTTPMethod: httpMethod, ··· 185 185 RegisteredClaims: jwt.RegisteredClaims{ 186 186 ID: randomNonce(), 187 187 IssuedAt: jwt.NewNumericDate(time.Now()), 188 - ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl)), 188 + ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRATION_DURATION)), 189 189 }, 190 190 } 191 191 if dpopNonce != "" { 192 192 claims.Nonce = &dpopNonce 193 193 } 194 194 195 - // XXX: refactor to helper method 196 - var keyMethod jwt.SigningMethod 197 - switch privKey.(type) { 198 - case *crypto.PrivateKeyP256: 199 - keyMethod = signingMethodES256 200 - case *crypto.PrivateKeyK256: 201 - keyMethod = signingMethodES256K 202 - default: 203 - return "", fmt.Errorf("unknown clientSecretKey type") 195 + keyMethod, err := keySigningMethod(privKey) 196 + if err != nil { 197 + return "", err 204 198 } 205 199 206 - // XXX: parse/cache this elsewhere 200 + // TODO: parse/cache this elsewhere 207 201 pub, err := privKey.PublicKey() 208 202 if err != nil { 209 203 return "", err ··· 530 524 Issuer: sess.Data.AuthServerURL, 531 525 ID: randomNonce(), 532 526 IssuedAt: jwt.NewNumericDate(time.Now()), 533 - ExpiresAt: jwt.NewNumericDate(time.Now().Add(sess.Config.TTL)), 527 + ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRATION_DURATION)), 534 528 }, 535 529 } 536 530 if sess.Data.DpopHostNonce != "" { 537 531 claims.Nonce = &sess.Data.DpopHostNonce 538 532 } 539 533 540 - // XXX: refactor to helper method 541 - var keyMethod jwt.SigningMethod 542 - switch sess.DpopPrivateKey.(type) { 543 - case *crypto.PrivateKeyP256: 544 - keyMethod = signingMethodES256 545 - case *crypto.PrivateKeyK256: 546 - keyMethod = signingMethodES256K 547 - default: 548 - return "", fmt.Errorf("unknown clientSecretKey type") 534 + keyMethod, err := keySigningMethod(sess.DpopPrivateKey) 535 + if err != nil { 536 + return "", err 549 537 } 550 538 551 539 // TODO: parse/cache this elsewhere