Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5db5fcb5e6 | ||
|  | 0df4535e17 | ||
| 86e605f265 | 
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,15 +1,6 @@ | |||||||
| /public-jwks | /public-jwks | ||||||
| /go-mockid | /go-mockid | ||||||
| 
 | 
 | ||||||
| # ---> Security |  | ||||||
| .env |  | ||||||
| 
 |  | ||||||
| # ---> Vim |  | ||||||
| .*.sw* |  | ||||||
| 
 |  | ||||||
| # ---> Node |  | ||||||
| node_modules |  | ||||||
| 
 |  | ||||||
| # ---> Go | # ---> Go | ||||||
| # Binaries for programs and plugins | # Binaries for programs and plugins | ||||||
| *.exe | *.exe | ||||||
|  | |||||||
| @ -1 +0,0 @@ | |||||||
| vendor/ |  | ||||||
							
								
								
									
										78
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								README.md
									
									
									
									
									
								
							| @ -1,81 +1,3 @@ | |||||||
| # go-mockid | # go-mockid | ||||||
| 
 | 
 | ||||||
| OAuth2 / JWT / OpenID Connect for mocking auth... which isn't that different from doing it for real, actually. | OAuth2 / JWT / OpenID Connect for mocking auth... which isn't that different from doing it for real, actually. | ||||||
| 
 |  | ||||||
| ## Enabling Google OAuth2 (Mid-2020) |  | ||||||
| 
 |  | ||||||
| 1. Create an account at https://console.developers.google.com/apis/dashboard |  | ||||||
| 2. Go back to https://console.developers.google.com/apis/dashboard |  | ||||||
| 3. Create a New Project from the dropdown in the upper left that lists the current project name |  | ||||||
|   4. Give the project a name such as `Example Web App` and accept its generated ID |  | ||||||
|   5. Click "Create" |  | ||||||
| 
 |  | ||||||
| Add your test domain |  | ||||||
| 
 |  | ||||||
| 1. Go back to https://console.developers.google.com/apis/dashboard |  | ||||||
| 1. Select your new project from the upper-left drop-down |  | ||||||
| 2. Select `Domain Verification` from the left hand side of the screen |  | ||||||
| 3. Add your test domain (i.e. `beta.example.com`), but a domain that you actually own |  | ||||||
| 4. Select `Verify Ownership` |  | ||||||
| 5. Follow the specific instructions for adding a txt record to the subdomain you chose |  | ||||||
| 6. Add a collaborator / co-owner if you wish |  | ||||||
| 
 |  | ||||||
| Enable OAuth2 |  | ||||||
| 
 |  | ||||||
| 1. Go back to https://console.developers.google.com/apis/dashboard |  | ||||||
| 1. Select `OAuth consent screen` |  | ||||||
| 2. Select `External` |  | ||||||
| 3. Complete the consent screen form |  | ||||||
| 
 |  | ||||||
| Create Google Credentials |  | ||||||
| 
 |  | ||||||
| 1. Go back to https://console.developers.google.com/apis/dashboard |  | ||||||
| 1. Select `Credentials` from the left sidebar |  | ||||||
| 2. Select `OAuth ID` |  | ||||||
| 3. Select `Web Application` |  | ||||||
| 4. Fill out the same test domain and test app name as before |  | ||||||
| 5. Save the ID and Secret to a place you won't forget (perhaps a .gitignored .env) |  | ||||||
| 
 |  | ||||||
| Update your signin page. |  | ||||||
| 
 |  | ||||||
| 1. You need to put your default scopes (i.e. `profile email`) and client ID in the meta tag of your login page HTML. `profile` is the minimum scope and is always returned. |  | ||||||
|   ```html |  | ||||||
|   <head> |  | ||||||
|     <meta name="google-signin-scope" content="email"> |  | ||||||
|     <meta |  | ||||||
|       name="google-signin-client_id" |  | ||||||
|       content="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com" |  | ||||||
|     /> |  | ||||||
|   </head> |  | ||||||
|   ``` |  | ||||||
| 2. Although it should be possible to use an thin OAuth client, you'll probably want to start by including the (huge) Google platform.js |  | ||||||
|   ```html |  | ||||||
|   <script src="https://apis.google.com/js/platform.js" async defer></script> |  | ||||||
|   ``` |  | ||||||
| 3. You can start off with the Google's sign in button, but you need your own `data-onsuccess` callback. You can also adjust the `data-scope` per button to include more stuff. Scopes are defined at https://developers.google.com/identity/protocols/oauth2/scopes |  | ||||||
|   ```html |  | ||||||
|   <div |  | ||||||
|     class="g-signin2" |  | ||||||
|     data-onsuccess="ongsignin" |  | ||||||
|     data-scope="profile email https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/drive.readonly" |  | ||||||
|   ></div> |  | ||||||
|   <script> |  | ||||||
|     window.ongsignin = function (gauth) { |  | ||||||
|       // Note: this is a special prototype-style instance object with few |  | ||||||
|       // enumerable properties (which don't make sense). Requires API docs. |  | ||||||
|       // See https://developers.google.com/identity/sign-in/web |  | ||||||
|       console.log(goauth) |  | ||||||
|     }; |  | ||||||
|   </script> |  | ||||||
|   ``` |  | ||||||
| 4. Despite the documentation stating that passing a token as a query is deprecated and to use the `Authorization` header, the inspect token URL only supports the query parameter: `GET https://oauth2.googleapis.com/tokeninfo?id_token=<token>` |  | ||||||
|   - You can also validate the token with Google's public key |  | ||||||
|   - https://accounts.google.com/.well-known/openid-configuration |  | ||||||
|   - https://www.googleapis.com/oauth2/v3/certs (note that one of the Key IDs will match that of your kid) |  | ||||||
| 5. While testing you'll probably want to revoke the app's permissions |  | ||||||
|   - Go to https://myaccount.google.com/permissions |  | ||||||
|   - Under "Third-party apps with account access" click "Manage third-party access" and search in the long list and click "Remove access". |  | ||||||
|   - Under "Signing in to other sites" click "Signing in with Google" and search in the list to revoke access |  | ||||||
|   - Active tokens will persist until they expire (1 hour), so you may need to clear cache, cookies, etc, which can be a pain |  | ||||||
| 5. Sign out can be accomplished with a button that calls `gapi.auth2.getAuthInstance().signOut().then(function() { });` |  | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -1,65 +0,0 @@ | |||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"flag" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	mailgun "github.com/mailgun/mailgun-go/v3" |  | ||||||
| 
 |  | ||||||
| 	_ "github.com/joho/godotenv/autoload" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	/* |  | ||||||
| 	  MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |  | ||||||
| 	  MAILGUN_DOMAIN=mail.example.com |  | ||||||
| 	  MAILER_FROM="Rob the Robot <rob.the.robot@mail.example.com>" |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	to := flag.String("to", "", "message recipient in the format of 'John Doe <john@example.com>'") |  | ||||||
| 	replyTo := flag.String("reply-to", "", "reply-to in the format of 'John Doe <john@example.com>'") |  | ||||||
| 	subject := flag.String("subject", "Test Subject", "the utf8-encoded subject of the email") |  | ||||||
| 	text := flag.String( |  | ||||||
| 		"text", |  | ||||||
| 		"Testing some Mailgun awesomeness!", |  | ||||||
| 		"the body of the email as utf8-encoded plain-text format", |  | ||||||
| 	) |  | ||||||
| 	flag.Parse() |  | ||||||
| 
 |  | ||||||
| 	if 0 == len(*to) { |  | ||||||
| 		flag.Usage() |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	domain := os.Getenv("MAILGUN_DOMAIN") |  | ||||||
| 	apiKey := os.Getenv("MAILGUN_API_KEY") |  | ||||||
| 	from := os.Getenv("MAILER_FROM") |  | ||||||
| 
 |  | ||||||
| 	if 0 == len(*text) { |  | ||||||
| 		*text = "Testing some Mailgun awesomeness!" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	msgId, err := SendSimpleMessage(domain, apiKey, *to, from, *subject, *text, *replyTo) |  | ||||||
| 	if nil != err { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	fmt.Printf("Queued with Message ID %q\n", msgId) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func SendSimpleMessage(domain, apiKey, to, from, subject, text, replyTo string) (string, error) { |  | ||||||
| 	mg := mailgun.NewMailgun(domain, apiKey) |  | ||||||
| 	m := mg.NewMessage(from, subject, text, to) |  | ||||||
| 	if 0 != len(replyTo) { |  | ||||||
| 		// mailgun's required "h:" prefix is added by the library |  | ||||||
| 		m.AddHeader("Reply-To", replyTo) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) |  | ||||||
| 	defer cancel() |  | ||||||
| 
 |  | ||||||
| 	_, id, err := mg.Send(ctx, m) |  | ||||||
| 	return id, err |  | ||||||
| } |  | ||||||
| @ -1,7 +0,0 @@ | |||||||
| { |  | ||||||
|   "kty": "EC", |  | ||||||
|   "crv": "P-256", |  | ||||||
|   "d": "GYAwlBHc2mPsj1lp315HbYOmKNJ7esmO3JAkZVn9nJs", |  | ||||||
|   "x": "ToL2HppsTESXQKvp7ED6NMgV4YnwbMeONexNry3KDNQ", |  | ||||||
|   "y": "Tt6Q3rxU37KAinUV9PLMlwosNy1t3Bf2VDg5q955AGc" |  | ||||||
| } |  | ||||||
| @ -1,7 +0,0 @@ | |||||||
| SALT=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |  | ||||||
| 
 |  | ||||||
| MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |  | ||||||
| MAILGUN_DOMAIN=mail.example.com |  | ||||||
| 
 |  | ||||||
| MAILER_FROM="Rob the Robot <rob.the.robot@mail.example.com>" |  | ||||||
| MAILER_REPLY_TO=support@example.com |  | ||||||
| @ -1 +0,0 @@ | |||||||
| go test -mod=vendor -v ./... |  | ||||||
							
								
								
									
										11
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								go.mod
									
									
									
									
									
								
							| @ -1,12 +1,5 @@ | |||||||
| module git.coolaj86.com/coolaj86/go-mockid | module git.coolaj86.com/coolaj86/go-mockid | ||||||
| 
 | 
 | ||||||
| go 1.13 | go 1.12 | ||||||
| 
 | 
 | ||||||
| require ( | require github.com/joho/godotenv v1.3.0 | ||||||
| 	git.rootprojects.org/root/hashcash v1.0.1 |  | ||||||
| 	git.rootprojects.org/root/keypairs v0.6.5 |  | ||||||
| 	github.com/google/uuid v1.1.1 |  | ||||||
| 	github.com/joho/godotenv v1.3.0 |  | ||||||
| 	github.com/mailgun/mailgun-go/v3 v3.6.4 |  | ||||||
| 	github.com/mileusna/useragent v1.0.2 |  | ||||||
| ) |  | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,26 +1,2 @@ | |||||||
| git.rootprojects.org/root/hashcash v1.0.1 h1:PkzwZu4CR5q/hwAntJdvcmNhmP0ONhetMo7rYhIZhZ0= |  | ||||||
| git.rootprojects.org/root/hashcash v1.0.1/go.mod h1:HdoULUe94o1NVMES5K6aP3p8QGQiIia73F1HNZ1+FkQ= |  | ||||||
| git.rootprojects.org/root/keypairs v0.6.5 h1:sdRAQD/O/JBS8+ZxUewXnY+cjQVDNH3TmcS+KtANZqA= |  | ||||||
| git.rootprojects.org/root/keypairs v0.6.5/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA= |  | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |  | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |  | ||||||
| github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= |  | ||||||
| github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= |  | ||||||
| github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= |  | ||||||
| github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= |  | ||||||
| github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= |  | ||||||
| github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= |  | ||||||
| github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4= |  | ||||||
| github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= |  | ||||||
| github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= |  | ||||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |  | ||||||
| github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= | ||||||
| github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | ||||||
| github.com/mailgun/mailgun-go/v3 v3.6.4 h1:+cvbZRgLSHivbz/w1iWLmxVl6Bqf4geD2D7QMj4+8PE= |  | ||||||
| github.com/mailgun/mailgun-go/v3 v3.6.4/go.mod h1:ZjVnH8S0dR2BLjvkZc/rxwerdcirzlA12LQDuGAadR0= |  | ||||||
| github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= |  | ||||||
| github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= |  | ||||||
| github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= |  | ||||||
| github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= |  | ||||||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |  | ||||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |  | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								kvdb/kvdb.go
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								kvdb/kvdb.go
									
									
									
									
									
								
							| @ -1,131 +0,0 @@ | |||||||
| package kvdb |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type KVDB struct { |  | ||||||
| 	Prefix string |  | ||||||
| 	Ext    string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (kv *KVDB) Load( |  | ||||||
| 	keyif interface{}, |  | ||||||
| 	typ ...interface{}, |  | ||||||
| ) (value interface{}, ok bool, err error) { |  | ||||||
| 	key, _ := keyif.(string) |  | ||||||
| 	if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#!:| \n") { |  | ||||||
| 		return nil, false, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	userFile := filepath.Join(kv.Prefix, key+"."+kv.Ext) |  | ||||||
| 	fmt.Println("Debug user file:", userFile) |  | ||||||
| 	b, err := ioutil.ReadFile(userFile) |  | ||||||
| 	if nil != err { |  | ||||||
| 		if os.IsNotExist(err) { |  | ||||||
| 			return nil, false, nil |  | ||||||
| 		} |  | ||||||
| 		fmt.Println("kvdb debug read:", err) |  | ||||||
| 		return nil, false, errors.New("database read failed") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ok = true |  | ||||||
| 	value = b |  | ||||||
| 	if 1 == len(typ) { |  | ||||||
| 		err := json.Unmarshal(b, typ[0]) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, false, err |  | ||||||
| 		} |  | ||||||
| 		value = typ[0] |  | ||||||
| 	} else if len(b) > 0 && '"' == b[0] { |  | ||||||
| 		var str string |  | ||||||
| 		err := json.Unmarshal(b, &str) |  | ||||||
| 		if nil == err { |  | ||||||
| 			value = str |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return value, ok, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (kv *KVDB) Store(keyif interface{}, value interface{}) (err error) { |  | ||||||
| 	key, _ := keyif.(string) |  | ||||||
| 	if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") { |  | ||||||
| 		return errors.New("invalid key name") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext) |  | ||||||
| 	f, err := os.Open(keypath) |  | ||||||
| 	if nil == err { |  | ||||||
| 		s, err := f.Stat() |  | ||||||
| 		if nil != err { |  | ||||||
| 			// if we can open, we should be able to stat |  | ||||||
| 			return errors.New("database connection failure") |  | ||||||
| 		} |  | ||||||
| 		ts := strconv.FormatInt(s.ModTime().Unix(), 10) |  | ||||||
| 		bakpath := filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext) |  | ||||||
| 		if err := os.Rename(keypath, bakpath); nil != err { |  | ||||||
| 			// keep the old record as a backup |  | ||||||
| 			return errors.New("database write failure") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var b []byte |  | ||||||
| 	switch v := value.(type) { |  | ||||||
| 	case []byte: |  | ||||||
| 		b = v |  | ||||||
| 	case string: |  | ||||||
| 		b, _ = json.Marshal(v) |  | ||||||
| 	default: |  | ||||||
| 		fmt.Println("kvdb: not []byte or string:", v) |  | ||||||
| 		jsonb, err := json.Marshal(v) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		b = jsonb |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := ioutil.WriteFile( |  | ||||||
| 		keypath, |  | ||||||
| 		b, |  | ||||||
| 		os.FileMode(0600), |  | ||||||
| 	); nil != err { |  | ||||||
| 		fmt.Println("write failure:", err) |  | ||||||
| 		return errors.New("database write failed") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (kv *KVDB) Delete(keyif interface{}) (err error) { |  | ||||||
| 	key, _ := keyif.(string) |  | ||||||
| 	if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") { |  | ||||||
| 		return errors.New("invalid key name") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext) |  | ||||||
| 	f, err := os.Open(keypath) |  | ||||||
| 	if nil == err { |  | ||||||
| 		s, err := f.Stat() |  | ||||||
| 		if nil != err { |  | ||||||
| 			return errors.New("database connection failure") |  | ||||||
| 		} |  | ||||||
| 		ts := strconv.FormatInt(s.ModTime().Unix(), 64) |  | ||||||
| 		if err := os.Rename(keypath, filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext)); nil != err { |  | ||||||
| 			return errors.New("database connection failure") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (kv *KVDB) Vacuum() (err error) { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -1,63 +0,0 @@ | |||||||
| package kvdb |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strings" |  | ||||||
| 	"testing" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type TestEntry struct { |  | ||||||
| 	Email    string   `json:"email"` |  | ||||||
| 	Subjects []string `json:"subjects"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var email = "john@example.com" |  | ||||||
| var sub = "id123" |  | ||||||
| var dbPrefix = "../testdb" |  | ||||||
| var testKV = &KVDB{ |  | ||||||
| 	Prefix: dbPrefix + "/test-entries", |  | ||||||
| 	Ext:    "eml.json", |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestStore(t *testing.T) { |  | ||||||
| 	entry := &TestEntry{ |  | ||||||
| 		Email:    email, |  | ||||||
| 		Subjects: []string{sub}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := testKV.Store(email, entry); nil != err { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	value, ok, err := testKV.Load(email, &(TestEntry{})) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatal("test entry not found") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	v, ok := value.(*TestEntry) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatal("test entry not of type TestEntry") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if email != v.Email || sub != strings.Join(v.Subjects, ",") { |  | ||||||
| 		t.Fatalf("value: %#v", v) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestNoExist(t *testing.T) { |  | ||||||
| 	value, ok, err := testKV.Load("not"+email, &(TestEntry{})) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if ok { |  | ||||||
| 		t.Fatal("found entry that doesn't exist") |  | ||||||
| 	} |  | ||||||
| 	if value != nil { |  | ||||||
| 		t.Fatal("had value for entry that doesn't exist") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										54
									
								
								mockid.go
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								mockid.go
									
									
									
									
									
								
							| @ -1,19 +1,16 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"math/rand" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"git.coolaj86.com/coolaj86/go-mockid/mockid" | 	"git.coolaj86.com/coolaj86/go-mockid/mockid" | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| 
 | 
 | ||||||
| 	_ "github.com/joho/godotenv/autoload" | 	_ "github.com/joho/godotenv/autoload" | ||||||
| ) | ) | ||||||
| @ -23,7 +20,21 @@ func main() { | |||||||
| 	var port int | 	var port int | ||||||
| 	var host string | 	var host string | ||||||
| 
 | 
 | ||||||
| 	rand.Seed(time.Now().UnixNano()) | 	jwkm := map[string]string{ | ||||||
|  | 		"crv": "P-256", | ||||||
|  | 		"d":   "GYAwlBHc2mPsj1lp315HbYOmKNJ7esmO3JAkZVn9nJs", | ||||||
|  | 		"x":   "ToL2HppsTESXQKvp7ED6NMgV4YnwbMeONexNry3KDNQ", | ||||||
|  | 		"y":   "Tt6Q3rxU37KAinUV9PLMlwosNy1t3Bf2VDg5q955AGc", | ||||||
|  | 	} | ||||||
|  | 	jwk := &mockid.PrivateJWK{ | ||||||
|  | 		PublicJWK: mockid.PublicJWK{ | ||||||
|  | 			Crv: jwkm["crv"], | ||||||
|  | 			X:   jwkm["x"], | ||||||
|  | 			Y:   jwkm["y"], | ||||||
|  | 		}, | ||||||
|  | 		D: jwkm["d"], | ||||||
|  | 	} | ||||||
|  | 	priv := mockid.ParseKey(jwk) | ||||||
| 
 | 
 | ||||||
| 	portFlag := flag.Int("port", 0, "Port on which the HTTP server should run") | 	portFlag := flag.Int("port", 0, "Port on which the HTTP server should run") | ||||||
| 	urlFlag := flag.String("url", "", "Outward-facing address, such as https://example.com") | 	urlFlag := flag.String("url", "", "Outward-facing address, such as https://example.com") | ||||||
| @ -41,20 +52,6 @@ func main() { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	jwkpath := "./default.jwk.json" |  | ||||||
| 	jwkb, err := ioutil.ReadFile(jwkpath) |  | ||||||
| 	if nil != err { |  | ||||||
| 		panic(fmt.Errorf("read default jwk %v: %w", jwkpath, err)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, err := keypairs.ParseJWKPrivateKey(jwkb) |  | ||||||
| 	if nil != err { |  | ||||||
| 		// TODO delete the bad file? |  | ||||||
| 		panic(fmt.Errorf("unmarshal jwk %v: %w", string(jwkb), err)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if nil != urlFlag && "" != *urlFlag { | 	if nil != urlFlag && "" != *urlFlag { | ||||||
| 		host = *urlFlag | 		host = *urlFlag | ||||||
| 	} else { | 	} else { | ||||||
| @ -67,15 +64,15 @@ func main() { | |||||||
| 	} else { | 	} else { | ||||||
| 		jwksPrefix = "public-jwks" | 		jwksPrefix = "public-jwks" | ||||||
| 	} | 	} | ||||||
| 	err = os.MkdirAll(jwksPrefix, 0755) | 	err := os.MkdirAll(jwksPrefix, 0755) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		fmt.Fprintf(os.Stderr, "couldn't write %q: %s", jwksPrefix, err) | 		fmt.Fprintf(os.Stderr, "couldn't write %q: %s", jwksPrefix, err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mux := mockid.Route(jwksPrefix, privkey) | 	mockid.Route(jwksPrefix, priv, jwk) | ||||||
| 
 | 
 | ||||||
| 	fs := http.FileServer(http.Dir("./public")) | 	fs := http.FileServer(http.Dir("public")) | ||||||
| 	http.Handle("/", fs) | 	http.Handle("/", fs) | ||||||
| 	/* | 	/* | ||||||
| 		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | 		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||||
| @ -86,16 +83,15 @@ func main() { | |||||||
| 
 | 
 | ||||||
| 	fmt.Printf("Serving on port %d\n", port) | 	fmt.Printf("Serving on port %d\n", port) | ||||||
| 	go func() { | 	go func() { | ||||||
| 		log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), mux)) | 		log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), nil)) | ||||||
| 		done <- true | 		done <- true | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	// TODO privB := keypairs.MarshalJWKPrivateKey(privkey) | 	b, _ := json.Marshal(jwk) | ||||||
| 	privB := keypairs.MarshalJWKPrivateKey(privkey) | 	fmt.Printf("Private Key:\n\t%s\n", string(b)) | ||||||
| 	fmt.Printf("Private Key:\n\t%s\n", string(privB)) | 	b, _ = json.Marshal(jwk.PublicJWK) | ||||||
| 	pubB := keypairs.MarshalJWKPublicKey(keypairs.NewPublicKey(privkey.Public())) | 	fmt.Printf("Public Key:\n\t%s\n", string(b)) | ||||||
| 	fmt.Printf("Public Key:\n\t%s\n", string(pubB)) | 	protected, payload, token := mockid.GenToken(host, priv, url.Values{}) | ||||||
| 	protected, payload, token := mockid.GenToken(host, privkey, url.Values{}) |  | ||||||
| 	fmt.Printf("Protected (Header):\n\t%s\n", protected) | 	fmt.Printf("Protected (Header):\n\t%s\n", protected) | ||||||
| 	fmt.Printf("Payload (Claims):\n\t%s\n", payload) | 	fmt.Printf("Payload (Claims):\n\t%s\n", payload) | ||||||
| 	fmt.Printf("Access Token:\n\t%s\n", token) | 	fmt.Printf("Access Token:\n\t%s\n", token) | ||||||
|  | |||||||
| @ -1,71 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"math/rand" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"git.coolaj86.com/coolaj86/go-mockid/xkeypairs" |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| func getJWS(r *http.Request) (*xkeypairs.KeyOptions, error) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| func getOpts(r *http.Request) (*xkeypairs.KeyOptions, error) { |  | ||||||
| 	tok := make(map[string]interface{}) |  | ||||||
| 	decoder := json.NewDecoder(r.Body) |  | ||||||
| 	err := decoder.Decode(&tok) |  | ||||||
| 	if nil != err && io.EOF != err { |  | ||||||
| 		log.Printf("json decode error: %s", err) |  | ||||||
| 		return nil, errors.New("Bad Request: invalid json body") |  | ||||||
| 	} |  | ||||||
| 	defer r.Body.Close() |  | ||||||
| 
 |  | ||||||
| 	var seed int64 |  | ||||||
| 	seedStr, _ := tok["seed"].(string) |  | ||||||
| 	if "" != seedStr { |  | ||||||
| 		if len(seedStr) > 256 { |  | ||||||
| 			return nil, errors.New("Bad Request: base64 seed should be <256 characters (and is truncated to 64-bits anyway)") |  | ||||||
| 		} |  | ||||||
| 		b := sha256.Sum256([]byte(seedStr)) |  | ||||||
| 		seed, _ = binary.ReadVarint(bytes.NewReader(b[0:8])) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key, _ := tok["key"].(string) |  | ||||||
| 	opts := &xkeypairs.KeyOptions{ |  | ||||||
| 		Seed: seed, |  | ||||||
| 		Key:  key, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts.Claims, _ = tok["claims"].(keypairs.Object) |  | ||||||
| 	opts.Header, _ = tok["header"].(keypairs.Object) |  | ||||||
| 
 |  | ||||||
| 	var n int |  | ||||||
| 	if 0 != seed { |  | ||||||
| 		n = opts.MyFooNextReader().(*mathrand.Rand).Intn(2) |  | ||||||
| 	} else { |  | ||||||
| 		n = rand.Intn(2) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts.KeyType, _ = tok["kty"].(string) |  | ||||||
| 	if "" == opts.KeyType { |  | ||||||
| 		if 0 == n { |  | ||||||
| 			opts.KeyType = "RSA" |  | ||||||
| 		} else { |  | ||||||
| 			opts.KeyType = "EC" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return opts, nil |  | ||||||
| } |  | ||||||
| @ -1,145 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"git.coolaj86.com/coolaj86/go-mockid/xkeypairs" |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // GeneratePublicJWK will create a new private key in JWK format |  | ||||||
| func GeneratePublicJWK(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, err := getPrivKey(opts) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jwk := keypairs.MarshalJWKPublicKey(keypairs.NewPublicKey(privkey.Public())) |  | ||||||
| 	w.Write(append(jwk, '\n')) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GeneratePrivateJWK will create a new private key in JWK format |  | ||||||
| func GeneratePrivateJWK(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey := xkeypairs.GenPrivKey(opts) |  | ||||||
| 
 |  | ||||||
| 	jwk := keypairs.MarshalJWKPrivateKey(privkey) |  | ||||||
| 	w.Write(append(jwk, '\n')) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GeneratePublicDER will create a new private key in JWK format |  | ||||||
| func GeneratePublicDER(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, err := getPrivKey(opts) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b, _ := keypairs.MarshalDERPublicKey(privkey.Public()) |  | ||||||
| 
 |  | ||||||
| 	w.Write(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GeneratePrivateDER will create a new private key in a valid DER encoding |  | ||||||
| func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey := xkeypairs.GenPrivKey(opts) |  | ||||||
| 
 |  | ||||||
| 	der, _ := keypairs.MarshalDERPrivateKey(privkey) |  | ||||||
| 	w.Write(der) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GeneratePublicPEM will create a new private key in JWK format |  | ||||||
| func GeneratePublicPEM(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, err := getPrivKey(opts) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b, _ := keypairs.MarshalPEMPublicKey(privkey.Public()) |  | ||||||
| 
 |  | ||||||
| 	w.Write(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GeneratePrivatePEM will create a new private key in a valid PEM encoding |  | ||||||
| func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey := xkeypairs.GenPrivKey(opts) |  | ||||||
| 
 |  | ||||||
| 	privpem, _ := keypairs.MarshalPEMPrivateKey(privkey) |  | ||||||
| 	w.Write(privpem) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const maxRetry = 16 |  | ||||||
| 
 |  | ||||||
| func getPrivKey(opts *xkeypairs.KeyOptions) (keypairs.PrivateKey, error) { |  | ||||||
| 	if "" != opts.Key { |  | ||||||
| 		return keypairs.ParsePrivateKey([]byte(opts.Key)) |  | ||||||
| 	} |  | ||||||
| 	return xkeypairs.GenPrivKey(opts), nil |  | ||||||
| } |  | ||||||
| @ -1,57 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // SignJWS will create an uncompressed JWT with the given payload |  | ||||||
| func SignJWS(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	sign(w, r, false) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SignJWT will create an compressed JWS (JWT) with the given payload |  | ||||||
| func SignJWT(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	sign(w, r, true) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func sign(w http.ResponseWriter, r *http.Request, jwt bool) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := getOpts(r) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, err := getPrivKey(opts) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	header := opts.Header |  | ||||||
| 	if 0 != opts.Seed { |  | ||||||
| 		header["_seed"] = opts.Seed |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jws, err := keypairs.SignClaims(privkey, header, opts.Claims) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var b []byte |  | ||||||
| 	if jwt { |  | ||||||
| 		s := keypairs.JWSToJWT(jws) |  | ||||||
| 		w.Write(append([]byte(s), '\n')) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	b, _ = json.Marshal(jws) |  | ||||||
| 	w.Write(append(b, '\n')) |  | ||||||
| } |  | ||||||
| @ -1,87 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Verify will verify both JWT and uncompressed JWS |  | ||||||
| func Verify(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	if "POST" != r.Method { |  | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jws := &keypairs.JWS{} |  | ||||||
| 
 |  | ||||||
| 	authzParts := strings.Split(r.Header.Get("Authorization"), " ") |  | ||||||
| 	lenAuthz := len(authzParts) |  | ||||||
| 	if 2 == lenAuthz { |  | ||||||
| 		jwt := authzParts[1] |  | ||||||
| 		jwsParts := strings.Split(jwt, ".") |  | ||||||
| 		if 3 == len(jwsParts) { |  | ||||||
| 			jws.Protected = jwsParts[0] |  | ||||||
| 			jws.Payload = jwsParts[1] |  | ||||||
| 			jws.Signature = jwsParts[2] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if nil == jws { |  | ||||||
| 		if 0 != lenAuthz { |  | ||||||
| 			http.Error(w, "Bad Request: malformed Authorization header", http.StatusBadRequest) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		decoder := json.NewDecoder(r.Body) |  | ||||||
| 		err := decoder.Decode(jws) |  | ||||||
| 		if nil != err && io.EOF != err { |  | ||||||
| 			log.Printf("json decode error: %s", err) |  | ||||||
| 			http.Error(w, "Bad Request: invalid JWS body", http.StatusBadRequest) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		defer r.Body.Close() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	protected, err := base64.RawURLEncoding.DecodeString(jws.Protected) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, "Bad Request: invalid JWS header base64Url encoding", http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(protected), &jws.Header); nil != err { |  | ||||||
| 		log.Printf("json decode header error: %s", err) |  | ||||||
| 		http.Error(w, "Bad Request: invalid JWS header", http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	payload, err := base64.RawURLEncoding.DecodeString(jws.Payload) |  | ||||||
| 	if nil != err { |  | ||||||
| 		http.Error(w, "Bad Request: invalid JWS payload base64Url encoding", http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(payload), &jws.Claims); nil != err { |  | ||||||
| 		log.Printf("json decode claims error: %s", err) |  | ||||||
| 		http.Error(w, "Bad Request: invalid JWS claims", http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "false" == r.URL.Query().Get("exp") { |  | ||||||
| 		//expf64, _ := jws.Claims["exp"].(float64) |  | ||||||
| 		jws.Claims["exp"] = float64(time.Now().Add(5 * time.Minute).Unix()) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	errs := keypairs.VerifyClaims(nil, jws) |  | ||||||
| 	if 0 == len(errs) { |  | ||||||
| 		log.Printf("jws verify error: %s", errs) |  | ||||||
| 		http.Error(w, "Bad Request: could not verify JWS claims", http.StatusBadRequest) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b := []byte(`{"success":true}`) |  | ||||||
| 	w.Write(append(b, '\n')) |  | ||||||
| } |  | ||||||
| @ -1,101 +0,0 @@ | |||||||
| package mockid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"net/http" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/hashcash" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var hashcashes = &hashcashDB{ |  | ||||||
| 	db: sync.Map{}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewHashcash(sub string, exp time.Time) *hashcash.Hashcash { |  | ||||||
| 
 |  | ||||||
| 	h := hashcash.New(hashcash.Hashcash{ |  | ||||||
| 		Subject:   sub, |  | ||||||
| 		ExpiresAt: exp, |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	// ignoring the error because this implementation is backed by an in-memory map |  | ||||||
| 	_ = hashcashes.Store(h.Nonce, h) |  | ||||||
| 
 |  | ||||||
| 	return h |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ErrNotFound = errors.New("not found") |  | ||||||
| 
 |  | ||||||
| func UseHashcash(hc, sub string) error { |  | ||||||
| 	phony, err := hashcash.Parse(hc) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	hi, ok, _ := hashcashes.Load(phony.Nonce) |  | ||||||
| 	if !ok { |  | ||||||
| 		return ErrNotFound |  | ||||||
| 	} |  | ||||||
| 	mccoy := hi.(*hashcash.Hashcash) |  | ||||||
| 	mccopy := *mccoy |  | ||||||
| 
 |  | ||||||
| 	mccopy.Solution = phony.Solution |  | ||||||
| 	if err := mccopy.Verify(sub); nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_ = hashcashes.Delete(mccoy.Nonce) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func issueHashcash(w http.ResponseWriter, r *http.Request) *hashcash.Hashcash { |  | ||||||
| 	h := NewHashcash(r.Host, time.Now().Add(5*time.Minute)) |  | ||||||
| 	w.Header().Set("Hashcash-Challenge", h.String()) |  | ||||||
| 	return h |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func requireHashcash(next http.HandlerFunc) http.HandlerFunc { |  | ||||||
| 	return func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		hc := r.Header.Get("Hashcash") |  | ||||||
| 		_ = issueHashcash(w, r) |  | ||||||
| 		if err := UseHashcash(hc, r.Host); nil != err { |  | ||||||
| 			http.Error(w, "Bad Request", http.StatusBadRequest) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		next(w, r) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type hashcashDB struct { |  | ||||||
| 	db sync.Map |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (h *hashcashDB) Load(key interface{}) (value interface{}, ok bool, err error) { |  | ||||||
| 	v, ok := h.db.Load(key) |  | ||||||
| 	return v, ok, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (h *hashcashDB) Store(key interface{}, value interface{}) (err error) { |  | ||||||
| 	h.db.Store(key, value) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (h *hashcashDB) Delete(key interface{}) (err error) { |  | ||||||
| 	h.db.Delete(key) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (h *hashcashDB) vacuum() (err error) { |  | ||||||
| 	now := time.Now().UTC() |  | ||||||
| 	h.db.Range(func(key interface{}, val interface{}) bool { |  | ||||||
| 		v := val.(*hashcash.Hashcash) |  | ||||||
| 		if v.ExpiresAt.Sub(now) < 0 { |  | ||||||
| 			h.db.Delete(key) |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	}) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -1,46 +0,0 @@ | |||||||
| package mockid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"os" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	mailgun "github.com/mailgun/mailgun-go/v3" |  | ||||||
| 
 |  | ||||||
| 	_ "github.com/joho/godotenv/autoload" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	mgDomain string |  | ||||||
| 	mgAPIKey string |  | ||||||
| 	mgFrom   string |  | ||||||
| 	mg       *mailgun.MailgunImpl |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	/* |  | ||||||
| 	  MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |  | ||||||
| 	  MAILGUN_DOMAIN=mail.example.com |  | ||||||
| 	  MAILER_FROM="Rob the Robot <rob.the.robot@mail.example.com>" |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	mgDomain = os.Getenv("MAILGUN_DOMAIN") |  | ||||||
| 	mgAPIKey = os.Getenv("MAILGUN_API_KEY") |  | ||||||
| 	mgFrom = os.Getenv("MAILER_FROM") |  | ||||||
| 
 |  | ||||||
| 	mg = mailgun.NewMailgun(mgDomain, mgAPIKey) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func SendSimpleMessage(to, from, subject, text, replyTo string) (string, error) { |  | ||||||
| 	m := mg.NewMessage(from, subject, text, to) |  | ||||||
| 	if 0 != len(replyTo) { |  | ||||||
| 		// mailgun's required "h:" prefix is added by the library |  | ||||||
| 		m.AddHeader("Reply-To", replyTo) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) |  | ||||||
| 	defer cancel() |  | ||||||
| 
 |  | ||||||
| 	_, id, err := mg.Send(ctx, m) |  | ||||||
| 	return id, err |  | ||||||
| } |  | ||||||
							
								
								
									
										508
									
								
								mockid/mockid.go
									
									
									
									
									
								
							
							
						
						
									
										508
									
								
								mockid/mockid.go
									
									
									
									
									
								
							| @ -1,27 +1,29 @@ | |||||||
| package mockid | package mockid | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" | 	"crypto/sha256" | ||||||
|  | 	"crypto/sha512" | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
| 	"math/big" | 	"math/big" | ||||||
|  | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| 	//jwt "github.com/dgrijalva/jwt-go" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // TestMain will overwrite this | type PrivateJWK struct { | ||||||
| var rndsrc io.Reader = rand.Reader | 	PublicJWK | ||||||
|  | 	D string `json:"d"` | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| type PublicJWK struct { | type PublicJWK struct { | ||||||
| 	Crv   string `json:"crv"` | 	Crv   string `json:"crv"` | ||||||
| @ -31,79 +33,332 @@ type PublicJWK struct { | |||||||
| 	Y     string `json:"y"` | 	Y     string `json:"y"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type KVDB interface { | var nonces map[string]int64 | ||||||
| 	Load(key interface{}) (value interface{}, ok bool, err error) | 
 | ||||||
| 	Store(key interface{}, value interface{}) (err error) | func init() { | ||||||
| 	Delete(key interface{}) (err error) | 	nonces = make(map[string]int64) | ||||||
| 	Vacuum() (err error) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type InspectableToken struct { | func Route(jwksPrefix string, priv *ecdsa.PrivateKey, jwk *PrivateJWK) { | ||||||
| 	Public    keypairs.PublicKey     `json:"jwk"` | 	pub := &priv.PublicKey | ||||||
| 	Protected map[string]interface{} `json:"protected"` | 	thumbprint := thumbprintKey(pub) | ||||||
| 	Payload   map[string]interface{} `json:"payload"` |  | ||||||
| 	Signature string                 `json:"signature"` |  | ||||||
| 	Verified  bool                   `json:"verified"` |  | ||||||
| 	Errors    []string               `json:"errors"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (t *InspectableToken) MarshalJSON() ([]byte, error) { |  | ||||||
| 	pub := keypairs.MarshalJWKPublicKey(t.Public) |  | ||||||
| 	header, _ := json.Marshal(t.Protected) |  | ||||||
| 	payload, _ := json.Marshal(t.Payload) |  | ||||||
| 	errs, _ := json.Marshal(t.Errors) |  | ||||||
| 	return []byte(fmt.Sprintf( |  | ||||||
| 		`{"jwk":%s,"protected":%s,"payload":%s,"signature":%q,"verified":%t,"errors":%s}`, |  | ||||||
| 		pub, header, payload, t.Signature, t.Verified, errs, |  | ||||||
| 	)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var defaultFrom string |  | ||||||
| var defaultReplyTo string |  | ||||||
| 
 |  | ||||||
| var salt []byte |  | ||||||
| 
 |  | ||||||
| func Init() { |  | ||||||
| 	var err error |  | ||||||
| 	salt64 := os.Getenv("SALT") |  | ||||||
| 	salt, err = base64.RawURLEncoding.DecodeString(salt64) |  | ||||||
| 	if len(salt64) < 22 || nil != err { |  | ||||||
| 		panic("SALT must be set as 22+ character base64") |  | ||||||
| 	} |  | ||||||
| 	defaultFrom = os.Getenv("MAILER_FROM") |  | ||||||
| 	defaultReplyTo = os.Getenv("MAILER_REPLY_TO") |  | ||||||
| 	//nonces = make(map[string]int64) |  | ||||||
| 	//nonCh = make(chan string) |  | ||||||
| 
 | 
 | ||||||
|  | 	http.HandleFunc("/api/new-nonce", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		baseURL := getBaseURL(r) | ||||||
| 		/* | 		/* | ||||||
| 		  go func() { | 			res.statusCode = 200; | ||||||
| 		    for { | 			res.setHeader("Cache-Control", "max-age=0, no-cache, no-store"); | ||||||
| 		      nonce := <- nonCh | 			// TODO | ||||||
| 			    nonces[nonce] = time.Now().Unix() | 			//res.setHeader("Date", "Sun, 10 Mar 2019 08:04:45 GMT"); | ||||||
| 		    } | 			// is this the expiration of the nonce itself? methinks maybe so | ||||||
| 		  }() | 			//res.setHeader("Expires", "Sun, 10 Mar 2019 08:04:45 GMT"); | ||||||
|  | 			// TODO use one of the registered domains | ||||||
|  | 			//var indexUrl = "https://acme-staging-v02.api.letsencrypt.org/index" | ||||||
| 		*/ | 		*/ | ||||||
|  | 		//var port = (state.config.ipc && state.config.ipc.port || state._ipc.port || undefined); | ||||||
|  | 		//var indexUrl = "http://localhost:" + port + "/index"; | ||||||
|  | 		indexUrl := baseURL + "/index" | ||||||
|  | 		w.Header().Set("Link", "<"+indexUrl+">;rel=\"index\"") | ||||||
|  | 		w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store") | ||||||
|  | 		w.Header().Set("Pragma", "no-cache") | ||||||
|  | 		//res.setHeader("Strict-Transport-Security", "max-age=604800"); | ||||||
| 
 | 
 | ||||||
| 	go func() { | 		w.Header().Set("X-Frame-Options", "DENY") | ||||||
| 		for { | 		issueNonce(w, r) | ||||||
| 			time.Sleep(15 * time.Second) | 	}) | ||||||
| 			hashcashes.vacuum() | 
 | ||||||
| 		} | 	http.HandleFunc("/api/new-account", requireNonce(func(w http.ResponseWriter, r *http.Request) { | ||||||
| 	}() | 		http.Error(w, "Not Implemented", http.StatusNotImplemented) | ||||||
|  | 	})) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/api/jwks", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		log.Printf("%s %s %s", r.Method, r.Host, r.URL.Path) | ||||||
|  | 		if "POST" != r.Method { | ||||||
|  | 			http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | ||||||
|  | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (string, string, string) { | 		tok := make(map[string]interface{}) | ||||||
| 	thumbprint := keypairs.ThumbprintPublicKey(keypairs.NewPublicKey(privkey.Public())) | 		decoder := json.NewDecoder(r.Body) | ||||||
| 	// TODO keypairs.Alg(key) | 		err := decoder.Decode(&tok) | ||||||
| 	alg := "ES256" | 		if nil != err { | ||||||
| 	switch privkey.(type) { | 			http.Error(w, "Bad Request: invalid json", http.StatusBadRequest) | ||||||
| 	case *rsa.PrivateKey: | 			return | ||||||
| 		alg = "RS256" |  | ||||||
| 		} | 		} | ||||||
| 	protected := fmt.Sprintf(`{"typ":"JWT","alg":%q,"kid":"%s"}`, alg, thumbprint) | 		defer r.Body.Close() | ||||||
|  | 
 | ||||||
|  | 		// TODO better, JSON error messages | ||||||
|  | 		if _, ok := tok["d"]; ok { | ||||||
|  | 			http.Error(w, "Bad Request: private key", http.StatusBadRequest) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		kty, _ := tok["kty"].(string) | ||||||
|  | 		switch kty { | ||||||
|  | 		case "EC": | ||||||
|  | 			postEC(jwksPrefix, tok, w, r) | ||||||
|  | 		case "RSA": | ||||||
|  | 			postRSA(jwksPrefix, tok, w, r) | ||||||
|  | 		default: | ||||||
|  | 			http.Error(w, "Bad Request: only EC and RSA keys are supported", http.StatusBadRequest) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/access_token", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		log.Printf("%s %s\n", r.Method, r.URL.Path) | ||||||
|  | 		_, _, token := GenToken(getBaseURL(r), priv, r.URL.Query()) | ||||||
|  | 		fmt.Fprintf(w, token) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/authorization_header", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		log.Printf("%s %s\n", r.Method, r.URL.Path) | ||||||
|  | 
 | ||||||
|  | 		var header string | ||||||
|  | 		headers, _ := r.URL.Query()["header"] | ||||||
|  | 		if 0 == len(headers) { | ||||||
|  | 			header = "Authorization" | ||||||
|  | 		} else { | ||||||
|  | 			header = headers[0] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var prefix string | ||||||
|  | 		prefixes, _ := r.URL.Query()["prefix"] | ||||||
|  | 		if 0 == len(prefixes) { | ||||||
|  | 			prefix = "Bearer " | ||||||
|  | 		} else { | ||||||
|  | 			prefix = prefixes[0] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		_, _, token := GenToken(getBaseURL(r), priv, r.URL.Query()) | ||||||
|  | 		fmt.Fprintf(w, "%s: %s%s", header, prefix, token) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/key.jwk.json", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		log.Printf("%s %s", r.Method, r.URL.Path) | ||||||
|  | 		fmt.Fprintf(w, `{ "kty": "EC" , "crv": %q , "d": %q , "x": %q , "y": %q , "ext": true , "key_ops": ["sign"] }`, jwk.Crv, jwk.D, jwk.X, jwk.Y) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		baseURL := getBaseURL(r) | ||||||
|  | 		log.Printf("%s %s\n", r.Method, r.URL.Path) | ||||||
|  | 		fmt.Fprintf(w, `{ "issuer": "%s", "jwks_uri": "%s/.well-known/jwks.json" }`, baseURL, baseURL) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		log.Printf("%s %s %s", r.Method, r.Host, r.URL.Path) | ||||||
|  | 		parts := strings.Split(r.Host, ".") | ||||||
|  | 		kid := parts[0] | ||||||
|  | 
 | ||||||
|  | 		b, err := ioutil.ReadFile(filepath.Join(jwksPrefix, strings.ToLower(kid)+".jwk.json")) | ||||||
|  | 		if nil != err { | ||||||
|  | 			//http.Error(w, "Not Found", http.StatusNotFound) | ||||||
|  | 			jwkstr := fmt.Sprintf( | ||||||
|  | 				`{ "keys": [ { "kty": "EC" , "crv": %q , "x": %q , "y": %q , "kid": %q , "ext": true , "key_ops": ["verify"] , "exp": %s } ] }`, | ||||||
|  | 				jwk.Crv, jwk.X, jwk.Y, thumbprint, strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10), | ||||||
|  | 			) | ||||||
|  | 			fmt.Println(jwkstr) | ||||||
|  | 			fmt.Fprintf(w, jwkstr) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		tok := &PublicJWK{} | ||||||
|  | 		err = json.Unmarshal(b, tok) | ||||||
|  | 		if nil != err { | ||||||
|  | 			// TODO delete the bad file? | ||||||
|  | 			http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		jwkstr := fmt.Sprintf( | ||||||
|  | 			`{ "keys": [ { "kty": "EC", "crv": %q, "x": %q, "y": %q, "kid": %q,`+ | ||||||
|  | 				` "ext": true, "key_ops": ["verify"], "exp": %s } ] }`, | ||||||
|  | 			tok.Crv, tok.X, tok.Y, tok.KeyID, strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10), | ||||||
|  | 		) | ||||||
|  | 		fmt.Println(jwkstr) | ||||||
|  | 		fmt.Fprintf(w, jwkstr) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseExp(exp string) (int, error) { | ||||||
|  | 	if "" == exp { | ||||||
|  | 		exp = "15m" | ||||||
|  | 	} | ||||||
|  | 	mult := 1 | ||||||
|  | 	switch exp[len(exp)-1] { | ||||||
|  | 	case 'w': | ||||||
|  | 		mult *= 7 | ||||||
|  | 		fallthrough | ||||||
|  | 	case 'd': | ||||||
|  | 		mult *= 24 | ||||||
|  | 		fallthrough | ||||||
|  | 	case 'h': | ||||||
|  | 		mult *= 60 | ||||||
|  | 		fallthrough | ||||||
|  | 	case 'm': | ||||||
|  | 		mult *= 60 | ||||||
|  | 		fallthrough | ||||||
|  | 	case 's': | ||||||
|  | 		// no fallthrough | ||||||
|  | 	default: | ||||||
|  | 		// could be 'k' or 'z', but we assume its empty | ||||||
|  | 		exp += "s" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	num, err := strconv.Atoi(exp[:len(exp)-1]) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return num * mult, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func postEC(jwksPrefix string, tok map[string]interface{}, w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	crv, ok := tok["crv"].(string) | ||||||
|  | 	if 5 != len(crv) || "P-" != crv[:2] { | ||||||
|  | 		http.Error(w, "Bad Request: bad curve", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	x, ok := tok["x"].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		http.Error(w, "Bad Request: missing 'x'", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	y, ok := tok["y"].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		http.Error(w, "Bad Request: missing 'y'", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	thumbprintable := []byte( | ||||||
|  | 		fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, crv, x, y), | ||||||
|  | 	) | ||||||
|  | 	alg := crv[2:] | ||||||
|  | 
 | ||||||
|  | 	var thumb []byte | ||||||
|  | 	switch alg { | ||||||
|  | 	case "256": | ||||||
|  | 		hash := sha256.Sum256(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	case "384": | ||||||
|  | 		hash := sha512.Sum384(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	case "521": | ||||||
|  | 		fallthrough | ||||||
|  | 	case "512": | ||||||
|  | 		hash := sha512.Sum512(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	default: | ||||||
|  | 		http.Error(w, "Bad Request: bad key length or curve", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	kid := base64.RawURLEncoding.EncodeToString(thumb) | ||||||
|  | 	if kid2, _ := tok["kid"].(string); "" != kid2 && kid != kid2 { | ||||||
|  | 		http.Error(w, "Bad Request: kid should be "+kid, http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub := []byte(fmt.Sprintf( | ||||||
|  | 		`{"crv":%q,"kid":%q,"kty":"EC","x":%q,"y":%q}`, crv, kid, x, y, | ||||||
|  | 	)) | ||||||
|  | 
 | ||||||
|  | 	// TODO allow posting at the top-level? | ||||||
|  | 	// TODO support a group of keys by PPID | ||||||
|  | 	// (right now it's only by KID) | ||||||
|  | 	if !strings.HasPrefix(r.Host, strings.ToLower(kid)+".") { | ||||||
|  | 		http.Error(w, "Bad Request: prefix should be "+kid, http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := ioutil.WriteFile( | ||||||
|  | 		filepath.Join(jwksPrefix, strings.ToLower(kid)+".jwk.json"), | ||||||
|  | 		pub, | ||||||
|  | 		0644, | ||||||
|  | 	); nil != err { | ||||||
|  | 		fmt.Println("can't write file") | ||||||
|  | 		http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	baseURL := getBaseURL(r) | ||||||
|  | 	w.Write([]byte(fmt.Sprintf( | ||||||
|  | 		`{ "iss":%q, "jwks_url":%q }`, baseURL+"/", baseURL+"/.well-known/jwks.json", | ||||||
|  | 	))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func postRSA(jwksPrefix string, tok map[string]interface{}, w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	e, ok := tok["e"].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		http.Error(w, "Bad Request: missing 'e'", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	n, ok := tok["n"].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		http.Error(w, "Bad Request: missing 'n'", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	thumbprintable := []byte( | ||||||
|  | 		fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, e, n), | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var thumb []byte | ||||||
|  | 	// TODO handle bit lengths well | ||||||
|  | 	switch 3 * (len(n) / 4.0) { | ||||||
|  | 	case 256: | ||||||
|  | 		hash := sha256.Sum256(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	case 384: | ||||||
|  | 		hash := sha512.Sum384(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	case 512: | ||||||
|  | 		hash := sha512.Sum512(thumbprintable) | ||||||
|  | 		thumb = hash[:] | ||||||
|  | 	default: | ||||||
|  | 		http.Error(w, "Bad Request: only standard RSA key lengths (2048, 3072, 4096) are supported", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	kid := base64.RawURLEncoding.EncodeToString(thumb) | ||||||
|  | 	if kid2, _ := tok["kid"].(string); "" != kid2 && kid != kid2 { | ||||||
|  | 		http.Error(w, "Bad Request: kid should be "+kid, http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub := []byte(fmt.Sprintf( | ||||||
|  | 		`{"e":%q,"kid":%q,"kty":"EC","n":%q}`, e, kid, n, | ||||||
|  | 	)) | ||||||
|  | 
 | ||||||
|  | 	// TODO allow posting at the top-level? | ||||||
|  | 	// TODO support a group of keys by PPID | ||||||
|  | 	// (right now it's only by KID) | ||||||
|  | 	if !strings.HasPrefix(r.Host, strings.ToLower(kid)+".") { | ||||||
|  | 		http.Error(w, "Bad Request: prefix should be "+kid, http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := ioutil.WriteFile( | ||||||
|  | 		filepath.Join(jwksPrefix, strings.ToLower(kid)+".jwk.json"), | ||||||
|  | 		pub, | ||||||
|  | 		0644, | ||||||
|  | 	); nil != err { | ||||||
|  | 		fmt.Println("can't write file") | ||||||
|  | 		http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	baseURL := getBaseURL(r) | ||||||
|  | 	w.Write([]byte(fmt.Sprintf( | ||||||
|  | 		`{ "iss":%q, "jwks_url":%q }`, baseURL+"/", baseURL+"/.well-known/jwks.json", | ||||||
|  | 	))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GenToken(host string, priv *ecdsa.PrivateKey, query url.Values) (string, string, string) { | ||||||
|  | 	thumbprint := thumbprintKey(&priv.PublicKey) | ||||||
|  | 	protected := fmt.Sprintf(`{"typ":"JWT","alg":"ES256","kid":"%s"}`, thumbprint) | ||||||
| 	protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected)) | 	protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected)) | ||||||
| 
 | 
 | ||||||
| 	exp, err := time.ParseDuration(query.Get("exp")) | 	exp, err := parseExp(query.Get("exp")) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		// cryptic error code | 		// cryptic error code | ||||||
| 		// TODO propagate error | 		// TODO propagate error | ||||||
| @ -112,25 +367,12 @@ func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (strin | |||||||
| 
 | 
 | ||||||
| 	payload := fmt.Sprintf( | 	payload := fmt.Sprintf( | ||||||
| 		`{"iss":"%s/","sub":"dummy","exp":%s}`, | 		`{"iss":"%s/","sub":"dummy","exp":%s}`, | ||||||
| 		host, strconv.FormatInt(time.Now().Add(exp*time.Second).Unix(), 10), | 		host, strconv.FormatInt(time.Now().Add(time.Duration(exp)*time.Second).Unix(), 10), | ||||||
| 	) | 	) | ||||||
| 	payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload)) | 	payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload)) | ||||||
| 
 | 
 | ||||||
| 	hash := sha256.Sum256([]byte(fmt.Sprintf(`%s.%s`, protected64, payload64))) | 	hash := sha256.Sum256([]byte(fmt.Sprintf(`%s.%s`, protected64, payload64))) | ||||||
| 	sig := JOSESign(privkey, hash[:]) | 	r, s, _ := ecdsa.Sign(rand.Reader, priv, hash[:]) | ||||||
| 	sig64 := base64.RawURLEncoding.EncodeToString(sig) |  | ||||||
| 	token := fmt.Sprintf("%s.%s.%s\n", protected64, payload64, sig64) |  | ||||||
| 	return protected, payload, token |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func JOSESign(privkey keypairs.PrivateKey, hash []byte) []byte { |  | ||||||
| 	var sig []byte |  | ||||||
| 
 |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		panic("TODO: implement rsa sign") |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		r, s, _ := ecdsa.Sign(rndsrc, k, hash[:]) |  | ||||||
| 	rb := r.Bytes() | 	rb := r.Bytes() | ||||||
| 	for len(rb) < 32 { | 	for len(rb) < 32 { | ||||||
| 		rb = append([]byte{0}, rb...) | 		rb = append([]byte{0}, rb...) | ||||||
| @ -139,34 +381,80 @@ func JOSESign(privkey keypairs.PrivateKey, hash []byte) []byte { | |||||||
| 	for len(rb) < 32 { | 	for len(rb) < 32 { | ||||||
| 		sb = append([]byte{0}, sb...) | 		sb = append([]byte{0}, sb...) | ||||||
| 	} | 	} | ||||||
| 		sig = append(rb, sb...) | 	sig64 := base64.RawURLEncoding.EncodeToString(append(rb, sb...)) | ||||||
| 	} | 	token := fmt.Sprintf(`%s.%s.%s`, protected64, payload64, sig64) | ||||||
| 	return sig | 	return protected, payload, token | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: move to keypairs | func ParseKey(jwk *PrivateJWK) *ecdsa.PrivateKey { | ||||||
|  | 	xb, _ := base64.RawURLEncoding.DecodeString(jwk.X) | ||||||
|  | 	xi := &big.Int{} | ||||||
|  | 	xi.SetBytes(xb) | ||||||
|  | 	yb, _ := base64.RawURLEncoding.DecodeString(jwk.Y) | ||||||
|  | 	yi := &big.Int{} | ||||||
|  | 	yi.SetBytes(yb) | ||||||
|  | 	pub := &ecdsa.PublicKey{ | ||||||
|  | 		Curve: elliptic.P256(), | ||||||
|  | 		X:     xi, | ||||||
|  | 		Y:     yi, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| func JOSEVerify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { | 	db, _ := base64.RawURLEncoding.DecodeString(jwk.D) | ||||||
|  | 	di := &big.Int{} | ||||||
|  | 	di.SetBytes(db) | ||||||
|  | 	priv := &ecdsa.PrivateKey{ | ||||||
|  | 		PublicKey: *pub, | ||||||
|  | 		D:         di, | ||||||
|  | 	} | ||||||
|  | 	return priv | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	switch pub := pubkey.Key().(type) { | func thumbprintKey(pub *ecdsa.PublicKey) string { | ||||||
| 	case *rsa.PublicKey: | 	minpub := []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, "P-256", pub.X, pub.Y)) | ||||||
| 		// TODO keypairs.Size(key) to detect key size ? | 	sha := sha256.Sum256(minpub) | ||||||
| 		//alg := "SHA256" | 	return base64.RawURLEncoding.EncodeToString(sha[:]) | ||||||
| 		// TODO: this hasn't been tested yet |  | ||||||
| 		if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err { |  | ||||||
| 			return false |  | ||||||
| } | } | ||||||
| 		return true | 
 | ||||||
| 	case *ecdsa.PublicKey: | func issueNonce(w http.ResponseWriter, r *http.Request) { | ||||||
| 		r := &big.Int{} | 	b := make([]byte, 16) | ||||||
| 		r.SetBytes(sig[0:32]) | 	_, _ = rand.Read(b) | ||||||
| 		s := &big.Int{} | 	nonce := base64.RawURLEncoding.EncodeToString(b) | ||||||
| 		s.SetBytes(sig[32:]) | 	nonces[nonce] = time.Now().Unix() | ||||||
| 		fmt.Println("debug: sig len:", len(sig)) | 
 | ||||||
| 		fmt.Println("debug: r, s:", r, s) | 	w.Header().Set("Replay-Nonce", nonce) | ||||||
| 		return ecdsa.Verify(pub, hash, r, s) | } | ||||||
| 	default: | 
 | ||||||
| 		panic("impossible condition: non-rsa/non-ecdsa key") | func requireNonce(next http.HandlerFunc) http.HandlerFunc { | ||||||
| 		return false | 	return func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		nonce := r.Header.Get("Replay-Nonce") | ||||||
|  | 		// TODO expire nonces every so often | ||||||
|  | 		t := nonces[nonce] | ||||||
|  | 		if 0 == t { | ||||||
|  | 			http.Error( | ||||||
|  | 				w, | ||||||
|  | 				`{ "error": "invalid or expired nonce", "error_code": "ENONCE" }`, | ||||||
|  | 				http.StatusBadRequest, | ||||||
|  | 			) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		delete(nonces, nonce) | ||||||
|  | 		issueNonce(w, r) | ||||||
|  | 
 | ||||||
|  | 		next(w, r) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func getBaseURL(r *http.Request) string { | ||||||
|  | 	var scheme string | ||||||
|  | 	if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { | ||||||
|  | 		scheme = "https:" | ||||||
|  | 	} else { | ||||||
|  | 		scheme = "http:" | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf( | ||||||
|  | 		"%s//%s", | ||||||
|  | 		scheme, | ||||||
|  | 		r.Host, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,476 +1,12 @@ | |||||||
| package mockid | package mockid | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/elliptic" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"log" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httptest" |  | ||||||
| 	"net/url" |  | ||||||
| 	"os" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 	"time" | ||||||
| 	"git.coolaj86.com/coolaj86/go-mockid/xkeypairs" |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| 	//keypairs "github.com/big-squid/go-keypairs" | 	//keypairs "github.com/big-squid/go-keypairs" | ||||||
| 	//"github.com/big-squid/go-keypairs/keyfetch/uncached" | 	//"github.com/big-squid/go-keypairs/keyfetch/uncached" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var srv *httptest.Server | func TestTest(t *testing.T) { | ||||||
| 
 | 	t.Fatal("no test") | ||||||
| type TestReader struct{} |  | ||||||
| 
 |  | ||||||
| func (TestReader) Read(p []byte) (n int, err error) { |  | ||||||
| 	return mathrand.Read(p) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var testrnd = TestReader{} |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	xkeypairs.RandomReader = testrnd |  | ||||||
| 	rndsrc = testrnd |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestMain(m *testing.M) { |  | ||||||
| 	mathrand.Seed(0) // Predictable results |  | ||||||
| 
 |  | ||||||
| 	os.Setenv("SALT", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") |  | ||||||
| 	jwksPrefix := "public-jwks" |  | ||||||
| 	err := os.MkdirAll(jwksPrefix, 0755) |  | ||||||
| 	if nil != err { |  | ||||||
| 		fmt.Fprintf(os.Stderr, "couldn't write %q: %s", jwksPrefix, err) |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	privkey, _ := ecdsa.GenerateKey(elliptic.P256(), rndsrc) |  | ||||||
| 	mux := Route(jwksPrefix, privkey) |  | ||||||
| 
 |  | ||||||
| 	srv = httptest.NewServer(mux) |  | ||||||
| 
 |  | ||||||
| 	//fs := http.FileServer(http.Dir("public")) |  | ||||||
| 	//http.Handle("/", fs) |  | ||||||
| 
 |  | ||||||
| 	os.Exit(m.Run()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //func TestSelfSignWithoutExp(t *testing.T) |  | ||||||
| //func TestSelfSignWithJTIWithoutExp(t *testing.T) |  | ||||||
| 
 |  | ||||||
| func TestVerifyExpired(t *testing.T) { |  | ||||||
| 	jwt := "eyJfc2VlZCI6LTEzMDY3NDU1MDQxNDQsImFsZyI6IlJTMjU2IiwiandrIjp7ImUiOiJBUUFCIiwia2lkIjoiSEZ4ZTlGV1dVc2N3bjltaVozSXNJeWMwMjMtbEJ1UmtvOEJpVV9IRG9KOCIsImt0eSI6IlJTQSIsIm4iOiJ2NUZkSTdYaC0wekxWVEVQZl94ekdIUVpDcEZ2MWR2N2h3eHhrVjctYmxpYmt6LXIxUG9lZ3lQYzFXMjZlWFBvd0xQQXQ3a3dHQnVOdjdMVjh5MEtvMkxOZklaXzRILW54SkJPaWIybXlHOVVfQ29WRDBiM3NBWTdmcDd2QlV1bTBXYVM4R3hZOGtYU0ZOS0VTY0NDNVBpSmFyblNISk1PcUdIVm51YmpsSjl5c1NyNmNsaGpxc0R4dU9qOHpxamF2MUFxek1STWVpRl9CREJsOUFoUGNZSHpHN0JtaXB5UEo2XzBwdWNLTi0tUDZDRk92d05SVGx2ek41RmlRM3VHcy1fMHcwQzVMZWJ6N21BNmJNTFdXc0tRRFBvb3cxallCWHJKdVF1WkZoSmxLMmdidm9ZcV85dWhfLUM1Z3pPZnR4UHBCNnhtY3RfelVaeUdwUUxnQlEiLCJ1c2UiOiJzaWcifSwidHlwIjoiSldUIn0.eyJleHAiOjE1OTY2MTQ3NTYsInN1YiI6ImJhbmFuYXMifQ.qHpzlglOfZMzE3CTNAUXld_wC62JTAJuoQfMaNeFa-XPtYB2Maj8_w3YmRZg_q5S6y9ToCmZ8nWd1kuMheA5qBKOUQeQH47Jts5zWLd0UBckIHo5lK4mk0bUWuiNgr7c9DY6k1DIdFaavyWCXbhFwG0X83qlMhQlPh02dDpCuU78Nn2hF3mZETQKpBIVESYtfeU1Xy3OU_am0kwcN2klLcdweOcrLx_ONfcvAGY3KiIdFiz0ViySAsQ39BiSSvoDYqOOOi41Hky67bnyZQOdalQC_95McTeXApzmGXRUE74Gj-S8c9e5it5d4QZLPaQ1JHzUKz1s7TPvThIn58NA-g" |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/verify") |  | ||||||
| 
 |  | ||||||
| 	req := &http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Header: http.Header{}, |  | ||||||
| 	} |  | ||||||
| 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt)) |  | ||||||
| 	res, err := client.Do(req) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 == res.StatusCode { |  | ||||||
| 		log.Printf(string(data)) |  | ||||||
| 		t.Error(fmt.Errorf("did not expect successful status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestVerifySelfSignedJWT(t *testing.T) { |  | ||||||
| 	jwt := "eyJfc2VlZCI6LTEzMDY3NDU1MDQxNDQsImFsZyI6IlJTMjU2IiwiandrIjp7ImUiOiJBUUFCIiwia2lkIjoiSEZ4ZTlGV1dVc2N3bjltaVozSXNJeWMwMjMtbEJ1UmtvOEJpVV9IRG9KOCIsImt0eSI6IlJTQSIsIm4iOiJ2NUZkSTdYaC0wekxWVEVQZl94ekdIUVpDcEZ2MWR2N2h3eHhrVjctYmxpYmt6LXIxUG9lZ3lQYzFXMjZlWFBvd0xQQXQ3a3dHQnVOdjdMVjh5MEtvMkxOZklaXzRILW54SkJPaWIybXlHOVVfQ29WRDBiM3NBWTdmcDd2QlV1bTBXYVM4R3hZOGtYU0ZOS0VTY0NDNVBpSmFyblNISk1PcUdIVm51YmpsSjl5c1NyNmNsaGpxc0R4dU9qOHpxamF2MUFxek1STWVpRl9CREJsOUFoUGNZSHpHN0JtaXB5UEo2XzBwdWNLTi0tUDZDRk92d05SVGx2ek41RmlRM3VHcy1fMHcwQzVMZWJ6N21BNmJNTFdXc0tRRFBvb3cxallCWHJKdVF1WkZoSmxLMmdidm9ZcV85dWhfLUM1Z3pPZnR4UHBCNnhtY3RfelVaeUdwUUxnQlEiLCJ1c2UiOiJzaWcifSwidHlwIjoiSldUIn0.eyJleHAiOjE1OTY2MTQ3NTYsInN1YiI6ImJhbmFuYXMifQ.qHpzlglOfZMzE3CTNAUXld_wC62JTAJuoQfMaNeFa-XPtYB2Maj8_w3YmRZg_q5S6y9ToCmZ8nWd1kuMheA5qBKOUQeQH47Jts5zWLd0UBckIHo5lK4mk0bUWuiNgr7c9DY6k1DIdFaavyWCXbhFwG0X83qlMhQlPh02dDpCuU78Nn2hF3mZETQKpBIVESYtfeU1Xy3OU_am0kwcN2klLcdweOcrLx_ONfcvAGY3KiIdFiz0ViySAsQ39BiSSvoDYqOOOi41Hky67bnyZQOdalQC_95McTeXApzmGXRUE74Gj-S8c9e5it5d4QZLPaQ1JHzUKz1s7TPvThIn58NA-g" |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/verify?exp=false") |  | ||||||
| 
 |  | ||||||
| 	req := &http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		//Body:   ioutil.NopCloser(bytes.NewReader(jws)), |  | ||||||
| 		Header: http.Header{}, |  | ||||||
| 	} |  | ||||||
| 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt)) |  | ||||||
| 	res, err := client.Do(req) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		log.Printf(string(data)) |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	log.Printf("TODO: verify, and verify non-self-signed") |  | ||||||
| 	log.Printf(string(data)) |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestSelfSign(t *testing.T) { |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	//urlstr, _ := url.Parse(srv.URL + "/debug/jose.jws.json") |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/jose.jws.jwt") |  | ||||||
| 
 |  | ||||||
| 	//fmt.Println("URL:", srv.URL, urlstr) |  | ||||||
| 	tokenRequest := []byte(`{"seed":"test","header":{"_jwk":true},"claims":{"sub":"bananas","exp":"10m"}}`) |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader(tokenRequest)), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	log.Printf("TODO: verify, and verify non-self-signed") |  | ||||||
| 	log.Printf(string(data)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestGenerateJWK(t *testing.T) { |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/private.jwk.json") |  | ||||||
| 	//fmt.Println("URL:", srv.URL, urlstr) |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jwk := map[string]string{} |  | ||||||
| 	err = json.Unmarshal(data, &jwk) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" == jwk["d"] { |  | ||||||
| 		t.Fatal("Missing key 'd' from supposed private key") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key, err := keypairs.ParsePrivateKey(data) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch key.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is RSA") |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is EC") |  | ||||||
| 	default: |  | ||||||
| 		t.Fatal(errors.New("impossible key type")) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	//fmt.Printf("%#v\n", jwk) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestGenWithSeed(t *testing.T) { |  | ||||||
| 	// Key A |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/private.jwk.json") |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dataA, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// See https://github.com/square/go-jose/issues/189 |  | ||||||
| 	for i := 0; i < 8; i++ { |  | ||||||
| 		// Key B |  | ||||||
| 		client = srv.Client() |  | ||||||
| 		urlstr, _ = url.Parse(srv.URL + "/debug/private.jwk.json") |  | ||||||
| 		res, err = client.Do(&http.Request{ |  | ||||||
| 			Method: "POST", |  | ||||||
| 			URL:    urlstr, |  | ||||||
| 			Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))), |  | ||||||
| 		}) |  | ||||||
| 		if nil != err { |  | ||||||
| 			//t.Fatal(err) |  | ||||||
| 			t.Error(err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		if 200 != res.StatusCode { |  | ||||||
| 			t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		dataB, err := ioutil.ReadAll(res.Body) |  | ||||||
| 		if nil != err { |  | ||||||
| 			//t.Fatal(err) |  | ||||||
| 			t.Error(err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if '{' != dataA[0] || len(dataA) < 100 || string(dataA) != string(dataB) { |  | ||||||
| 			log.Println(string(dataA)) |  | ||||||
| 			log.Println(string(dataB)) |  | ||||||
| 			t.Error(errors.New("keys with identical seeds should be identical")) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestGenWithRand(t *testing.T) { |  | ||||||
| 	// Key A |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/private.jwk.json") |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":""}`))), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dataA, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Key B |  | ||||||
| 	client = srv.Client() |  | ||||||
| 	urlstr, _ = url.Parse(srv.URL + "/debug/private.jwk.json") |  | ||||||
| 	res, err = client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":""}`))), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dataB, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if string(dataA) == string(dataB) { |  | ||||||
| 		t.Error(errors.New("keys with identical seeds should yield identical keys")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestGeneratePEM(t *testing.T) { |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/priv.pem") |  | ||||||
| 	//fmt.Println("URL:", srv.URL, urlstr) |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key, err := xkeypairs.ParsePEMPrivateKey(data) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch key.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is RSA") |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is EC") |  | ||||||
| 	default: |  | ||||||
| 		t.Fatal(errors.New("impossible key type")) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestPublicJWKWithKey(t *testing.T) { |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/public.jwk.json") |  | ||||||
| 	//fmt.Println("URL:", srv.URL, urlstr) |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"key":"{\"crv\":\"P-256\",\"d\":\"s0YhjGUJpp6OvyuNS_4igrc7ddDZy5N2ANxoQm7E5sc\",\"kty\":\"EC\",\"x\":\"hPsE4OMhpd2TvrhjDgr1BhF-L1n4O-gPm1flwTh5kzo\",\"y\":\"BWZ1naEJuNOdnQ4HmbHavqdLKxoj77Fu8mkJPjSuh54\"}"}`))), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jwk := map[string]string{} |  | ||||||
| 	err = json.Unmarshal(data, &jwk) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" != jwk["d"] { |  | ||||||
| 		t.Fatal("Has private key 'd' from supposed public key") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "hPsE4OMhpd2TvrhjDgr1BhF-L1n4O-gPm1flwTh5kzo" != jwk["x"] { |  | ||||||
| 		t.Fatal("Missing public key 'x' or 'e' from supposed public key") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key, err := keypairs.ParsePublicKey(data) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch key.Key().(type) { |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is EC") |  | ||||||
| 	default: |  | ||||||
| 		t.Fatal(errors.New("impossible key type")) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestPublicPEMWithSeed(t *testing.T) { |  | ||||||
| 	client := srv.Client() |  | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/debug/pub.pem") |  | ||||||
| 	//fmt.Println("URL:", srv.URL, urlstr) |  | ||||||
| 	res, err := client.Do(&http.Request{ |  | ||||||
| 		Method: "POST", |  | ||||||
| 		URL:    urlstr, |  | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))), |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if 200 != res.StatusCode { |  | ||||||
| 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data, err := ioutil.ReadAll(res.Body) |  | ||||||
| 	if nil != err { |  | ||||||
| 		//t.Fatal(err) |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key, err := keypairs.ParsePublicKey(data) |  | ||||||
| 	if nil != err { |  | ||||||
| 		t.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch key.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is RSA") |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		// no-op |  | ||||||
| 		//log.Println("is EC") |  | ||||||
| 	default: |  | ||||||
| 		t.Fatal(errors.New("impossible key type")) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,64 +0,0 @@ | |||||||
| package mockid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"net/http" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| //var nonces map[string]int64 |  | ||||||
| //var nonCh chan string |  | ||||||
| var nonces sync.Map |  | ||||||
| 
 |  | ||||||
| func issueNonce(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	b := make([]byte, 16) |  | ||||||
| 	_, _ = rand.Read(b) |  | ||||||
| 	nonce := base64.RawURLEncoding.EncodeToString(b) |  | ||||||
| 	//nonCh <- nonce |  | ||||||
| 	nonces.Store(nonce, time.Now()) |  | ||||||
| 
 |  | ||||||
| 	w.Header().Set("Replay-Nonce", nonce) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func requireNonce(next http.HandlerFunc) http.HandlerFunc { |  | ||||||
| 	return func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		nonce := r.Header.Get("Replay-Nonce") |  | ||||||
| 		// TODO expire nonces every so often |  | ||||||
| 		//t := nonces[nonce] |  | ||||||
| 
 |  | ||||||
| 		if !useNonce(nonce) { |  | ||||||
| 			http.Error( |  | ||||||
| 				w, |  | ||||||
| 				`{ "error": "invalid or expired nonce", "error_code": "ENONCE" }`, |  | ||||||
| 				http.StatusBadRequest, |  | ||||||
| 			) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		issueNonce(w, r) |  | ||||||
| 		next(w, r) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func checkNonce(nonce string) bool { |  | ||||||
| 	var t time.Time |  | ||||||
| 	tmp, ok := nonces.Load(nonce) |  | ||||||
| 	if ok { |  | ||||||
| 		t = tmp.(time.Time) |  | ||||||
| 	} |  | ||||||
| 	if ok && time.Now().Sub(t) <= 15*time.Minute { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func useNonce(nonce string) bool { |  | ||||||
| 	if checkNonce(nonce) { |  | ||||||
| 		//delete(nonces, nonce) |  | ||||||
| 		nonces.Delete(nonce) |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
							
								
								
									
										1160
									
								
								mockid/route.go
									
									
									
									
									
								
							
							
						
						
									
										1160
									
								
								mockid/route.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,46 +0,0 @@ | |||||||
| package xkeypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (jws *JWS) DecodeComponents() error { |  | ||||||
| 	protected, err := base64.RawURLEncoding.DecodeString(jws.Protected) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return errors.New("invalid JWS header base64Url encoding") |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(protected), &jws.Header); nil != err { |  | ||||||
| 		return errors.New("invalid JWS header") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	payload, err := base64.RawURLEncoding.DecodeString(jws.Payload) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return errors.New("invalid JWS payload base64Url encoding") |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(payload), &jws.Claims); nil != err { |  | ||||||
| 		return errors.New("invalid JWS claims") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| func Decode(msg string) (*JWS, error) { |  | ||||||
| 	jws := &JWS{} |  | ||||||
| 
 |  | ||||||
| 	decoder := json.NewDecoder(r.Body) |  | ||||||
| 	err := decoder.Decode(jws) |  | ||||||
| 	return jws, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Unmarshal(msg string) (*JWS, error) { |  | ||||||
| 	jws := &JWS{} |  | ||||||
| 
 |  | ||||||
| 	if err := json.Unmarshal([]byte(msg), jws); nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return jws, nil |  | ||||||
| } |  | ||||||
| */ |  | ||||||
| @ -1,70 +0,0 @@ | |||||||
| package xkeypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"math/big" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type JWK interface { |  | ||||||
| 	marshalJWK() ([]byte, error) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ECJWK struct { |  | ||||||
| 	KeyID string   `json:"kid,omitempty"` |  | ||||||
| 	Curve string   `json:"crv"` |  | ||||||
| 	X     string   `json:"x"` |  | ||||||
| 	Y     string   `json:"y"` |  | ||||||
| 	Use   []string `json:"use,omitempty"` |  | ||||||
| 	Seed  string   `json:"_seed,omitempty"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *ECJWK) marshalJWK() ([]byte, error) { |  | ||||||
| 	return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, k.Curve, k.X, k.Y)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type RSAJWK struct { |  | ||||||
| 	KeyID string   `json:"kid,omitempty"` |  | ||||||
| 	Exp   string   `json:"e"` |  | ||||||
| 	N     string   `json"n"` |  | ||||||
| 	Use   []string `json:"use,omitempty"` |  | ||||||
| 	Seed  string   `json:"_seed,omitempty"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *RSAJWK) marshalJWK() ([]byte, error) { |  | ||||||
| 	return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, k.Exp, k.N)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ToPublicJWK(pubkey keypairs.PublicKey) JWK { |  | ||||||
| 	switch k := pubkey.Key().(type) { |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		return ECToPublicJWK(k) |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		return RSAToPublicJWK(k) |  | ||||||
| 	default: |  | ||||||
| 		panic(errors.New("impossible key type")) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ECToPublicJWK will output the most minimal version of an EC JWK (no key id, no "use" flag, nada) |  | ||||||
| func ECToPublicJWK(k *ecdsa.PublicKey) *ECJWK { |  | ||||||
| 	return &ECJWK{ |  | ||||||
| 		Curve: k.Curve.Params().Name, |  | ||||||
| 		X:     base64.RawURLEncoding.EncodeToString(k.X.Bytes()), |  | ||||||
| 		Y:     base64.RawURLEncoding.EncodeToString(k.Y.Bytes()), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RSAToPublicJWK will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada) |  | ||||||
| func RSAToPublicJWK(p *rsa.PublicKey) *RSAJWK { |  | ||||||
| 	return &RSAJWK{ |  | ||||||
| 		Exp: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()), |  | ||||||
| 		N:   base64.RawURLEncoding.EncodeToString(p.N.Bytes()), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @ -1,173 +0,0 @@ | |||||||
| package xkeypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"math/big" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // MarshalPEMPublicKey outputs the given public key as JWK |  | ||||||
| func MarshalPEMPublicKey(pubkey crypto.PublicKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPublicKey(pubkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(block), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalDERPublicKey outputs the given public key as JWK |  | ||||||
| func MarshalDERPublicKey(pubkey crypto.PublicKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPublicKey(pubkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return block.Bytes, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // marshalDERPublicKey outputs the given public key as JWK |  | ||||||
| func marshalDERPublicKey(pubkey crypto.PublicKey) (*pem.Block, error) { |  | ||||||
| 
 |  | ||||||
| 	var der []byte |  | ||||||
| 	var typ string |  | ||||||
| 	var err error |  | ||||||
| 	switch k := pubkey.(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		der = x509.MarshalPKCS1PublicKey(k) |  | ||||||
| 		typ = "RSA PUBLIC KEY" |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		typ = "PUBLIC KEY" |  | ||||||
| 		der, err = x509.MarshalPKIXPublicKey(k) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		panic("Developer Error: impossible key type") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &pem.Block{ |  | ||||||
| 		Bytes: der, |  | ||||||
| 		Type:  typ, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalJWKPrivateKey outputs the given private key as JWK |  | ||||||
| func MarshalJWKPrivateKey(privkey keypairs.PrivateKey) []byte { |  | ||||||
| 	// thumbprint keys are alphabetically sorted and only include the necessary public parts |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		return MarshalRSAPrivateKey(k) |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		return MarshalECPrivateKey(k) |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(keypairs.ErrInvalidPublicKey) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalDERPrivateKey outputs the given private key as ASN.1 DER |  | ||||||
| func MarshalDERPrivateKey(privkey keypairs.PrivateKey) ([]byte, error) { |  | ||||||
| 	// thumbprint keys are alphabetically sorted and only include the necessary public parts |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		return x509.MarshalPKCS1PrivateKey(k), nil |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		return x509.MarshalECPrivateKey(k) |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(keypairs.ErrInvalidPublicKey) |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func marshalDERPrivateKey(privkey keypairs.PrivateKey) (*pem.Block, error) { |  | ||||||
| 	var typ string |  | ||||||
| 	var bytes []byte |  | ||||||
| 	var err error |  | ||||||
| 
 |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		if 0 == mathrand.Intn(2) { |  | ||||||
| 			typ = "PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalPKCS8PrivateKey(k) |  | ||||||
| 			if nil != err { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			typ = "RSA PRIVATE KEY" |  | ||||||
| 			bytes = x509.MarshalPKCS1PrivateKey(k) |  | ||||||
| 		} |  | ||||||
| 		return &pem.Block{ |  | ||||||
| 			Type:  typ, |  | ||||||
| 			Bytes: bytes, |  | ||||||
| 		}, nil |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		if 0 == mathrand.Intn(2) { |  | ||||||
| 			typ = "PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalPKCS8PrivateKey(k) |  | ||||||
| 		} else { |  | ||||||
| 			typ = "EC PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalECPrivateKey(k) |  | ||||||
| 		} |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return &pem.Block{ |  | ||||||
| 			Type:  typ, |  | ||||||
| 			Bytes: bytes, |  | ||||||
| 		}, nil |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(keypairs.ErrInvalidPublicKey) |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalPEMPrivateKey outputs the given private key as ASN.1 PEM |  | ||||||
| func MarshalPEMPrivateKey(privkey keypairs.PrivateKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPrivateKey(privkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(block), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalECPrivateKey will output the given private key as JWK |  | ||||||
| func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte { |  | ||||||
| 	crv := k.Curve.Params().Name |  | ||||||
| 	d := base64.RawURLEncoding.EncodeToString(k.D.Bytes()) |  | ||||||
| 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) |  | ||||||
| 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf( |  | ||||||
| 		`{"crv":%q,"d":%q,"kty":"EC","x":%q,"y":%q}`, |  | ||||||
| 		crv, d, x, y, |  | ||||||
| 	)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalRSAPrivateKey will output the given private key as JWK |  | ||||||
| func MarshalRSAPrivateKey(pk *rsa.PrivateKey) []byte { |  | ||||||
| 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pk.E)).Bytes()) |  | ||||||
| 	n := base64.RawURLEncoding.EncodeToString(pk.N.Bytes()) |  | ||||||
| 	d := base64.RawURLEncoding.EncodeToString(pk.D.Bytes()) |  | ||||||
| 	p := base64.RawURLEncoding.EncodeToString(pk.Primes[0].Bytes()) |  | ||||||
| 	q := base64.RawURLEncoding.EncodeToString(pk.Primes[1].Bytes()) |  | ||||||
| 	dp := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dp.Bytes()) |  | ||||||
| 	dq := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dq.Bytes()) |  | ||||||
| 	qi := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Qinv.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf( |  | ||||||
| 		`{"d":%q,"dp":%q,"dq":%q,"e":%q,"kty":"RSA","n":%q,"p":%q,"q":%q,"qi":%q}`, |  | ||||||
| 		d, dp, dq, e, n, p, q, qi, |  | ||||||
| 	)) |  | ||||||
| } |  | ||||||
| @ -1,173 +0,0 @@ | |||||||
| package xkeypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // RandomReader may be overwritten for testing |  | ||||||
| var RandomReader io.Reader = rand.Reader |  | ||||||
| 
 |  | ||||||
| //var RandomReader = rand.Reader |  | ||||||
| 
 |  | ||||||
| type JWS struct { |  | ||||||
| 	Header    Object `json:"header"`    // JSON |  | ||||||
| 	Claims    Object `json:"claims"`    // JSON |  | ||||||
| 	Protected string `json:"protected"` // base64 |  | ||||||
| 	Payload   string `json:"payload"`   // base64 |  | ||||||
| 	Signature string `json:"signature"` // base64 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Object = map[string]interface{} |  | ||||||
| 
 |  | ||||||
| // SignClaims adds `typ`, `kid` (or `jwk`), and `alg` in the header and expects claims for `jti`, `exp`, `iss`, and `iat` |  | ||||||
| func SignClaims(privkey keypairs.PrivateKey, header Object, claims Object) (*JWS, error) { |  | ||||||
| 	var randsrc io.Reader = RandomReader |  | ||||||
| 	seed, _ := header["_seed"].(int64) |  | ||||||
| 	if 0 != seed { |  | ||||||
| 		randsrc = mathrand.New(mathrand.NewSource(seed)) |  | ||||||
| 		//delete(header, "_seed") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	protected, err := headerToProtected(keypairs.NewPublicKey(privkey.Public()), header) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	protected64 := base64.RawURLEncoding.EncodeToString(protected) |  | ||||||
| 
 |  | ||||||
| 	payload, err := claimsToPayload(claims) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	payload64 := base64.RawURLEncoding.EncodeToString(payload) |  | ||||||
| 
 |  | ||||||
| 	signable := fmt.Sprintf(`%s.%s`, protected64, payload64) |  | ||||||
| 	hash := sha256.Sum256([]byte(signable)) |  | ||||||
| 
 |  | ||||||
| 	sig := Sign(randsrc, privkey, hash[:]) |  | ||||||
| 	sig64 := base64.RawURLEncoding.EncodeToString(sig) |  | ||||||
| 	//log.Printf("\n(Sign)\nSignable: %s", signable) |  | ||||||
| 	//log.Printf("Hash: %s", hash) |  | ||||||
| 	//log.Printf("Sig: %s", sig64) |  | ||||||
| 
 |  | ||||||
| 	return &JWS{ |  | ||||||
| 		Header:    header, |  | ||||||
| 		Claims:    claims, |  | ||||||
| 		Protected: protected64, |  | ||||||
| 		Payload:   payload64, |  | ||||||
| 		Signature: sig64, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func headerToProtected(pub keypairs.PublicKey, header Object) ([]byte, error) { |  | ||||||
| 	if nil == header { |  | ||||||
| 		header = Object{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Only supporting 2048-bit and P256 keys right now |  | ||||||
| 	// because that's all that's practical and well-supported. |  | ||||||
| 	// No security theatre here. |  | ||||||
| 	alg := "ES256" |  | ||||||
| 	switch pub.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		alg = "RS256" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if selfSign, _ := header["_jwk"].(bool); selfSign { |  | ||||||
| 		delete(header, "_jwk") |  | ||||||
| 		any := Object{} |  | ||||||
| 		_ = json.Unmarshal(keypairs.MarshalJWKPublicKey(pub), &any) |  | ||||||
| 		header["jwk"] = any |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// TODO what are the acceptable values? JWT. JWS? others? |  | ||||||
| 	header["typ"] = "JWT" |  | ||||||
| 	if _, ok := header["jwk"]; !ok { |  | ||||||
| 		thumbprint := keypairs.ThumbprintPublicKey(pub) |  | ||||||
| 		kid, _ := header["kid"].(string) |  | ||||||
| 		if "" != kid && thumbprint != kid { |  | ||||||
| 			return nil, errors.New("'kid' should be the key's thumbprint") |  | ||||||
| 		} |  | ||||||
| 		header["kid"] = thumbprint |  | ||||||
| 	} |  | ||||||
| 	header["alg"] = alg |  | ||||||
| 
 |  | ||||||
| 	protected, err := json.Marshal(header) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return protected, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func claimsToPayload(claims Object) ([]byte, error) { |  | ||||||
| 	if nil == claims { |  | ||||||
| 		claims = Object{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jti, _ := claims["jti"].(string) |  | ||||||
| 	exp, _ := claims["exp"].(int64) |  | ||||||
| 	dur, _ := claims["exp"].(string) |  | ||||||
| 	insecure, _ := claims["insecure"].(bool) |  | ||||||
| 
 |  | ||||||
| 	// parse if exp is actually a duration, such as "15m" |  | ||||||
| 	if 0 == exp && "" != dur { |  | ||||||
| 		s, err := time.ParseDuration(dur) |  | ||||||
| 		// TODO s, err := time.ParseDuration(dur) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		exp = time.Now().Add(s * time.Second).Unix() |  | ||||||
| 		claims["exp"] = exp |  | ||||||
| 	} |  | ||||||
| 	if "" == jti && 0 == exp && !insecure { |  | ||||||
| 		return nil, errors.New("token must have jti or exp as to be expirable / cancellable") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return json.Marshal(claims) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func JWSToJWT(jwt *JWS) string { |  | ||||||
| 	return fmt.Sprintf( |  | ||||||
| 		"%s.%s.%s", |  | ||||||
| 		jwt.Protected, |  | ||||||
| 		jwt.Payload, |  | ||||||
| 		jwt.Signature, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Sign(rand io.Reader, privkey keypairs.PrivateKey, hash []byte) []byte { |  | ||||||
| 	var sig []byte |  | ||||||
| 
 |  | ||||||
| 	if len(hash) != 32 { |  | ||||||
| 		panic("only 256-bit hashes for 2048-bit and 256-bit keys are supported") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		sig, _ = rsa.SignPKCS1v15(rand, k, crypto.SHA256, hash) |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		r, s, _ := ecdsa.Sign(rand, k, hash[:]) |  | ||||||
| 		rb := r.Bytes() |  | ||||||
| 		for len(rb) < 32 { |  | ||||||
| 			rb = append([]byte{0}, rb...) |  | ||||||
| 		} |  | ||||||
| 		sb := s.Bytes() |  | ||||||
| 		for len(rb) < 32 { |  | ||||||
| 			sb = append([]byte{0}, sb...) |  | ||||||
| 		} |  | ||||||
| 		sig = append(rb, sb...) |  | ||||||
| 	} |  | ||||||
| 	return sig |  | ||||||
| } |  | ||||||
| @ -1,172 +0,0 @@ | |||||||
| package xkeypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/elliptic" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"math/big" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func VerifyClaims(pubkey keypairs.PublicKey, jws *JWS) (bool, error) { |  | ||||||
| 	seed, _ := jws.Header["_seed"].(int64) |  | ||||||
| 	seedf64, _ := jws.Header["_seed"].(float64) |  | ||||||
| 	kty, _ := jws.Header["_kty"].(string) |  | ||||||
| 	kid, _ := jws.Header["kid"].(string) |  | ||||||
| 	jwkmap, hasJWK := jws.Header["jwk"].(Object) |  | ||||||
| 	//var jwk JWK = nil |  | ||||||
| 
 |  | ||||||
| 	if 0 == seed { |  | ||||||
| 		seed = int64(seedf64) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var pub keypairs.PublicKey = nil |  | ||||||
| 	if hasJWK { |  | ||||||
| 		log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk' thumbprint") |  | ||||||
| 		log.Println("Security TODO: did not check jws.Claims[\"iss\"]") |  | ||||||
| 		kty := jwkmap["kty"] |  | ||||||
| 		var err error |  | ||||||
| 		if "RSA" == kty { |  | ||||||
| 			e, _ := jwkmap["e"].(string) |  | ||||||
| 			n, _ := jwkmap["n"].(string) |  | ||||||
| 			k, _ := (&RSAJWK{ |  | ||||||
| 				Exp: e, |  | ||||||
| 				N:   n, |  | ||||||
| 			}).marshalJWK() |  | ||||||
| 			pub, err = keypairs.ParseJWKPublicKey(k) |  | ||||||
| 			if nil != err { |  | ||||||
| 				return false, err |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			crv, _ := jwkmap["crv"].(string) |  | ||||||
| 			x, _ := jwkmap["x"].(string) |  | ||||||
| 			y, _ := jwkmap["y"].(string) |  | ||||||
| 			k, _ := (&ECJWK{ |  | ||||||
| 				Curve: crv, |  | ||||||
| 				X:     x, |  | ||||||
| 				Y:     y, |  | ||||||
| 			}).marshalJWK() |  | ||||||
| 			pub, err = keypairs.ParseJWKPublicKey(k) |  | ||||||
| 			if nil != err { |  | ||||||
| 				return false, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if "" == kid { |  | ||||||
| 			return false, errors.New("token should have 'kid' or 'jwk' in header") |  | ||||||
| 		} |  | ||||||
| 		if nil == pubkey { |  | ||||||
| 			if 0 == seed { |  | ||||||
| 				return false, errors.New("the debug API requires '_seed' to accompany 'kid'") |  | ||||||
| 			} |  | ||||||
| 			if "" == kty { |  | ||||||
| 				return false, errors.New("the debug API requires '_kty' to accompany '_seed'") |  | ||||||
| 			} |  | ||||||
| 			privkey := genPrivKey(seed, kty) |  | ||||||
| 			pub = keypairs.NewPublicKey(privkey.Public()) |  | ||||||
| 		} else { |  | ||||||
| 			pub = pubkey |  | ||||||
| 		} |  | ||||||
| 		log.Println("Security TODO: did not check jws.Claims[\"kid\"] against thumbprint") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jti, _ := jws.Claims["jti"].(string) |  | ||||||
| 	expf64, _ := jws.Claims["exp"].(float64) |  | ||||||
| 	exp := int64(expf64) |  | ||||||
| 	if 0 == exp { |  | ||||||
| 		if "" == jti { |  | ||||||
| 			return false, errors.New("one of 'jti' or 'exp' must exist for token expiry") |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if time.Now().Unix() > exp { |  | ||||||
| 			return false, fmt.Errorf("token expired at %d (%s)", exp, time.Unix(exp, 0)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	signable := fmt.Sprintf("%s.%s", jws.Protected, jws.Payload) |  | ||||||
| 	hash := sha256.Sum256([]byte(signable)) |  | ||||||
| 	sig, err := base64.RawURLEncoding.DecodeString(jws.Signature) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	//log.Printf("\n(Verify)\nSignable: %s", signable) |  | ||||||
| 	//log.Printf("Hash: %s", hash) |  | ||||||
| 	//log.Printf("Sig: %s", jws.Signature) |  | ||||||
| 
 |  | ||||||
| 	return Verify(pub, hash[:], sig), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Verify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { |  | ||||||
| 
 |  | ||||||
| 	switch pub := pubkey.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		//log.Printf("RSA VERIFY") |  | ||||||
| 		// TODO keypairs.Size(key) to detect key size ? |  | ||||||
| 		//alg := "SHA256" |  | ||||||
| 		// TODO: this hasn't been tested yet |  | ||||||
| 		if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		r := &big.Int{} |  | ||||||
| 		r.SetBytes(sig[0:32]) |  | ||||||
| 		s := &big.Int{} |  | ||||||
| 		s.SetBytes(sig[32:]) |  | ||||||
| 		return ecdsa.Verify(pub, hash, r, s) |  | ||||||
| 	default: |  | ||||||
| 		panic("impossible condition: non-rsa/non-ecdsa key") |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const maxRetry = 16 |  | ||||||
| 
 |  | ||||||
| func genPrivKey(seed int64, kty string) keypairs.PrivateKey { |  | ||||||
| 	var privkey keypairs.PrivateKey |  | ||||||
| 
 |  | ||||||
| 	if "RSA" == kty { |  | ||||||
| 		keylen := 2048 |  | ||||||
| 		privkey, _ = rsa.GenerateKey(nextReader(seed), keylen) |  | ||||||
| 		if 0 != seed { |  | ||||||
| 			for i := 0; i < maxRetry; i++ { |  | ||||||
| 				otherkey, _ := rsa.GenerateKey(nextReader(seed), keylen) |  | ||||||
| 				otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) |  | ||||||
| 				if 0 != otherCmp { |  | ||||||
| 					// There are two possible keys, choose the lesser D value |  | ||||||
| 					// See https://github.com/square/go-jose/issues/189 |  | ||||||
| 					if otherCmp < 0 { |  | ||||||
| 						privkey = otherkey |  | ||||||
| 					} |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 				if maxRetry == i-1 { |  | ||||||
| 					log.Printf("error: coinflip landed on heads %d times", maxRetry) |  | ||||||
| 					// TODO return random / retry error |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		// TODO: EC keys may also suffer the same random problems in the future |  | ||||||
| 		privkey, _ = ecdsa.GenerateKey(elliptic.P256(), nextReader(seed)) |  | ||||||
| 	} |  | ||||||
| 	return privkey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // this shananigans is only for testing and debug API stuff |  | ||||||
| func nextReader(seed int64) io.Reader { |  | ||||||
| 	if 0 == seed { |  | ||||||
| 		return RandomReader |  | ||||||
| 	} |  | ||||||
| 	return mathrand.New(mathrand.NewSource(seed)) |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								public/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								public/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| package-lock.json |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| { "node": true |  | ||||||
| , "browser": true |  | ||||||
| , "globals": { "Promise": true } |  | ||||||
| , "esversion": 8 |  | ||||||
| 
 |  | ||||||
| , "indent": 2 |  | ||||||
| , "onevar": true |  | ||||||
| , "laxbreak": true |  | ||||||
| , "curly": true |  | ||||||
| , "nonbsp": true |  | ||||||
| 
 |  | ||||||
| , "eqeqeq": true |  | ||||||
| , "immed": true |  | ||||||
| , "undef": true |  | ||||||
| , "unused": true |  | ||||||
| } |  | ||||||
| @ -1 +0,0 @@ | |||||||
| dist/ |  | ||||||
| @ -1,8 +0,0 @@ | |||||||
| { |  | ||||||
|   "bracketSpacing": true, |  | ||||||
|   "printWidth": 80, |  | ||||||
|   "singleQuote": true, |  | ||||||
|   "tabWidth": 4, |  | ||||||
|   "trailingComma": "none", |  | ||||||
|   "useTabs": true |  | ||||||
| } |  | ||||||
							
								
								
									
										60
									
								
								public/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								public/app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | // https://developers.google.com/sheets/api/guides/authorizing
 | ||||||
|  | // https://www.googleapis.com/auth/spreadsheets.readonly
 | ||||||
|  | 
 | ||||||
|  | // Scope names
 | ||||||
|  | // https://developers.google.com/drive/api/v3/about-auth
 | ||||||
|  | 
 | ||||||
|  | // Google Sheets API
 | ||||||
|  | // https://developers.google.com/sheets/api/guides/migration#list_spreadsheets_for_the_authenticated_user
 | ||||||
|  | 
 | ||||||
|  | function onSignIn(googleUser) { | ||||||
|  |   var profile = googleUser.getBasicProfile(); | ||||||
|  | 
 | ||||||
|  |   var id_token = googleUser.getAuthResponse().id_token; | ||||||
|  |   var access_token = googleUser.getAuthResponse(); | ||||||
|  |   console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
 | ||||||
|  |   console.log('Name: ' + profile.getName()); | ||||||
|  |   console.log('Image URL: ' + profile.getImageUrl()); | ||||||
|  |   console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
 | ||||||
|  |   console.log('access_token', access_token); | ||||||
|  |   console.log('id_token', id_token); | ||||||
|  |   window | ||||||
|  |     .fetch('https://oauth2.googleapis.com/tokeninfo?id_token=' + id_token) | ||||||
|  |     .then(function(resp) { | ||||||
|  |       return resp.json().then(function(data) { | ||||||
|  |         console.log(data); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   /* | ||||||
|  |   window | ||||||
|  |     .fetch( | ||||||
|  |       "https://www.googleapis.com/drive/v3/files?q=mimeType%3D'application%2Fvnd.google-apps.spreadsheet'", | ||||||
|  |       { | ||||||
|  |         headers: {Authorization: 'Bearer ' + access_token.access_token}, | ||||||
|  |       }, | ||||||
|  |     ) | ||||||
|  |     .then(function(resp) { | ||||||
|  |       return resp.json().then(function(data) { | ||||||
|  |         console.log(data); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   window | ||||||
|  |     .fetch('https://spreadsheets.google.com/feeds/spreadsheets/private/full', { | ||||||
|  |       headers: {Authorization: 'Bearer ' + access_token.access_token}, | ||||||
|  |     }) | ||||||
|  |     .then(function(resp) { | ||||||
|  |       return resp.json().then(function(data) { | ||||||
|  |         console.log(data); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function signOut() { | ||||||
|  |   var auth2 = gapi.auth2.getAuthInstance(); | ||||||
|  |   auth2.signOut().then(function() { | ||||||
|  |     console.log('User signed out.'); | ||||||
|  |   }); | ||||||
|  | } | ||||||
| @ -1,44 +1,11 @@ | |||||||
| <html> |  | ||||||
| 	<head> |  | ||||||
| 		<meta name="google-signin-scope" content="email" /> |  | ||||||
| <meta | <meta | ||||||
|   name="google-signin-client_id" |   name="google-signin-client_id" | ||||||
| 			content="291138637698-9hjbgadgkibuv9j26104aj0bg5bia30j.apps.googleusercontent.com" |   content="128764648444-nk2ss16gmals7rhsk2kj0i0ove0v0tnk.apps.googleusercontent.com" | ||||||
| /> | /> | ||||||
|  | <!-- https://developers.google.com/identity/sign-in/web/sign-in --> | ||||||
|  | <!-- https://developers.google.com/identity/sign-in/web/quick-migration-guide --> | ||||||
|  | <!-- Note: You can also specify your app's client ID with the client_id parameter of the gapi.auth2.init() method. --> | ||||||
| 
 | 
 | ||||||
| 		<style> |  | ||||||
| 			@media (prefers-color-scheme: dark) { |  | ||||||
| 				body { |  | ||||||
| 					background-color: #222; |  | ||||||
| 					color: #aaa; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			body { |  | ||||||
| 				background-color: #222; |  | ||||||
| 				color: #aaa; |  | ||||||
| 			} |  | ||||||
| 			.container { |  | ||||||
| 				padding: 2em; |  | ||||||
| 			} |  | ||||||
| 			.authn-container { |  | ||||||
| 				width: 300px; |  | ||||||
| 				margin: auto; |  | ||||||
| 				border: solid 1px #c0c0c0; |  | ||||||
| 			} |  | ||||||
| 			.authn-container hr { |  | ||||||
| 				width: 80%; |  | ||||||
| 			} |  | ||||||
| 			.link { |  | ||||||
| 				background: none; |  | ||||||
| 				border: none; |  | ||||||
| 				padding: 0; |  | ||||||
| 				text-decoration: underline; |  | ||||||
| 				cursor: pointer; |  | ||||||
| 			} |  | ||||||
| 		</style> |  | ||||||
| 	</head> |  | ||||||
| 	<body> |  | ||||||
| 		<div class="container"> |  | ||||||
| <pre><code> | <pre><code> | ||||||
| <h1>Tokens for Testing</h1> | <h1>Tokens for Testing</h1> | ||||||
| Compatible with | Compatible with | ||||||
| @ -54,7 +21,6 @@ Compatible with | |||||||
| 
 | 
 | ||||||
|   * https://mock.pocketid.app/access_token |   * https://mock.pocketid.app/access_token | ||||||
|   * https://mock.pocketid.app/authorization_header |   * https://mock.pocketid.app/authorization_header | ||||||
|   * https://mock.pocketid.app/inspect_token |  | ||||||
|   * https://xxx.mock.pocketid.app/.well-known/openid-configuration |   * https://xxx.mock.pocketid.app/.well-known/openid-configuration | ||||||
|   * https://xxx.mock.pocketid.app/.well-known/jwks.json |   * https://xxx.mock.pocketid.app/.well-known/jwks.json | ||||||
|   * https://mock.pocketid.app/key.jwk.json |   * https://mock.pocketid.app/key.jwk.json | ||||||
| @ -90,13 +56,6 @@ For example: | |||||||
|   HEADER=$(curl -fL https://mock.pocketid.app/authorization_header) |   HEADER=$(curl -fL https://mock.pocketid.app/authorization_header) | ||||||
|   # Authorization: Bearer <token> |   # Authorization: Bearer <token> | ||||||
| 
 | 
 | ||||||
| <h3>Inspecting the Token</h3> |  | ||||||
| 
 |  | ||||||
| You can see its decoded form at the `inspect_token` endpoint: |  | ||||||
| 
 |  | ||||||
|   curl -fL https://mock.pocketid.app/inspect_token \ |  | ||||||
|     -H "$(curl -fL https://mock.pocketid.app/authorization_header)" |  | ||||||
| 
 |  | ||||||
| <h3>The Token, Decoded</h3> | <h3>The Token, Decoded</h3> | ||||||
| 
 | 
 | ||||||
| The Token will look like this: | The Token will look like this: | ||||||
| @ -154,110 +113,12 @@ You shouldn't use it for automated testing, because it will change, but it looks | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| </code></pre> | </code></pre> | ||||||
| 		</div> |  | ||||||
| 
 | 
 | ||||||
| 		<div class="authn-flow"> | <script src="https://apis.google.com/js/platform.js" async defer></script> | ||||||
| 			<div class="authn-container authn-loading"> |  | ||||||
| 				<center>⌛</center> |  | ||||||
| 			</div> |  | ||||||
| 
 |  | ||||||
| 			<div class="authn-container authn-email"> |  | ||||||
| 				<center> |  | ||||||
| 					<form class="authn-form"> |  | ||||||
| 						<h2>Login</h2> |  | ||||||
| 						<input |  | ||||||
| 							name="username" |  | ||||||
| 							type="email" |  | ||||||
| 							placeholder="email" |  | ||||||
| 							value="coolaj86+noreply@gmail.com" |  | ||||||
| 						/> |  | ||||||
| 						<br /> |  | ||||||
| 						<button type="submit">Continue</button> |  | ||||||
| 					</form> |  | ||||||
| 					<hr /> |  | ||||||
| <div | <div | ||||||
|   class="g-signin2" |   class="g-signin2" | ||||||
| 						data-scope="email" |  | ||||||
|   data-onsuccess="onSignIn" |   data-onsuccess="onSignIn" | ||||||
| 						data-theme="dark" |   data-scope="profile email https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/drive.readonly" | ||||||
| ></div> | ></div> | ||||||
| 					<br /> | <a href="#" onclick="signOut();">Sign out</a> | ||||||
| 				</center> | <script src="app.js"></script> | ||||||
| 			</div> |  | ||||||
| 
 |  | ||||||
| 			<div class="authn-container authn-new-user"> |  | ||||||
| 				<center> |  | ||||||
| 					<h2>Choose Password</h2> |  | ||||||
| 					<form class="authn-form"> |  | ||||||
| 						<input |  | ||||||
| 							name="password" |  | ||||||
| 							type="password" |  | ||||||
| 							placeholder="password" |  | ||||||
| 							value="secret" |  | ||||||
| 						/><button type="button">Show</button> |  | ||||||
| 						<br /> |  | ||||||
| 						<button type="submit">Create Account</button> |  | ||||||
| 						<br /> |  | ||||||
| 						Already have an account? |  | ||||||
| 						<button class="link" type="button"> |  | ||||||
| 							link existing account |  | ||||||
| 						</button> |  | ||||||
| 					</form> |  | ||||||
| 				</center> |  | ||||||
| 			</div> |  | ||||||
| 
 |  | ||||||
| 			<div class="authn-container authn-existing"> |  | ||||||
| 				<!-- skip this for google auth --> |  | ||||||
| 				<center> |  | ||||||
| 					<h2>Existing User</h2> |  | ||||||
| 					<form class="authn-form"> |  | ||||||
| 						<input |  | ||||||
| 							name="password" |  | ||||||
| 							type="password" |  | ||||||
| 							placeholder="password" |  | ||||||
| 						/> |  | ||||||
| 						<br /> |  | ||||||
| 						<button type="submit">Continue</button> |  | ||||||
| 						<br /> |  | ||||||
| 						<button class="link" type="button"> |  | ||||||
| 							forgot password |  | ||||||
| 						</button> |  | ||||||
| 					</form> |  | ||||||
| 				</center> |  | ||||||
| 			</div> |  | ||||||
| 
 |  | ||||||
| 			<div class="authn-container authn-failed"> |  | ||||||
| 				<center> |  | ||||||
| 					<h2>Incorrect Password</h2> |  | ||||||
| 					<form class="authn-form"> |  | ||||||
| 						<input |  | ||||||
| 							name="password" |  | ||||||
| 							type="password" |  | ||||||
| 							placeholder="password" |  | ||||||
| 						/> |  | ||||||
| 						<br /> |  | ||||||
| 						<button type="submit">Continue</button> |  | ||||||
| 						<br /> |  | ||||||
| 						<button class="link" type="button"> |  | ||||||
| 							forgot password |  | ||||||
| 						</button> |  | ||||||
| 					</form> |  | ||||||
| 				</center> |  | ||||||
| 			</div> |  | ||||||
| 
 |  | ||||||
| 			<div class="authn-container authn-new-device"> |  | ||||||
| 				<center> |  | ||||||
| 					<h2>New Device</h2> |  | ||||||
| 					<p>Check your email to confirm new device.</p> |  | ||||||
| 				</center> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 
 |  | ||||||
| 		<script src="./dist/main.js"></script> |  | ||||||
| 		<script |  | ||||||
| 			src="https://apis.google.com/js/platform.js" |  | ||||||
| 			async |  | ||||||
| 			defer |  | ||||||
| 		></script> |  | ||||||
| 	</body> |  | ||||||
| </html> |  | ||||||
|  | |||||||
| @ -1,3 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| require('./pocket/consumer.js'); |  | ||||||
| @ -1,34 +0,0 @@ | |||||||
| { |  | ||||||
|   "name": "pocketid", |  | ||||||
|   "version": "0.1.0", |  | ||||||
|   "description": "ID tokens made easy", |  | ||||||
|   "main": "pocketid.js", |  | ||||||
|   "scripts": { |  | ||||||
|     "prettier": "prettier --write '**/*.{css,js,md}'", |  | ||||||
|     "test": "node pocketid_test.js" |  | ||||||
|   }, |  | ||||||
|   "repository": { |  | ||||||
|     "type": "git", |  | ||||||
|     "url": "https://example.com/pocketid.git" |  | ||||||
|   }, |  | ||||||
|   "keywords": [ |  | ||||||
|     "oauth1", |  | ||||||
|     "oauth2", |  | ||||||
|     "oauth3", |  | ||||||
|     "oidc", |  | ||||||
|     "acme", |  | ||||||
|     "jwt", |  | ||||||
|     "jose", |  | ||||||
|     "jws", |  | ||||||
|     "jwk" |  | ||||||
|   ], |  | ||||||
|   "author": "AJ ONeal <coolaj86@gmail.com>", |  | ||||||
|   "license": "MPL-2.0", |  | ||||||
|   "dependencies": { |  | ||||||
|     "@root/keypairs": "^0.10.1" |  | ||||||
|   }, |  | ||||||
|   "devDependencies": { |  | ||||||
|     "webpack": "^5.0.0-beta.28", |  | ||||||
|     "webpack-cli": "^3.3.12" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| var path = require('path'); |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
| 	entry: './main.js', |  | ||||||
| 	mode: 'development', |  | ||||||
| 	devServer: { |  | ||||||
| 		contentBase: path.join(__dirname, 'dist'), |  | ||||||
| 		port: 3001 |  | ||||||
| 	}, |  | ||||||
| 	output: { |  | ||||||
| 		publicPath: 'http://localhost:3001/' |  | ||||||
| 	}, |  | ||||||
| 	module: { |  | ||||||
| 		rules: [{}] |  | ||||||
| 	}, |  | ||||||
| 	plugins: [] |  | ||||||
| }; |  | ||||||
							
								
								
									
										19
									
								
								vendor/git.rootprojects.org/root/hashcash/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/git.rootprojects.org/root/hashcash/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,19 +0,0 @@ | |||||||
| cmd/hashcash/hashcash |  | ||||||
| 
 |  | ||||||
| # ---> Go |  | ||||||
| # Binaries for programs and plugins |  | ||||||
| *.exe |  | ||||||
| *.exe~ |  | ||||||
| *.dll |  | ||||||
| *.so |  | ||||||
| *.dylib |  | ||||||
| 
 |  | ||||||
| # Test binary, built with `go test -c` |  | ||||||
| *.test |  | ||||||
| 
 |  | ||||||
| # Output of the go coverage tool, specifically when used with LiteIDE |  | ||||||
| *.out |  | ||||||
| 
 |  | ||||||
| # Dependency directories (remove the comment below to include it) |  | ||||||
| # vendor/ |  | ||||||
| 
 |  | ||||||
							
								
								
									
										312
									
								
								vendor/git.rootprojects.org/root/hashcash/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										312
									
								
								vendor/git.rootprojects.org/root/hashcash/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,312 +0,0 @@ | |||||||
| Mozilla Public License Version 2.0 |  | ||||||
| 
 |  | ||||||
|    1. Definitions |  | ||||||
| 
 |  | ||||||
| 1.1. "Contributor" means each individual or legal entity that creates, contributes |  | ||||||
| to the creation of, or owns Covered Software. |  | ||||||
| 
 |  | ||||||
| 1.2. "Contributor Version" means the combination of the Contributions of others |  | ||||||
| (if any) used by a Contributor and that particular Contributor's Contribution. |  | ||||||
| 
 |  | ||||||
|       1.3. "Contribution" means Covered Software of a particular Contributor. |  | ||||||
| 
 |  | ||||||
| 1.4. "Covered Software" means Source Code Form to which the initial Contributor |  | ||||||
| has attached the notice in Exhibit A, the Executable Form of such Source Code |  | ||||||
| Form, and Modifications of such Source Code Form, in each case including portions |  | ||||||
| thereof. |  | ||||||
| 
 |  | ||||||
|       1.5. "Incompatible With Secondary Licenses" means |  | ||||||
| 
 |  | ||||||
| (a) that the initial Contributor has attached the notice described in Exhibit |  | ||||||
| B to the Covered Software; or |  | ||||||
| 
 |  | ||||||
| (b) that the Covered Software was made available under the terms of version |  | ||||||
| 1.1 or earlier of the License, but not also under the terms of a Secondary |  | ||||||
| License. |  | ||||||
| 
 |  | ||||||
| 1.6. "Executable Form" means any form of the work other than Source Code Form. |  | ||||||
| 
 |  | ||||||
| 1.7. "Larger Work" means a work that combines Covered Software with other |  | ||||||
| material, in a separate file or files, that is not Covered Software. |  | ||||||
| 
 |  | ||||||
|       1.8. "License" means this document. |  | ||||||
| 
 |  | ||||||
| 1.9. "Licensable" means having the right to grant, to the maximum extent possible, |  | ||||||
| whether at the time of the initial grant or subsequently, any and all of the |  | ||||||
| rights conveyed by this License. |  | ||||||
| 
 |  | ||||||
|       1.10. "Modifications" means any of the following: |  | ||||||
| 
 |  | ||||||
| (a) any file in Source Code Form that results from an addition to, deletion |  | ||||||
| from, or modification of the contents of Covered Software; or |  | ||||||
| 
 |  | ||||||
| (b) any new file in Source Code Form that contains any Covered Software. |  | ||||||
| 
 |  | ||||||
| 1.11. "Patent Claims" of a Contributor means any patent claim(s), including |  | ||||||
| without limitation, method, process, and apparatus claims, in any patent Licensable |  | ||||||
| by such Contributor that would be infringed, but for the grant of the License, |  | ||||||
| by the making, using, selling, offering for sale, having made, import, or |  | ||||||
| transfer of either its Contributions or its Contributor Version. |  | ||||||
| 
 |  | ||||||
| 1.12. "Secondary License" means either the GNU General Public License, Version |  | ||||||
| 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General |  | ||||||
| Public License, Version 3.0, or any later versions of those licenses. |  | ||||||
| 
 |  | ||||||
| 1.13. "Source Code Form" means the form of the work preferred for making modifications. |  | ||||||
| 
 |  | ||||||
| 1.14. "You" (or "Your") means an individual or a legal entity exercising rights |  | ||||||
| under this License. For legal entities, "You" includes any entity that controls, |  | ||||||
| is controlled by, or is under common control with You. For purposes of this |  | ||||||
| definition, "control" means (a) the power, direct or indirect, to cause the |  | ||||||
| direction or management of such entity, whether by contract or otherwise, |  | ||||||
| or (b) ownership of more than fifty percent (50%) of the outstanding shares |  | ||||||
| or beneficial ownership of such entity. |  | ||||||
| 
 |  | ||||||
|    2. License Grants and Conditions |  | ||||||
| 
 |  | ||||||
|       2.1. Grants |  | ||||||
| 
 |  | ||||||
| Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive |  | ||||||
| license: |  | ||||||
| 
 |  | ||||||
| (a) under intellectual property rights (other than patent or trademark) Licensable |  | ||||||
| by such Contributor to use, reproduce, make available, modify, display, perform, |  | ||||||
| distribute, and otherwise exploit its Contributions, either on an unmodified |  | ||||||
| basis, with Modifications, or as part of a Larger Work; and |  | ||||||
| 
 |  | ||||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer for |  | ||||||
| sale, have made, import, and otherwise transfer either its Contributions or |  | ||||||
| its Contributor Version. |  | ||||||
| 
 |  | ||||||
|       2.2. Effective Date |  | ||||||
| 
 |  | ||||||
| The licenses granted in Section 2.1 with respect to any Contribution become |  | ||||||
| effective for each Contribution on the date the Contributor first distributes |  | ||||||
| such Contribution. |  | ||||||
| 
 |  | ||||||
|       2.3. Limitations on Grant Scope |  | ||||||
| 
 |  | ||||||
| The licenses granted in this Section 2 are the only rights granted under this |  | ||||||
| License. No additional rights or licenses will be implied from the distribution |  | ||||||
| or licensing of Covered Software under this License. Notwithstanding Section |  | ||||||
| 2.1(b) above, no patent license is granted by a Contributor: |  | ||||||
| 
 |  | ||||||
| (a) for any code that a Contributor has removed from Covered Software; or |  | ||||||
| 
 |  | ||||||
| (b) for infringements caused by: (i) Your and any other third party's modifications |  | ||||||
| of Covered Software, or (ii) the combination of its Contributions with other |  | ||||||
| software (except as part of its Contributor Version); or |  | ||||||
| 
 |  | ||||||
| (c) under Patent Claims infringed by Covered Software in the absence of its |  | ||||||
| Contributions. |  | ||||||
| 
 |  | ||||||
| This License does not grant any rights in the trademarks, service marks, or |  | ||||||
| logos of any Contributor (except as may be necessary to comply with the notice |  | ||||||
| requirements in Section 3.4). |  | ||||||
| 
 |  | ||||||
|       2.4. Subsequent Licenses |  | ||||||
| 
 |  | ||||||
| No Contributor makes additional grants as a result of Your choice to distribute |  | ||||||
| the Covered Software under a subsequent version of this License (see Section |  | ||||||
| 10.2) or under the terms of a Secondary License (if permitted under the terms |  | ||||||
| of Section 3.3). |  | ||||||
| 
 |  | ||||||
|       2.5. Representation |  | ||||||
| 
 |  | ||||||
| Each Contributor represents that the Contributor believes its Contributions |  | ||||||
| are its original creation(s) or it has sufficient rights to grant the rights |  | ||||||
| to its Contributions conveyed by this License. |  | ||||||
| 
 |  | ||||||
|       2.6. Fair Use |  | ||||||
| 
 |  | ||||||
| This License is not intended to limit any rights You have under applicable |  | ||||||
| copyright doctrines of fair use, fair dealing, or other equivalents. |  | ||||||
| 
 |  | ||||||
|       2.7. Conditions |  | ||||||
| 
 |  | ||||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in |  | ||||||
| Section 2.1. |  | ||||||
| 
 |  | ||||||
|    3. Responsibilities |  | ||||||
| 
 |  | ||||||
|       3.1. Distribution of Source Form |  | ||||||
| 
 |  | ||||||
| All distribution of Covered Software in Source Code Form, including any Modifications |  | ||||||
| that You create or to which You contribute, must be under the terms of this |  | ||||||
| License. You must inform recipients that the Source Code Form of the Covered |  | ||||||
| Software is governed by the terms of this License, and how they can obtain |  | ||||||
| a copy of this License. You may not attempt to alter or restrict the recipients' |  | ||||||
| rights in the Source Code Form. |  | ||||||
| 
 |  | ||||||
|       3.2. Distribution of Executable Form |  | ||||||
| 
 |  | ||||||
|       If You distribute Covered Software in Executable Form then: |  | ||||||
| 
 |  | ||||||
| (a) such Covered Software must also be made available in Source Code Form, |  | ||||||
| as described in Section 3.1, and You must inform recipients of the Executable |  | ||||||
| Form how they can obtain a copy of such Source Code Form by reasonable means |  | ||||||
| in a timely manner, at a charge no more than the cost of distribution to the |  | ||||||
| recipient; and |  | ||||||
| 
 |  | ||||||
| (b) You may distribute such Executable Form under the terms of this License, |  | ||||||
| or sublicense it under different terms, provided that the license for the |  | ||||||
| Executable Form does not attempt to limit or alter the recipients' rights |  | ||||||
| in the Source Code Form under this License. |  | ||||||
| 
 |  | ||||||
|       3.3. Distribution of a Larger Work |  | ||||||
| 
 |  | ||||||
| You may create and distribute a Larger Work under terms of Your choice, provided |  | ||||||
| that You also comply with the requirements of this License for the Covered |  | ||||||
| Software. If the Larger Work is a combination of Covered Software with a work |  | ||||||
| governed by one or more Secondary Licenses, and the Covered Software is not |  | ||||||
| Incompatible With Secondary Licenses, this License permits You to additionally |  | ||||||
| distribute such Covered Software under the terms of such Secondary License(s), |  | ||||||
| so that the recipient of the Larger Work may, at their option, further distribute |  | ||||||
| the Covered Software under the terms of either this License or such Secondary |  | ||||||
| License(s). |  | ||||||
| 
 |  | ||||||
|       3.4. Notices |  | ||||||
| 
 |  | ||||||
| You may not remove or alter the substance of any license notices (including |  | ||||||
| copyright notices, patent notices, disclaimers of warranty, or limitations |  | ||||||
| of liability) contained within the Source Code Form of the Covered Software, |  | ||||||
| except that You may alter any license notices to the extent required to remedy |  | ||||||
| known factual inaccuracies. |  | ||||||
| 
 |  | ||||||
|       3.5. Application of Additional Terms |  | ||||||
| 
 |  | ||||||
| You may choose to offer, and to charge a fee for, warranty, support, indemnity |  | ||||||
| or liability obligations to one or more recipients of Covered Software. However, |  | ||||||
| You may do so only on Your own behalf, and not on behalf of any Contributor. |  | ||||||
| You must make it absolutely clear that any such warranty, support, indemnity, |  | ||||||
| or liability obligation is offered by You alone, and You hereby agree to indemnify |  | ||||||
| every Contributor for any liability incurred by such Contributor as a result |  | ||||||
| of warranty, support, indemnity or liability terms You offer. You may include |  | ||||||
| additional disclaimers of warranty and limitations of liability specific to |  | ||||||
| any jurisdiction. |  | ||||||
| 
 |  | ||||||
|    4. Inability to Comply Due to Statute or Regulation |  | ||||||
| 
 |  | ||||||
| If it is impossible for You to comply with any of the terms of this License |  | ||||||
| with respect to some or all of the Covered Software due to statute, judicial |  | ||||||
| order, or regulation then You must: (a) comply with the terms of this License |  | ||||||
| to the maximum extent possible; and (b) describe the limitations and the code |  | ||||||
| they affect. Such description must be placed in a text file included with |  | ||||||
| all distributions of the Covered Software under this License. Except to the |  | ||||||
| extent prohibited by statute or regulation, such description must be sufficiently |  | ||||||
| detailed for a recipient of ordinary skill to be able to understand it. |  | ||||||
| 
 |  | ||||||
|    5. Termination |  | ||||||
| 
 |  | ||||||
| 5.1. The rights granted under this License will terminate automatically if |  | ||||||
| You fail to comply with any of its terms. However, if You become compliant, |  | ||||||
| then the rights granted under this License from a particular Contributor are |  | ||||||
| reinstated (a) provisionally, unless and until such Contributor explicitly |  | ||||||
| and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor |  | ||||||
| fails to notify You of the non-compliance by some reasonable means prior to |  | ||||||
| 60 days after You have come back into compliance. Moreover, Your grants from |  | ||||||
| a particular Contributor are reinstated on an ongoing basis if such Contributor |  | ||||||
| notifies You of the non-compliance by some reasonable means, this is the first |  | ||||||
| time You have received notice of non-compliance with this License from such |  | ||||||
| Contributor, and You become compliant prior to 30 days after Your receipt |  | ||||||
| of the notice. |  | ||||||
| 
 |  | ||||||
| 5.2. If You initiate litigation against any entity by asserting a patent infringement |  | ||||||
| claim (excluding declaratory judgment actions, counter-claims, and cross-claims) |  | ||||||
| alleging that a Contributor Version directly or indirectly infringes any patent, |  | ||||||
| then the rights granted to You by any and all Contributors for the Covered |  | ||||||
| Software under Section 2.1 of this License shall terminate. |  | ||||||
| 
 |  | ||||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end |  | ||||||
| user license agreements (excluding distributors and resellers) which have |  | ||||||
| been validly granted by You or Your distributors under this License prior |  | ||||||
| to termination shall survive termination. |  | ||||||
| 
 |  | ||||||
|    6. Disclaimer of Warranty |  | ||||||
| 
 |  | ||||||
| Covered Software is provided under this License on an "as is" basis, without |  | ||||||
| warranty of any kind, either expressed, implied, or statutory, including, |  | ||||||
| without limitation, warranties that the Covered Software is free of defects, |  | ||||||
| merchantable, fit for a particular purpose or non-infringing. The entire risk |  | ||||||
| as to the quality and performance of the Covered Software is with You. Should |  | ||||||
| any Covered Software prove defective in any respect, You (not any Contributor) |  | ||||||
| assume the cost of any necessary servicing, repair, or correction. This disclaimer |  | ||||||
| of warranty constitutes an essential part of this License. No use of any Covered |  | ||||||
| Software is authorized under this License except under this disclaimer. |  | ||||||
| 
 |  | ||||||
|    7. Limitation of Liability |  | ||||||
| 
 |  | ||||||
| Under no circumstances and under no legal theory, whether tort (including |  | ||||||
| negligence), contract, or otherwise, shall any Contributor, or anyone who |  | ||||||
| distributes Covered Software as permitted above, be liable to You for any |  | ||||||
| direct, indirect, special, incidental, or consequential damages of any character |  | ||||||
| including, without limitation, damages for lost profits, loss of goodwill, |  | ||||||
| work stoppage, computer failure or malfunction, or any and all other commercial |  | ||||||
| damages or losses, even if such party shall have been informed of the possibility |  | ||||||
| of such damages. This limitation of liability shall not apply to liability |  | ||||||
| for death or personal injury resulting from such party's negligence to the |  | ||||||
| extent applicable law prohibits such limitation. Some jurisdictions do not |  | ||||||
| allow the exclusion or limitation of incidental or consequential damages, |  | ||||||
| so this exclusion and limitation may not apply to You. |  | ||||||
| 
 |  | ||||||
|    8. Litigation |  | ||||||
| 
 |  | ||||||
| Any litigation relating to this License may be brought only in the courts |  | ||||||
| of a jurisdiction where the defendant maintains its principal place of business |  | ||||||
| and such litigation shall be governed by laws of that jurisdiction, without |  | ||||||
| reference to its conflict-of-law provisions. Nothing in this Section shall |  | ||||||
| prevent a party's ability to bring cross-claims or counter-claims. |  | ||||||
| 
 |  | ||||||
|    9. Miscellaneous |  | ||||||
| 
 |  | ||||||
| This License represents the complete agreement concerning the subject matter |  | ||||||
| hereof. If any provision of this License is held to be unenforceable, such |  | ||||||
| provision shall be reformed only to the extent necessary to make it enforceable. |  | ||||||
| Any law or regulation which provides that the language of a contract shall |  | ||||||
| be construed against the drafter shall not be used to construe this License |  | ||||||
| against a Contributor. |  | ||||||
| 
 |  | ||||||
|    10. Versions of the License |  | ||||||
| 
 |  | ||||||
|       10.1. New Versions |  | ||||||
| 
 |  | ||||||
| Mozilla Foundation is the license steward. Except as provided in Section 10.3, |  | ||||||
| no one other than the license steward has the right to modify or publish new |  | ||||||
| versions of this License. Each version will be given a distinguishing version |  | ||||||
| number. |  | ||||||
| 
 |  | ||||||
|       10.2. Effect of New Versions |  | ||||||
| 
 |  | ||||||
| You may distribute the Covered Software under the terms of the version of |  | ||||||
| the License under which You originally received the Covered Software, or under |  | ||||||
| the terms of any subsequent version published by the license steward. |  | ||||||
| 
 |  | ||||||
|       10.3. Modified Versions |  | ||||||
| 
 |  | ||||||
| If you create software not governed by this License, and you want to create |  | ||||||
| a new license for such software, you may create and use a modified version |  | ||||||
| of this License if you rename the license and remove any references to the |  | ||||||
| name of the license steward (except to note that such modified license differs |  | ||||||
| from this License). |  | ||||||
| 
 |  | ||||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses |  | ||||||
| 
 |  | ||||||
| If You choose to distribute Source Code Form that is Incompatible With Secondary |  | ||||||
| Licenses under the terms of this version of the License, the notice described |  | ||||||
| in Exhibit B of this License must be attached. Exhibit A - Source Code Form |  | ||||||
| License Notice |  | ||||||
| 
 |  | ||||||
| This Source Code Form is subject to the terms of the Mozilla Public License, |  | ||||||
| v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain |  | ||||||
| one at http://mozilla.org/MPL/2.0/. |  | ||||||
| 
 |  | ||||||
| If it is not possible or desirable to put the notice in a particular file, |  | ||||||
| then You may include the notice in a location (such as a LICENSE file in a |  | ||||||
| relevant directory) where a recipient would be likely to look for such a notice. |  | ||||||
| 
 |  | ||||||
| You may add additional accurate notices of copyright ownership. |  | ||||||
| 
 |  | ||||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice |  | ||||||
| 
 |  | ||||||
| This Source Code Form is "Incompatible With Secondary Licenses", as defined |  | ||||||
| by the Mozilla Public License, v. 2.0. |  | ||||||
							
								
								
									
										41
									
								
								vendor/git.rootprojects.org/root/hashcash/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								vendor/git.rootprojects.org/root/hashcash/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,41 +0,0 @@ | |||||||
| # hashcash |  | ||||||
| 
 |  | ||||||
| HTTP Hashcash implemented in Go. |  | ||||||
| 
 |  | ||||||
| Explanation at https://therootcompany.com/blog/http-hashcash/ |  | ||||||
| 
 |  | ||||||
| Go docs at https://godoc.org/git.rootprojects.org/root/hashcash |  | ||||||
| 
 |  | ||||||
| # CLI Usage |  | ||||||
| 
 |  | ||||||
| Install: |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| go get git.rootprojects.org/root/hashcash/cmd/hashcash |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Usage: |  | ||||||
| 
 |  | ||||||
| ```txt |  | ||||||
| Usage: |  | ||||||
| 	hashcash new [subject *] [expires in 5m] [difficulty 10] |  | ||||||
| 	hashcash parse <hashcash> |  | ||||||
| 	hashcash solve <hashcash> |  | ||||||
| 	hashcash verify <hashcash> [subject *] |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| my_hc=$(hashcash new) |  | ||||||
| echo New: $my_hc |  | ||||||
| hashcash parse "$my_hc" |  | ||||||
| echo "" |  | ||||||
| 
 |  | ||||||
| my_hc=$(hashcash solve "$my_hc") |  | ||||||
| echo Solved: $my_hc |  | ||||||
| hashcash parse "$my_hc" |  | ||||||
| echo "" |  | ||||||
| 
 |  | ||||||
| hashcash verify "$my_hc" |  | ||||||
| ``` |  | ||||||
							
								
								
									
										3
									
								
								vendor/git.rootprojects.org/root/hashcash/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/git.rootprojects.org/root/hashcash/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| module git.rootprojects.org/root/hashcash |  | ||||||
| 
 |  | ||||||
| go 1.15 |  | ||||||
							
								
								
									
										284
									
								
								vendor/git.rootprojects.org/root/hashcash/hashcash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										284
									
								
								vendor/git.rootprojects.org/root/hashcash/hashcash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,284 +0,0 @@ | |||||||
| package hashcash |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"errors" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ErrParse is returned when fewer than 6 or more than 7 segments are split |  | ||||||
| var ErrParse = errors.New("could not split the hashcash parts") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidTag is returned when the Hashcash version is unsupported |  | ||||||
| var ErrInvalidTag = errors.New("expected tag to be 'H'") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidDifficulty is returned when the difficulty is outside of the acceptable range |  | ||||||
| var ErrInvalidDifficulty = errors.New("the number of bits of difficulty is too low or too high") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidDate is returned when the date cannot be parsed as a positive int64 |  | ||||||
| var ErrInvalidDate = errors.New("invalid date") |  | ||||||
| 
 |  | ||||||
| // ErrExpired is returned when the current time is past that of ExpiresAt |  | ||||||
| var ErrExpired = errors.New("expired hashcash") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidSubject is returned when the subject is invalid or does not match that passed to Verify() |  | ||||||
| var ErrInvalidSubject = errors.New("the subject is invalid or rejected") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidNonce is returned when the nonce |  | ||||||
| //var ErrInvalidNonce = errors.New("the nonce has been used or is invalid") |  | ||||||
| 
 |  | ||||||
| // ErrUnsupportedAlgorithm is returned when the given algorithm is not supported |  | ||||||
| var ErrUnsupportedAlgorithm = errors.New("the given algorithm is invalid or not supported") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidSolution is returned when the given hashcash is not properly solved |  | ||||||
| var ErrInvalidSolution = errors.New("the given solution is not valid") |  | ||||||
| 
 |  | ||||||
| // MaxDifficulty is the upper bound for all Solve() operations |  | ||||||
| var MaxDifficulty = 26 |  | ||||||
| 
 |  | ||||||
| // Sep is the separator character to use |  | ||||||
| var Sep = ":" |  | ||||||
| 
 |  | ||||||
| // no milliseconds |  | ||||||
| //var isoTS = "2006-01-02T15:04:05Z" |  | ||||||
| 
 |  | ||||||
| // Hashcash represents a parsed Hashcash string |  | ||||||
| type Hashcash struct { |  | ||||||
| 	Tag        string    `json:"tag"`        // Always "H" for "HTTP" |  | ||||||
| 	Difficulty int       `json:"difficulty"` // Number of "partial pre-image" (zero) bits in the hashed code |  | ||||||
| 	ExpiresAt  time.Time `json:"exp"`        // The timestamp that the hashcash expires, as seconds since the Unix epoch |  | ||||||
| 	Subject    string    `json:"sub"`        // Resource data string being transmitted, e.g., a domain or URL |  | ||||||
| 	Nonce      string    `json:"nonce"`      // Unique string of random characters, encoded as url-safe base-64 |  | ||||||
| 	Alg        string    `json:"alg"`        // always SHA-256 for now |  | ||||||
| 	Solution   string    `json:"solution"`   // Binary counter, encoded as url-safe base-64 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // New returns a Hashcash with reasonable defaults |  | ||||||
| func New(h Hashcash) *Hashcash { |  | ||||||
| 	h.Tag = "H" |  | ||||||
| 
 |  | ||||||
| 	if 0 == h.Difficulty { |  | ||||||
| 		// safe for WebCrypto |  | ||||||
| 		h.Difficulty = 10 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if h.ExpiresAt.IsZero() { |  | ||||||
| 		h.ExpiresAt = time.Now().Add(5 * time.Minute) |  | ||||||
| 	} |  | ||||||
| 	h.ExpiresAt = h.ExpiresAt.UTC().Truncate(time.Second) |  | ||||||
| 
 |  | ||||||
| 	if "" == h.Subject { |  | ||||||
| 		h.Subject = "*" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" == h.Nonce { |  | ||||||
| 		nonce := make([]byte, 16) |  | ||||||
| 		if _, err := rand.Read(nonce); nil != err { |  | ||||||
| 			panic(err) |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		h.Nonce = base64.RawURLEncoding.EncodeToString(nonce) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" == h.Alg { |  | ||||||
| 		h.Alg = "SHA-256" |  | ||||||
| 	} |  | ||||||
| 	/* |  | ||||||
| 		if "SHA-256" != h.Alg { |  | ||||||
| 			// TODO error |  | ||||||
| 		} |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	return &h |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Parse will (obviously) parse the hashcash string, without verifying any |  | ||||||
| // of the parameters. |  | ||||||
| func Parse(hc string) (*Hashcash, error) { |  | ||||||
| 	parts := strings.Split(hc, Sep) |  | ||||||
| 	n := len(parts) |  | ||||||
| 	if n < 6 || n > 7 { |  | ||||||
| 		return nil, ErrParse |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	tag := parts[0] |  | ||||||
| 	if "H" != tag { |  | ||||||
| 		return nil, ErrInvalidTag |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	bits, err := strconv.Atoi(parts[1]) |  | ||||||
| 	if nil != err || bits < 0 { |  | ||||||
| 		return nil, ErrInvalidDifficulty |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Allow empty ExpiresAt |  | ||||||
| 	var exp time.Time |  | ||||||
| 	if "" != parts[2] { |  | ||||||
| 		expAt, err := strconv.ParseInt(parts[2], 10, 64) |  | ||||||
| 		if nil != err || expAt < 0 { |  | ||||||
| 			return nil, ErrInvalidDate |  | ||||||
| 		} |  | ||||||
| 		exp = time.Unix(int64(expAt), 0).UTC() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		exp, err := time.ParseInLocation(isoTS, parts[2], time.UTC) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, ErrInvalidDate |  | ||||||
| 		} |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	sub := parts[3] |  | ||||||
| 
 |  | ||||||
| 	nonce := parts[4] |  | ||||||
| 
 |  | ||||||
| 	alg := parts[5] |  | ||||||
| 
 |  | ||||||
| 	var solution string |  | ||||||
| 	if n > 6 { |  | ||||||
| 		solution = parts[6] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	h := &Hashcash{ |  | ||||||
| 		Tag:        tag, |  | ||||||
| 		Difficulty: bits, |  | ||||||
| 		ExpiresAt:  exp.UTC().Truncate(time.Second), |  | ||||||
| 		Subject:    sub, |  | ||||||
| 		Nonce:      nonce, |  | ||||||
| 		Alg:        alg, |  | ||||||
| 		Solution:   solution, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return h, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String will return the formatted Hashcash, omitting the solution if it has not be solved. |  | ||||||
| func (h *Hashcash) String() string { |  | ||||||
| 	var solution string |  | ||||||
| 	if "" != h.Solution { |  | ||||||
| 		solution = Sep + h.Solution |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var expAt string |  | ||||||
| 	if !h.ExpiresAt.IsZero() { |  | ||||||
| 		expAt = strconv.FormatInt(h.ExpiresAt.UTC().Truncate(time.Second).Unix(), 10) |  | ||||||
| 	} |  | ||||||
| 	return strings.Join( |  | ||||||
| 		[]string{ |  | ||||||
| 			"H", |  | ||||||
| 			strconv.Itoa(h.Difficulty), |  | ||||||
| 			//h.ExpiresAt.UTC().Format(isoTS), |  | ||||||
| 			expAt, |  | ||||||
| 			h.Subject, |  | ||||||
| 			h.Nonce, |  | ||||||
| 			h.Alg, |  | ||||||
| 		}, |  | ||||||
| 		Sep, |  | ||||||
| 	) + solution |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Verify the Hashcash based on Difficulty, Algorithm, ExpiresAt, Subject and, |  | ||||||
| // of course, the Solution and hash. |  | ||||||
| func (h *Hashcash) Verify(subject string) error { |  | ||||||
| 	if h.Difficulty < 0 { |  | ||||||
| 		return ErrInvalidDifficulty |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "SHA-256" != h.Alg { |  | ||||||
| 		return ErrUnsupportedAlgorithm |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !h.ExpiresAt.IsZero() && h.ExpiresAt.Sub(time.Now()) < 0 { |  | ||||||
| 		return ErrExpired |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if subject != h.Subject { |  | ||||||
| 		return ErrInvalidSubject |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	bits := h.Difficulty |  | ||||||
| 	hash := sha256.Sum256([]byte(h.String())) |  | ||||||
| 	n := bits / 8 // 10 / 8 = 1 |  | ||||||
| 	m := bits % 8 // 10 % 8 = 2 |  | ||||||
| 	if m > 0 { |  | ||||||
| 		n++ // 10 bits = 2 bytes |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !verifyBits(hash[:n], bits, n) { |  | ||||||
| 		return ErrInvalidSolution |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func verifyBits(hash []byte, bits, n int) bool { |  | ||||||
| 	for i := 0; i < n; i++ { |  | ||||||
| 		if bits > 8 { |  | ||||||
| 			bits -= 8 |  | ||||||
| 			if 0 != hash[i] { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// (bits % 8) == bits |  | ||||||
| 		pad := 8 - bits |  | ||||||
| 		if 0 != hash[i]>>pad { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// 0 == bits |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Solve will search for a solution, returning an error if the difficulty is |  | ||||||
| // above the local or global MaxDifficulty, the Algorithm is unsupported. |  | ||||||
| func (h *Hashcash) Solve(maxDifficulty int) error { |  | ||||||
| 	if "SHA-256" != h.Alg { |  | ||||||
| 		return ErrUnsupportedAlgorithm |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if h.Difficulty < 0 { |  | ||||||
| 		return ErrInvalidDifficulty |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if h.Difficulty > maxDifficulty || h.Difficulty > MaxDifficulty { |  | ||||||
| 		return ErrInvalidDifficulty |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" != h.Solution { |  | ||||||
| 		if nil == h.Verify(h.Subject) { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		h.Solution = "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	hashcash := h.String() |  | ||||||
| 	bits := h.Difficulty |  | ||||||
| 	n := bits / 8 // 10 / 8 = 1 |  | ||||||
| 	m := bits % 8 // 10 % 8 = 2 |  | ||||||
| 	if m > 0 { |  | ||||||
| 		n++ // 10 bits = 2 bytes |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var solution uint32 = 0 |  | ||||||
| 	sb := make([]byte, 4) |  | ||||||
| 	for { |  | ||||||
| 		// Note: it's not actually important what method of change or encoding is used |  | ||||||
| 		// but incrementing by 1 on an int32 is good enough, and makes for a small base64 encoding |  | ||||||
| 		binary.LittleEndian.PutUint32(sb, solution) |  | ||||||
| 		h.Solution = base64.RawURLEncoding.EncodeToString(sb) |  | ||||||
| 		hash := sha256.Sum256([]byte(hashcash + Sep + h.Solution)) |  | ||||||
| 		if verifyBits(hash[:n], bits, n) { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		solution++ |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										5
									
								
								vendor/git.rootprojects.org/root/keypairs/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/git.rootprojects.org/root/keypairs/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,5 +0,0 @@ | |||||||
| /keypairs |  | ||||||
| /dist/ |  | ||||||
| 
 |  | ||||||
| .DS_Store |  | ||||||
| .*.sw* |  | ||||||
							
								
								
									
										41
									
								
								vendor/git.rootprojects.org/root/keypairs/.goreleaser.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								vendor/git.rootprojects.org/root/keypairs/.goreleaser.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,41 +0,0 @@ | |||||||
| # This is an example goreleaser.yaml file with some sane defaults. |  | ||||||
| # Make sure to check the documentation at http://goreleaser.com |  | ||||||
| before: |  | ||||||
|   hooks: |  | ||||||
|     - go generate ./... |  | ||||||
| builds: |  | ||||||
|   - id: keypairs |  | ||||||
|     main: ./cmd/keypairs/keypairs.go |  | ||||||
|     env: |  | ||||||
|       - CGO_ENABLED=0 |  | ||||||
|     flags: |  | ||||||
|       - -mod=vendor |  | ||||||
|     goos: |  | ||||||
|       - linux |  | ||||||
|       - windows |  | ||||||
|       - darwin |  | ||||||
|       - freebsd |  | ||||||
|     goarch: |  | ||||||
|       - amd64 |  | ||||||
|       - arm |  | ||||||
|       - arm64 |  | ||||||
| archives: |  | ||||||
|   - replacements: |  | ||||||
|       386: i386 |  | ||||||
|       amd64: x86-64 |  | ||||||
|       arm64: aarch64 |  | ||||||
|     format_overrides: |  | ||||||
|       - goos: windows |  | ||||||
|         format: zip |  | ||||||
| env_files: |  | ||||||
|   github_token: ~/.config/goreleaser/github_token.txt |  | ||||||
| checksum: |  | ||||||
|   name_template: 'checksums.txt' |  | ||||||
| snapshot: |  | ||||||
|   name_template: "{{ .Tag }}-next" |  | ||||||
| changelog: |  | ||||||
|   sort: asc |  | ||||||
|   filters: |  | ||||||
|     exclude: |  | ||||||
|       - '^docs:' |  | ||||||
|       - '^test:' |  | ||||||
							
								
								
									
										1
									
								
								vendor/git.rootprojects.org/root/keypairs/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/git.rootprojects.org/root/keypairs/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| AJ ONeal <aj@therootcompany.com> (https://therootcompany.com) |  | ||||||
							
								
								
									
										21
									
								
								vendor/git.rootprojects.org/root/keypairs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/git.rootprojects.org/root/keypairs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,21 +0,0 @@ | |||||||
| The MIT License |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2018-2019 Big Squid, Inc |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
							
								
								
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,63 +0,0 @@ | |||||||
| # [keypairs](https://git.rootprojects.org/root/keypairs) |  | ||||||
| 
 |  | ||||||
| JSON Web Key (JWK) support and type safety lightly placed over top of Go's `crypto/ecdsa` and `crypto/rsa` |  | ||||||
| 
 |  | ||||||
| Useful for JWT, JOSE, etc. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER) |  | ||||||
| 
 |  | ||||||
| pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER) |  | ||||||
| 
 |  | ||||||
| jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day)) |  | ||||||
| 
 |  | ||||||
| kid, err := keypairs.ThumbprintPublicKey(pub) |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| # GoDoc API Documentation |  | ||||||
| 
 |  | ||||||
| See <https://pkg.go.dev/git.rootprojects.org/root/keypairs> |  | ||||||
| 
 |  | ||||||
| # Philosophy |  | ||||||
| 
 |  | ||||||
| Go's standard library is great. |  | ||||||
| 
 |  | ||||||
| Go has _excellent_ crytography support and provides wonderful |  | ||||||
| primitives for dealing with them. |  | ||||||
| 
 |  | ||||||
| I prefer to stay as close to Go's `crypto` package as possible, |  | ||||||
| just adding a light touch for JWT support and type safety. |  | ||||||
| 
 |  | ||||||
| # Type Safety |  | ||||||
| 
 |  | ||||||
| `crypto.PublicKey` is a "marker interface", meaning that it is **not typesafe**! |  | ||||||
| 
 |  | ||||||
| `go-keypairs` defines `type keypairs.PrivateKey interface { Public() crypto.PublicKey }`, |  | ||||||
| which is implemented by `crypto/rsa` and `crypto/ecdsa` |  | ||||||
| (but not `crypto/dsa`, which we really don't care that much about). |  | ||||||
| 
 |  | ||||||
| Go1.15 will add `[PublicKey.Equal(crypto.PublicKey)](https://github.com/golang/go/issues/21704)`, |  | ||||||
| which will make it possible to remove the additional wrapper over `PublicKey` |  | ||||||
| and use an interface instead. |  | ||||||
| 
 |  | ||||||
| Since there are no common methods between `rsa.PublicKey` and `ecdsa.PublicKey`, |  | ||||||
| go-keypairs lightly wraps each to implement `Thumbprint() string` (part of the JOSE/JWK spec). |  | ||||||
| 
 |  | ||||||
| ## JSON Web Key (JWK) as a "codec" |  | ||||||
| 
 |  | ||||||
| Although there are many, many ways that JWKs could be interpreted |  | ||||||
| (possibly why they haven't made it into the standard library), `go-keypairs` |  | ||||||
| follows the basic pattern of `encoding/x509` to `Parse` and `Marshal` |  | ||||||
| only the most basic and most meaningful parts of a key. |  | ||||||
| 
 |  | ||||||
| I highly recommend that you use `Thumbprint()` for `KeyID` you also |  | ||||||
| get the benefit of not losing information when encoding and decoding |  | ||||||
| between the ASN.1, x509, PEM, and JWK formats. |  | ||||||
| 
 |  | ||||||
| # LICENSE |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2020-present AJ ONeal \ |  | ||||||
| Copyright (c) 2018-2019 Big Squid, Inc. |  | ||||||
| 
 |  | ||||||
| This work is licensed under the terms of the MIT license. \ |  | ||||||
| For a copy, see <https://opensource.org/licenses/MIT>. |  | ||||||
							
								
								
									
										19
									
								
								vendor/git.rootprojects.org/root/keypairs/cli_test.sh
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/git.rootprojects.org/root/keypairs/cli_test.sh
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,19 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| set -u |  | ||||||
| 
 |  | ||||||
| go build -mod=vendor cmd/keypairs/*.go |  | ||||||
| ./keypairs gen > testkey.jwk.json 2> testpub.jwk.json |  | ||||||
| 
 |  | ||||||
| ./keypairs sign --exp 1h ./testkey.jwk.json '{"foo":"bar"}' > testjwt.txt 2> testjws.json |  | ||||||
| 
 |  | ||||||
| echo "" |  | ||||||
| echo "Should pass:" |  | ||||||
| ./keypairs verify ./testpub.jwk.json testjwt.txt > /dev/null |  | ||||||
| ./keypairs verify ./testpub.jwk.json "$(cat testjwt.txt)" > /dev/null |  | ||||||
| ./keypairs verify ./testpub.jwk.json testjws.json > /dev/null |  | ||||||
| ./keypairs verify ./testpub.jwk.json "$(cat testjws.json)" > /dev/null |  | ||||||
| 
 |  | ||||||
| echo "" |  | ||||||
| echo "Should fail:" |  | ||||||
| ./keypairs sign --exp -1m ./testkey.jwk.json '{"bar":"foo"}' > errjwt.txt 2> errjws.json |  | ||||||
| ./keypairs verify ./testpub.jwk.json errjwt.txt > /dev/null |  | ||||||
							
								
								
									
										40
									
								
								vendor/git.rootprojects.org/root/keypairs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/git.rootprojects.org/root/keypairs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,40 +0,0 @@ | |||||||
| /* |  | ||||||
| Package keypairs complements Go's standard keypair-related packages |  | ||||||
| (encoding/pem, crypto/x509, crypto/rsa, crypto/ecdsa, crypto/elliptic) |  | ||||||
| with JWK encoding support and typesafe PrivateKey and PublicKey interfaces. |  | ||||||
| 
 |  | ||||||
| Basics |  | ||||||
| 
 |  | ||||||
| 	key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER) |  | ||||||
| 
 |  | ||||||
| 	pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER) |  | ||||||
| 
 |  | ||||||
| 	jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day)) |  | ||||||
| 
 |  | ||||||
| 	kid, err := keypairs.ThumbprintPublicKey(pub) |  | ||||||
| 
 |  | ||||||
| Convenience functions are available which will fetch keys |  | ||||||
| (or retrieve them from cache) via OIDC, .well-known/jwks.json, and direct urls. |  | ||||||
| All keys are cached by Thumbprint, as well as kid(@issuer), if available. |  | ||||||
| 
 |  | ||||||
| 	import "git.rootprojects.org/root/keypairs/keyfetch" |  | ||||||
| 
 |  | ||||||
| 	pubs, err := keyfetch.OIDCJWKs("https://example.com/") |  | ||||||
| 	pubs, err := keyfetch.OIDCJWK(ThumbOrKeyID, "https://example.com/") |  | ||||||
| 
 |  | ||||||
| 	pubs, err := keyfetch.WellKnownJWKs("https://example.com/") |  | ||||||
| 	pubs, err := keyfetch.WellKnownJWK(ThumbOrKeyID, "https://example.com/") |  | ||||||
| 
 |  | ||||||
| 	pubs, err := keyfetch.JWKs("https://example.com/path/to/jwks/") |  | ||||||
| 	pubs, err := keyfetch.JWK(ThumbOrKeyID, "https://example.com/path/to/jwks/") |  | ||||||
| 
 |  | ||||||
| 	// From URL |  | ||||||
| 	pub, err := keyfetch.Fetch("https://example.com/jwk.json") |  | ||||||
| 
 |  | ||||||
| 	// From Cache only |  | ||||||
| 	pub := keyfetch.Get(thumbprint, "https://example.com/jwk.json") |  | ||||||
| 
 |  | ||||||
| A non-caching version with the same capabilities is also available. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| package keypairs |  | ||||||
							
								
								
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/generate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/generate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,69 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/elliptic" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"io" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var randReader io.Reader = rand.Reader |  | ||||||
| var allowMocking = false |  | ||||||
| 
 |  | ||||||
| // KeyOptions are the things that we may need to know about a request to fulfill it properly |  | ||||||
| type keyOptions struct { |  | ||||||
| 	//Key     string `json:"key"` |  | ||||||
| 	KeyType  string `json:"kty"` |  | ||||||
| 	mockSeed int64  //`json:"-"` |  | ||||||
| 	//SeedStr string `json:"seed"` |  | ||||||
| 	//Claims  Object `json:"claims"` |  | ||||||
| 	//Header  Object `json:"header"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (o *keyOptions) nextReader() io.Reader { |  | ||||||
| 	if allowMocking { |  | ||||||
| 		return o.maybeMockReader() |  | ||||||
| 	} |  | ||||||
| 	return randReader |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewDefaultPrivateKey generates a key with reasonable strength. |  | ||||||
| // Today that means a 256-bit equivalent - either RSA 2048 or EC P-256. |  | ||||||
| func NewDefaultPrivateKey() PrivateKey { |  | ||||||
| 	// insecure random is okay here, |  | ||||||
| 	// it's just used for a coin toss |  | ||||||
| 	mathrand.Seed(time.Now().UnixNano()) |  | ||||||
| 	coin := mathrand.Int() |  | ||||||
| 
 |  | ||||||
| 	// the idea here is that we want to make |  | ||||||
| 	// it dead simple to support RSA and EC |  | ||||||
| 	// so it shouldn't matter which is used |  | ||||||
| 	if 0 == coin%2 { |  | ||||||
| 		return newPrivateKey(&keyOptions{ |  | ||||||
| 			KeyType: "RSA", |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| 	return newPrivateKey(&keyOptions{ |  | ||||||
| 		KeyType: "EC", |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // newPrivateKey generates a 256-bit entropy RSA or ECDSA private key |  | ||||||
| func newPrivateKey(opts *keyOptions) PrivateKey { |  | ||||||
| 	var privkey PrivateKey |  | ||||||
| 
 |  | ||||||
| 	if "RSA" == opts.KeyType { |  | ||||||
| 		keylen := 2048 |  | ||||||
| 		privkey, _ = rsa.GenerateKey(opts.nextReader(), keylen) |  | ||||||
| 		if allowMocking { |  | ||||||
| 			privkey = maybeDerandomizeMockKey(privkey, keylen, opts) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		// TODO: EC keys may also suffer the same random problems in the future |  | ||||||
| 		privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader()) |  | ||||||
| 	} |  | ||||||
| 	return privkey |  | ||||||
| } |  | ||||||
							
								
								
									
										3
									
								
								vendor/git.rootprojects.org/root/keypairs/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/git.rootprojects.org/root/keypairs/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| module git.rootprojects.org/root/keypairs |  | ||||||
| 
 |  | ||||||
| go 1.12 |  | ||||||
							
								
								
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/jwk.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/jwk.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,69 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // JWK abstracts EC and RSA keys |  | ||||||
| type JWK interface { |  | ||||||
| 	marshalJWK() ([]byte, error) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ECJWK is the EC variant |  | ||||||
| type ECJWK struct { |  | ||||||
| 	KeyID string   `json:"kid,omitempty"` |  | ||||||
| 	Curve string   `json:"crv"` |  | ||||||
| 	X     string   `json:"x"` |  | ||||||
| 	Y     string   `json:"y"` |  | ||||||
| 	Use   []string `json:"use,omitempty"` |  | ||||||
| 	Seed  string   `json:"_seed,omitempty"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *ECJWK) marshalJWK() ([]byte, error) { |  | ||||||
| 	return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, k.Curve, k.X, k.Y)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RSAJWK is the RSA variant |  | ||||||
| type RSAJWK struct { |  | ||||||
| 	KeyID string   `json:"kid,omitempty"` |  | ||||||
| 	Exp   string   `json:"e"` |  | ||||||
| 	N     string   `json:"n"` |  | ||||||
| 	Use   []string `json:"use,omitempty"` |  | ||||||
| 	Seed  string   `json:"_seed,omitempty"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *RSAJWK) marshalJWK() ([]byte, error) { |  | ||||||
| 	return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, k.Exp, k.N)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| // ToPublicJWK exposes only the public parts |  | ||||||
| func ToPublicJWK(pubkey PublicKey) JWK { |  | ||||||
| 	switch k := pubkey.Key().(type) { |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		return ECToPublicJWK(k) |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		return RSAToPublicJWK(k) |  | ||||||
| 	default: |  | ||||||
| 		panic(errors.New("impossible key type")) |  | ||||||
| 		//return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ECToPublicJWK will output the most minimal version of an EC JWK (no key id, no "use" flag, nada) |  | ||||||
| func ECToPublicJWK(k *ecdsa.PublicKey) *ECJWK { |  | ||||||
| 	return &ECJWK{ |  | ||||||
| 		Curve: k.Curve.Params().Name, |  | ||||||
| 		X:     base64.RawURLEncoding.EncodeToString(k.X.Bytes()), |  | ||||||
| 		Y:     base64.RawURLEncoding.EncodeToString(k.Y.Bytes()), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RSAToPublicJWK will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada) |  | ||||||
| func RSAToPublicJWK(p *rsa.PublicKey) *RSAJWK { |  | ||||||
| 	return &RSAJWK{ |  | ||||||
| 		Exp: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()), |  | ||||||
| 		N:   base64.RawURLEncoding.EncodeToString(p.N.Bytes()), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| */ |  | ||||||
							
								
								
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/jws.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/jws.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,63 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // JWS is a parsed JWT, representation as signable/verifiable and human-readable parts |  | ||||||
| type JWS struct { |  | ||||||
| 	Header    Object `json:"header"`    // JSON |  | ||||||
| 	Claims    Object `json:"claims"`    // JSON |  | ||||||
| 	Protected string `json:"protected"` // base64 |  | ||||||
| 	Payload   string `json:"payload"`   // base64 |  | ||||||
| 	Signature string `json:"signature"` // base64 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JWSToJWT joins JWS parts into a JWT as {ProtectedHeader}.{SerializedPayload}.{Signature}. |  | ||||||
| func JWSToJWT(jwt *JWS) string { |  | ||||||
| 	return fmt.Sprintf( |  | ||||||
| 		"%s.%s.%s", |  | ||||||
| 		jwt.Protected, |  | ||||||
| 		jwt.Payload, |  | ||||||
| 		jwt.Signature, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JWTToJWS splits the JWT into its JWS segments |  | ||||||
| func JWTToJWS(jwt string) (jws *JWS) { |  | ||||||
| 	jwt = strings.TrimSpace(jwt) |  | ||||||
| 	parts := strings.Split(jwt, ".") |  | ||||||
| 	if 3 != len(parts) { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return &JWS{ |  | ||||||
| 		Protected: parts[0], |  | ||||||
| 		Payload:   parts[1], |  | ||||||
| 		Signature: parts[2], |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DecodeComponents decodes JWS Header and Claims |  | ||||||
| func (jws *JWS) DecodeComponents() error { |  | ||||||
| 	protected, err := base64.RawURLEncoding.DecodeString(jws.Protected) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return errors.New("invalid JWS header base64Url encoding") |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(protected), &jws.Header); nil != err { |  | ||||||
| 		return errors.New("invalid JWS header") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	payload, err := base64.RawURLEncoding.DecodeString(jws.Payload) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return errors.New("invalid JWS payload base64Url encoding") |  | ||||||
| 	} |  | ||||||
| 	if err := json.Unmarshal([]byte(payload), &jws.Claims); nil != err { |  | ||||||
| 		return errors.New("invalid JWS claims") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										516
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										516
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,516 +0,0 @@ | |||||||
| // Package keyfetch retrieve and cache PublicKeys |  | ||||||
| // from OIDC (https://example.com/.well-known/openid-configuration) |  | ||||||
| // and Auth0 (https://example.com/.well-known/jwks.json) |  | ||||||
| // JWKs URLs and expires them when `exp` is reached |  | ||||||
| // (or a default expiry if the key does not provide one). |  | ||||||
| // It uses the keypairs package to Unmarshal the JWKs into their |  | ||||||
| // native types (with a very thin shim to provide the type safety |  | ||||||
| // that Go's crypto.PublicKey and crypto.PrivateKey interfaces lack). |  | ||||||
| package keyfetch |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| 	"git.rootprojects.org/root/keypairs/keyfetch/uncached" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // TODO should be ErrInvalidJWKURL |  | ||||||
| 
 |  | ||||||
| // EInvalidJWKURL means that the url did not provide JWKs |  | ||||||
| var EInvalidJWKURL = errors.New("url does not lead to valid JWKs") |  | ||||||
| 
 |  | ||||||
| // KeyCache is an in-memory key cache |  | ||||||
| var KeyCache = map[string]CachableKey{} |  | ||||||
| 
 |  | ||||||
| // KeyCacheMux is used to guard the in-memory cache |  | ||||||
| var KeyCacheMux = sync.Mutex{} |  | ||||||
| 
 |  | ||||||
| // ErrInsecureDomain means that plain http was used where https was expected |  | ||||||
| var ErrInsecureDomain = errors.New("Whitelists should only allow secure URLs (i.e. https://). To allow unsecured private networking (i.e. Docker) pass PrivateWhitelist as a list of private URLs") |  | ||||||
| 
 |  | ||||||
| // TODO Cacheable key (shouldn't this be private)? |  | ||||||
| 
 |  | ||||||
| // CachableKey represents |  | ||||||
| type CachableKey struct { |  | ||||||
| 	Key    keypairs.PublicKey |  | ||||||
| 	Expiry time.Time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // maybe TODO use this poor-man's enum to allow kids thumbs to be accepted by the same method? |  | ||||||
| /* |  | ||||||
| type KeyID string |  | ||||||
| 
 |  | ||||||
| func (kid KeyID) ID() string { |  | ||||||
| 	return string(kid) |  | ||||||
| } |  | ||||||
| func (kid KeyID) isID() {} |  | ||||||
| 
 |  | ||||||
| type Thumbprint string |  | ||||||
| 
 |  | ||||||
| func (thumb Thumbprint) ID() string { |  | ||||||
| 	return string(thumb) |  | ||||||
| } |  | ||||||
| func (thumb Thumbprint) isID() {} |  | ||||||
| 
 |  | ||||||
| type ID interface { |  | ||||||
| 	ID() string |  | ||||||
| 	isID() |  | ||||||
| } |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| // StaleTime defines when public keys should be renewed (15 minutes by default) |  | ||||||
| var StaleTime = 15 * time.Minute |  | ||||||
| 
 |  | ||||||
| // DefaultKeyDuration defines how long a key should be considered fresh (48 hours by default) |  | ||||||
| var DefaultKeyDuration = 48 * time.Hour |  | ||||||
| 
 |  | ||||||
| // MinimumKeyDuration defines the minimum time that a key will be cached (1 hour by default) |  | ||||||
| var MinimumKeyDuration = time.Hour |  | ||||||
| 
 |  | ||||||
| // MaximumKeyDuration defines the maximum time that a key will be cached (72 hours by default) |  | ||||||
| var MaximumKeyDuration = 72 * time.Hour |  | ||||||
| 
 |  | ||||||
| // PublicKeysMap is a newtype for a map of keypairs.PublicKey |  | ||||||
| type PublicKeysMap map[string]keypairs.PublicKey |  | ||||||
| 
 |  | ||||||
| // OIDCJWKs fetches baseURL + ".well-known/openid-configuration" and then fetches and returns the Public Keys. |  | ||||||
| func OIDCJWKs(baseURL string) (PublicKeysMap, error) { |  | ||||||
| 	maps, keys, err := uncached.OIDCJWKs(baseURL) |  | ||||||
| 
 |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	cacheKeys(maps, keys, baseURL) |  | ||||||
| 	return keys, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // OIDCJWK fetches baseURL + ".well-known/openid-configuration" and then returns the key matching kid (or thumbprint) |  | ||||||
| func OIDCJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { |  | ||||||
| 	return immediateOneOrFetch(kidOrThumb, iss, uncached.OIDCJWKs) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WellKnownJWKs fetches baseURL + ".well-known/jwks.json" and caches and returns the keys |  | ||||||
| func WellKnownJWKs(kidOrThumb, iss string) (PublicKeysMap, error) { |  | ||||||
| 	maps, keys, err := uncached.WellKnownJWKs(iss) |  | ||||||
| 
 |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	cacheKeys(maps, keys, iss) |  | ||||||
| 	return keys, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WellKnownJWK fetches baseURL + ".well-known/jwks.json" and returns the key matching kid (or thumbprint) |  | ||||||
| func WellKnownJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { |  | ||||||
| 	return immediateOneOrFetch(kidOrThumb, iss, uncached.WellKnownJWKs) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JWKs returns a map of keys identified by their thumbprint |  | ||||||
| // (since kid may or may not be present) |  | ||||||
| func JWKs(jwksurl string) (PublicKeysMap, error) { |  | ||||||
| 	maps, keys, err := uncached.JWKs(jwksurl) |  | ||||||
| 
 |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	iss := strings.Replace(jwksurl, ".well-known/jwks.json", "", 1) |  | ||||||
| 	cacheKeys(maps, keys, iss) |  | ||||||
| 	return keys, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JWK tries to return a key from cache, falling back to the /.well-known/jwks.json of the issuer |  | ||||||
| func JWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { |  | ||||||
| 	return immediateOneOrFetch(kidOrThumb, iss, uncached.JWKs) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PEM tries to return a key from cache, falling back to the specified PEM url |  | ||||||
| func PEM(url string) (keypairs.PublicKey, error) { |  | ||||||
| 	// url is kid in this case |  | ||||||
| 	return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { |  | ||||||
| 		m, key, err := uncached.PEM(url) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// put in a map, just for caching |  | ||||||
| 		maps := map[string]map[string]string{} |  | ||||||
| 		maps[key.Thumbprint()] = m |  | ||||||
| 		maps[url] = m |  | ||||||
| 
 |  | ||||||
| 		keys := map[string]keypairs.PublicKey{} |  | ||||||
| 		keys[key.Thumbprint()] = key |  | ||||||
| 		keys[url] = key |  | ||||||
| 
 |  | ||||||
| 		return maps, keys, nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Fetch returns a key from cache, falling back to an exact url as the "issuer" |  | ||||||
| func Fetch(url string) (keypairs.PublicKey, error) { |  | ||||||
| 	// url is kid in this case |  | ||||||
| 	return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { |  | ||||||
| 		m, key, err := uncached.Fetch(url) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// put in a map, just for caching |  | ||||||
| 		maps := map[string]map[string]string{} |  | ||||||
| 		maps[key.Thumbprint()] = m |  | ||||||
| 
 |  | ||||||
| 		keys := map[string]keypairs.PublicKey{} |  | ||||||
| 		keys[key.Thumbprint()] = key |  | ||||||
| 
 |  | ||||||
| 		return maps, keys, nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get retrieves a key from cache, or returns an error. |  | ||||||
| // The issuer string may be empty if using a thumbprint rather than a kid. |  | ||||||
| func Get(kidOrThumb, iss string) keypairs.PublicKey { |  | ||||||
| 	if pub := get(kidOrThumb, iss); nil != pub { |  | ||||||
| 		return pub.Key |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func get(kidOrThumb, iss string) *CachableKey { |  | ||||||
| 	iss = normalizeIssuer(iss) |  | ||||||
| 	KeyCacheMux.Lock() |  | ||||||
| 	defer KeyCacheMux.Unlock() |  | ||||||
| 
 |  | ||||||
| 	// we're safe to check the cache by kid alone |  | ||||||
| 	// by virtue that we never set it by kid alone |  | ||||||
| 	hit, ok := KeyCache[kidOrThumb] |  | ||||||
| 	if ok { |  | ||||||
| 		if now := time.Now(); hit.Expiry.Sub(now) > 0 { |  | ||||||
| 			// only return non-expired keys |  | ||||||
| 			return &hit |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	id := kidOrThumb + "@" + iss |  | ||||||
| 	hit, ok = KeyCache[id] |  | ||||||
| 	if ok { |  | ||||||
| 		if now := time.Now(); hit.Expiry.Sub(now) > 0 { |  | ||||||
| 			// only return non-expired keys |  | ||||||
| 			return &hit |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func immediateOneOrFetch(kidOrThumb, iss string, fetcher myfetcher) (keypairs.PublicKey, error) { |  | ||||||
| 	now := time.Now() |  | ||||||
| 	key := get(kidOrThumb, iss) |  | ||||||
| 
 |  | ||||||
| 	if nil == key { |  | ||||||
| 		return fetchAndSelect(kidOrThumb, iss, fetcher) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Fetch just a little before the key actually expires |  | ||||||
| 	if key.Expiry.Sub(now) <= StaleTime { |  | ||||||
| 		go fetchAndSelect(kidOrThumb, iss, fetcher) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return key.Key, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type myfetcher func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) |  | ||||||
| 
 |  | ||||||
| func fetchAndSelect(id, baseURL string, fetcher myfetcher) (keypairs.PublicKey, error) { |  | ||||||
| 	maps, keys, err := fetcher(baseURL) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	cacheKeys(maps, keys, baseURL) |  | ||||||
| 
 |  | ||||||
| 	for i := range keys { |  | ||||||
| 		key := keys[i] |  | ||||||
| 
 |  | ||||||
| 		if id == key.Thumbprint() { |  | ||||||
| 			return key, nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if id == key.KeyID() { |  | ||||||
| 			return key, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil, fmt.Errorf("Key identified by '%s' was not found at %s", id, baseURL) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func cacheKeys(maps map[string]map[string]string, keys map[string]keypairs.PublicKey, issuer string) { |  | ||||||
| 	for i := range keys { |  | ||||||
| 		key := keys[i] |  | ||||||
| 		m := maps[i] |  | ||||||
| 		iss := issuer |  | ||||||
| 		if "" != m["iss"] { |  | ||||||
| 			iss = m["iss"] |  | ||||||
| 		} |  | ||||||
| 		iss = normalizeIssuer(iss) |  | ||||||
| 		cacheKey(m["kid"], iss, m["exp"], key) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func cacheKey(kid, iss, expstr string, pub keypairs.PublicKey) error { |  | ||||||
| 	var expiry time.Time |  | ||||||
| 	iss = normalizeIssuer(iss) |  | ||||||
| 
 |  | ||||||
| 	exp, _ := strconv.ParseInt(expstr, 10, 64) |  | ||||||
| 	if 0 == exp { |  | ||||||
| 		// use default |  | ||||||
| 		expiry = time.Now().Add(DefaultKeyDuration) |  | ||||||
| 	} else if exp < time.Now().Add(MinimumKeyDuration).Unix() || exp > time.Now().Add(MaximumKeyDuration).Unix() { |  | ||||||
| 		// use at least one hour |  | ||||||
| 		expiry = time.Now().Add(MinimumKeyDuration) |  | ||||||
| 	} else { |  | ||||||
| 		expiry = time.Unix(exp, 0) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	KeyCacheMux.Lock() |  | ||||||
| 	defer KeyCacheMux.Unlock() |  | ||||||
| 	// Put the key in the cache by both kid and thumbprint, and set the expiry |  | ||||||
| 	id := kid + "@" + iss |  | ||||||
| 	KeyCache[id] = CachableKey{ |  | ||||||
| 		Key:    pub, |  | ||||||
| 		Expiry: expiry, |  | ||||||
| 	} |  | ||||||
| 	// Since thumbprints are crypto secure, iss isn't needed |  | ||||||
| 	thumb := pub.Thumbprint() |  | ||||||
| 	KeyCache[thumb] = CachableKey{ |  | ||||||
| 		Key:    pub, |  | ||||||
| 		Expiry: expiry, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func clear() { |  | ||||||
| 	KeyCacheMux.Lock() |  | ||||||
| 	defer KeyCacheMux.Unlock() |  | ||||||
| 	KeyCache = map[string]CachableKey{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func normalizeIssuer(iss string) string { |  | ||||||
| 	return strings.TrimRight(iss, "/") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isTrustedIssuer(iss string, whitelist Whitelist, rs ...*http.Request) bool { |  | ||||||
| 	if "" == iss { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Normalize the http:// and https:// and parse |  | ||||||
| 	iss = strings.TrimRight(iss, "/") + "/" |  | ||||||
| 	if strings.HasPrefix(iss, "http://") { |  | ||||||
| 		// ignore |  | ||||||
| 	} else if strings.HasPrefix(iss, "//") { |  | ||||||
| 		return false // TODO |  | ||||||
| 	} else if !strings.HasPrefix(iss, "https://") { |  | ||||||
| 		iss = "https://" + iss |  | ||||||
| 	} |  | ||||||
| 	issURL, err := url.Parse(iss) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check that |  | ||||||
| 	// * schemes match (https: == https:) |  | ||||||
| 	// * paths match (/foo/ == /foo/, always with trailing slash added) |  | ||||||
| 	// * hostnames are compatible (a == b or "sub.foo.com".HasSufix(".foo.com")) |  | ||||||
| 	for i := range []*url.URL(whitelist) { |  | ||||||
| 		u := whitelist[i] |  | ||||||
| 
 |  | ||||||
| 		if issURL.Scheme != u.Scheme { |  | ||||||
| 			continue |  | ||||||
| 		} else if u.Path != strings.TrimRight(issURL.Path, "/")+"/" { |  | ||||||
| 			continue |  | ||||||
| 		} else if issURL.Host != u.Host { |  | ||||||
| 			if '.' == u.Host[0] && strings.HasSuffix(issURL.Host, u.Host) { |  | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		// All failures have been handled |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if implicit issuer is available |  | ||||||
| 	if 0 == len(rs) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return hasImplicitTrust(issURL, rs[0]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // hasImplicitTrust relies on the security of DNS and TLS to determine if the |  | ||||||
| // headers of the request can be trusted as identifying the server itself as |  | ||||||
| // a valid issuer, without additional configuration. |  | ||||||
| // |  | ||||||
| // Helpful for testing, but in the wrong hands could easily lead to a zero-day. |  | ||||||
| func hasImplicitTrust(issURL *url.URL, r *http.Request) bool { |  | ||||||
| 	if nil == r { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Sanity check that, if a load balancer exists, it isn't misconfigured |  | ||||||
| 	proto := r.Header.Get("X-Forwarded-Proto") |  | ||||||
| 	if "" != proto && proto != "https" { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Get the host |  | ||||||
| 	// * If TLS, block Domain Fronting |  | ||||||
| 	// * Otherwise assume trusted proxy |  | ||||||
| 	// * Otherwise assume test environment |  | ||||||
| 	var host string |  | ||||||
| 	if nil != r.TLS { |  | ||||||
| 		// Note that if this were to be implemented for HTTP/2 it would need to |  | ||||||
| 		// check all names on the certificate, not just the one with which the |  | ||||||
| 		// original connection was established. However, not our problem here. |  | ||||||
| 		// See https://serverfault.com/a/908087/93930 |  | ||||||
| 		if r.TLS.ServerName != r.Host { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		host = r.Host |  | ||||||
| 	} else { |  | ||||||
| 		host = r.Header.Get("X-Forwarded-Host") |  | ||||||
| 		if "" == host { |  | ||||||
| 			host = r.Host |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Same tests as above, adjusted since it can't handle wildcards and, since |  | ||||||
| 	// the path is variable, we make the assumption that a child can trust a |  | ||||||
| 	// parent, but that a parent cannot trust a child. |  | ||||||
| 	if r.Host != issURL.Host { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	if !strings.HasPrefix(strings.TrimRight(r.URL.Path, "/")+"/", issURL.Path) { |  | ||||||
| 		// Ex: Request URL                                   Token Issuer |  | ||||||
| 		// !"https:example.com/johndoe/api/dothing".HasPrefix("https:example.com/") |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Whitelist is a newtype for an array of URLs |  | ||||||
| type Whitelist []*url.URL |  | ||||||
| 
 |  | ||||||
| // NewWhitelist turns an array of URLs (such as https://example.com/) into |  | ||||||
| // a parsed array of *url.URLs that can be used by the IsTrustedIssuer function |  | ||||||
| func NewWhitelist(issuers []string, privateList ...[]string) (Whitelist, error) { |  | ||||||
| 	var err error |  | ||||||
| 
 |  | ||||||
| 	list := []*url.URL{} |  | ||||||
| 	if 0 != len(issuers) { |  | ||||||
| 		insecure := false |  | ||||||
| 		list, err = newWhitelist(list, issuers, insecure) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if 0 != len(privateList) && 0 != len(privateList[0]) { |  | ||||||
| 		insecure := true |  | ||||||
| 		list, err = newWhitelist(list, privateList[0], insecure) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return Whitelist(list), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newWhitelist(list []*url.URL, issuers []string, insecure bool) (Whitelist, error) { |  | ||||||
| 	for i := range issuers { |  | ||||||
| 		iss := issuers[i] |  | ||||||
| 		if "" == strings.TrimSpace(iss) { |  | ||||||
| 			fmt.Println("[Warning] You have an empty string in your keyfetch whitelist.") |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Should have a valid http or https prefix |  | ||||||
| 		// TODO support custom prefixes (i.e. app://) ? |  | ||||||
| 		if strings.HasPrefix(iss, "http://") { |  | ||||||
| 			if !insecure { |  | ||||||
| 				log.Println("Oops! You have an insecure domain in your whitelist: ", iss) |  | ||||||
| 				return nil, ErrInsecureDomain |  | ||||||
| 			} |  | ||||||
| 		} else if strings.HasPrefix(iss, "//") { |  | ||||||
| 			// TODO |  | ||||||
| 			return nil, errors.New("Rather than prefixing with // to support multiple protocols, add them seperately:" + iss) |  | ||||||
| 		} else if !strings.HasPrefix(iss, "https://") { |  | ||||||
| 			iss = "https://" + iss |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// trailing slash as a boundary character, which may or may not denote a directory |  | ||||||
| 		iss = strings.TrimRight(iss, "/") + "/" |  | ||||||
| 		u, err := url.Parse(iss) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Strip any * prefix, for easier comparison later |  | ||||||
| 		// *.example.com => .example.com |  | ||||||
| 		if strings.HasPrefix(u.Host, "*.") { |  | ||||||
| 			u.Host = u.Host[1:] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		list = append(list, u) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return list, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|   IsTrustedIssuer returns true when the `iss` (i.e. from a token) matches one |  | ||||||
|   in the provided whitelist (also matches wildcard domains). |  | ||||||
| 
 |  | ||||||
|   You may explicitly allow insecure http (i.e. for automated testing) by |  | ||||||
|   including http:// Otherwise the scheme in each item of the whitelist should |  | ||||||
| 	include the "https://" prefix. |  | ||||||
| 
 |  | ||||||
|   SECURITY CONSIDERATIONS (Please Read) |  | ||||||
| 
 |  | ||||||
|   You'll notice that *http.Request is optional. It should only be used under these |  | ||||||
|   three circumstances: |  | ||||||
| 
 |  | ||||||
|     1) Something else guarantees http -> https redirection happens before the |  | ||||||
|        connection gets here AND this server directly handles TLS/SSL. |  | ||||||
| 
 |  | ||||||
|     2) If you're using a load balancer or web server, and this doesn't handle |  | ||||||
|        TLS/SSL directly, that server is _explicitly_ configured to protect |  | ||||||
|        against Domain Fronting attacks. As of 2019, most web servers and load |  | ||||||
|        balancers do not protect against that by default. |  | ||||||
| 
 |  | ||||||
|     3) If you only use it to make your automated integration testing more |  | ||||||
|        and it isn't enabled in production. |  | ||||||
| 
 |  | ||||||
|   Otherwise, DO NOT pass in *http.Request as you will introduce a 0-day |  | ||||||
|   vulnerability allowing an attacker to spoof any token issuer of their choice. |  | ||||||
|   The only reason I allowed this in a public library where non-experts would |  | ||||||
|   encounter it is to make testing easier. |  | ||||||
| */ |  | ||||||
| func (w Whitelist) IsTrustedIssuer(iss string, rs ...*http.Request) bool { |  | ||||||
| 	return isTrustedIssuer(iss, w, rs...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String will generate a space-delimited list of whitelisted URLs |  | ||||||
| func (w Whitelist) String() string { |  | ||||||
| 	s := []string{} |  | ||||||
| 	for i := range w { |  | ||||||
| 		s = append(s, w[i].String()) |  | ||||||
| 	} |  | ||||||
| 	return strings.Join(s, " ") |  | ||||||
| } |  | ||||||
							
								
								
									
										183
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										183
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,183 +0,0 @@ | |||||||
| // Package uncached provides uncached versions of go-keypairs/keyfetch |  | ||||||
| package uncached |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.rootprojects.org/root/keypairs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // OIDCJWKs gets the OpenID Connect configuration from the baseURL and then calls JWKs with the specified jwks_uri |  | ||||||
| func OIDCJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { |  | ||||||
| 	baseURL = normalizeBaseURL(baseURL) |  | ||||||
| 	oidcConf := struct { |  | ||||||
| 		JWKSURI string `json:"jwks_uri"` |  | ||||||
| 	}{} |  | ||||||
| 
 |  | ||||||
| 	// must come in as https://<domain>/ |  | ||||||
| 	url := baseURL + ".well-known/openid-configuration" |  | ||||||
| 	err := safeFetch(url, func(body io.Reader) error { |  | ||||||
| 		decoder := json.NewDecoder(body) |  | ||||||
| 		decoder.UseNumber() |  | ||||||
| 		return decoder.Decode(&oidcConf) |  | ||||||
| 	}) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return JWKs(oidcConf.JWKSURI) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WellKnownJWKs calls JWKs with baseURL + /.well-known/jwks.json as constructs the jwks_uri |  | ||||||
| func WellKnownJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { |  | ||||||
| 	baseURL = normalizeBaseURL(baseURL) |  | ||||||
| 	url := baseURL + ".well-known/jwks.json" |  | ||||||
| 
 |  | ||||||
| 	return JWKs(url) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JWKs fetches and parses a jwks.json (assuming well-known format) |  | ||||||
| func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { |  | ||||||
| 	keys := map[string]keypairs.PublicKey{} |  | ||||||
| 	maps := map[string]map[string]string{} |  | ||||||
| 	resp := struct { |  | ||||||
| 		Keys []map[string]interface{} `json:"keys"` |  | ||||||
| 	}{ |  | ||||||
| 		Keys: make([]map[string]interface{}, 0, 1), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := safeFetch(jwksurl, func(body io.Reader) error { |  | ||||||
| 		decoder := json.NewDecoder(body) |  | ||||||
| 		decoder.UseNumber() |  | ||||||
| 		return decoder.Decode(&resp) |  | ||||||
| 	}); nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i := range resp.Keys { |  | ||||||
| 		k := resp.Keys[i] |  | ||||||
| 		m := getStringMap(k) |  | ||||||
| 
 |  | ||||||
| 		key, err := keypairs.NewJWKPublicKey(m) |  | ||||||
| 
 |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, nil, err |  | ||||||
| 		} |  | ||||||
| 		keys[key.Thumbprint()] = key |  | ||||||
| 		maps[key.Thumbprint()] = m |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return maps, keys, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PEM fetches and parses a PEM (assuming well-known format) |  | ||||||
| func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) { |  | ||||||
| 	var pub keypairs.PublicKey |  | ||||||
| 	if err := safeFetch(pemurl, func(body io.Reader) error { |  | ||||||
| 		pem, err := ioutil.ReadAll(body) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		pub, err = keypairs.ParsePublicKey(pem) |  | ||||||
| 		return err |  | ||||||
| 	}); nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jwk := map[string]interface{}{} |  | ||||||
| 	body := bytes.NewBuffer(keypairs.MarshalJWKPublicKey(pub)) |  | ||||||
| 	decoder := json.NewDecoder(body) |  | ||||||
| 	decoder.UseNumber() |  | ||||||
| 	_ = decoder.Decode(&jwk) |  | ||||||
| 
 |  | ||||||
| 	m := getStringMap(jwk) |  | ||||||
| 	m["kid"] = pemurl |  | ||||||
| 
 |  | ||||||
| 	switch p := pub.(type) { |  | ||||||
| 	case *keypairs.ECPublicKey: |  | ||||||
| 		p.KID = pemurl |  | ||||||
| 	case *keypairs.RSAPublicKey: |  | ||||||
| 		p.KID = pemurl |  | ||||||
| 	default: |  | ||||||
| 		return nil, nil, errors.New("impossible key type") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return m, pub, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Fetch retrieves a single JWK (plain, bare jwk) from a URL (off-spec) |  | ||||||
| func Fetch(url string) (map[string]string, keypairs.PublicKey, error) { |  | ||||||
| 	var m map[string]interface{} |  | ||||||
| 	if err := safeFetch(url, func(body io.Reader) error { |  | ||||||
| 		decoder := json.NewDecoder(body) |  | ||||||
| 		decoder.UseNumber() |  | ||||||
| 		return decoder.Decode(&m) |  | ||||||
| 	}); nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	n := getStringMap(m) |  | ||||||
| 	key, err := keypairs.NewJWKPublicKey(n) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return n, key, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getStringMap(m map[string]interface{}) map[string]string { |  | ||||||
| 	n := make(map[string]string) |  | ||||||
| 
 |  | ||||||
| 	// TODO get issuer from x5c, if exists |  | ||||||
| 
 |  | ||||||
| 	// convert map[string]interface{} to map[string]string |  | ||||||
| 	for j := range m { |  | ||||||
| 		switch s := m[j].(type) { |  | ||||||
| 		case string: |  | ||||||
| 			n[j] = s |  | ||||||
| 		default: |  | ||||||
| 			// safely ignore |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return n |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type decodeFunc func(io.Reader) error |  | ||||||
| 
 |  | ||||||
| // TODO: also limit the body size |  | ||||||
| func safeFetch(url string, decoder decodeFunc) error { |  | ||||||
| 	var netTransport = &http.Transport{ |  | ||||||
| 		Dial: (&net.Dialer{ |  | ||||||
| 			Timeout: 5 * time.Second, |  | ||||||
| 		}).Dial, |  | ||||||
| 		TLSHandshakeTimeout: 5 * time.Second, |  | ||||||
| 	} |  | ||||||
| 	var client = &http.Client{ |  | ||||||
| 		Timeout:   time.Second * 10, |  | ||||||
| 		Transport: netTransport, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	req, err := http.NewRequest("GET", url, nil) |  | ||||||
| 	req.Header.Set("User-Agent", "go-keypairs/keyfetch") |  | ||||||
| 	req.Header.Set("Accept", "application/json;q=0.9,*/*;q=0.8") |  | ||||||
| 	res, err := client.Do(req) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer res.Body.Close() |  | ||||||
| 
 |  | ||||||
| 	return decoder(res.Body) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func normalizeBaseURL(iss string) string { |  | ||||||
| 	return strings.TrimRight(iss, "/") + "/" |  | ||||||
| } |  | ||||||
							
								
								
									
										645
									
								
								vendor/git.rootprojects.org/root/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										645
									
								
								vendor/git.rootprojects.org/root/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,645 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/dsa" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/elliptic" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"math/big" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ErrInvalidPrivateKey means that the key is not a valid Private Key |  | ||||||
| var ErrInvalidPrivateKey = errors.New("PrivateKey must be of type *rsa.PrivateKey or *ecdsa.PrivateKey") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidPublicKey means that the key is not a valid Public Key |  | ||||||
| var ErrInvalidPublicKey = errors.New("PublicKey must be of type *rsa.PublicKey or *ecdsa.PublicKey") |  | ||||||
| 
 |  | ||||||
| // ErrParsePublicKey means that the bytes cannot be parsed in any known format |  | ||||||
| var ErrParsePublicKey = errors.New("PublicKey bytes could not be parsed as PEM or DER (PKIX/SPKI, PKCS1, or X509 Certificate) or JWK") |  | ||||||
| 
 |  | ||||||
| // ErrParsePrivateKey means that the bytes cannot be parsed in any known format |  | ||||||
| var ErrParsePrivateKey = errors.New("PrivateKey bytes could not be parsed as PEM or DER (PKCS8, SEC1, or PKCS1) or JWK") |  | ||||||
| 
 |  | ||||||
| // ErrParseJWK means that the JWK is valid JSON but not a valid JWK |  | ||||||
| var ErrParseJWK = errors.New("JWK is missing required base64-encoded JSON fields") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidKeyType means that the key is not an acceptable type |  | ||||||
| var ErrInvalidKeyType = errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'") |  | ||||||
| 
 |  | ||||||
| // ErrInvalidCurve means that a non-standard curve was used |  | ||||||
| var ErrInvalidCurve = errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'") |  | ||||||
| 
 |  | ||||||
| // ErrUnexpectedPublicKey means that a Private Key was expected |  | ||||||
| var ErrUnexpectedPublicKey = errors.New("PrivateKey was given where PublicKey was expected") |  | ||||||
| 
 |  | ||||||
| // ErrUnexpectedPrivateKey means that a Public Key was expected |  | ||||||
| var ErrUnexpectedPrivateKey = errors.New("PublicKey was given where PrivateKey was expected") |  | ||||||
| 
 |  | ||||||
| // ErrDevSwapPrivatePublic means that the developer compiled bad code that swapped public and private keys |  | ||||||
| const ErrDevSwapPrivatePublic = "[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected." |  | ||||||
| 
 |  | ||||||
| // ErrDevBadKeyType means that the developer compiled bad code that passes the wrong type |  | ||||||
| const ErrDevBadKeyType = "[Developer Error] crypto.PublicKey and crypto.PrivateKey are somewhat deceptive. They're actually empty interfaces that accept any object, even non-crypto objects. You passed an object of type '%T' by mistake." |  | ||||||
| 
 |  | ||||||
| // PrivateKey is a zero-cost typesafe substitue for crypto.PrivateKey |  | ||||||
| type PrivateKey interface { |  | ||||||
| 	Public() crypto.PublicKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PublicKey thinly veils crypto.PublicKey for type safety |  | ||||||
| type PublicKey interface { |  | ||||||
| 	crypto.PublicKey |  | ||||||
| 	Thumbprint() string |  | ||||||
| 	KeyID() string |  | ||||||
| 	Key() crypto.PublicKey |  | ||||||
| 	ExpiresAt() time.Time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ECPublicKey adds common methods to *ecdsa.PublicKey for type safety |  | ||||||
| type ECPublicKey struct { |  | ||||||
| 	PublicKey *ecdsa.PublicKey // empty interface |  | ||||||
| 	KID       string |  | ||||||
| 	Expiry    time.Time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RSAPublicKey adds common methods to *rsa.PublicKey for type safety |  | ||||||
| type RSAPublicKey struct { |  | ||||||
| 	PublicKey *rsa.PublicKey // empty interface |  | ||||||
| 	KID       string |  | ||||||
| 	Expiry    time.Time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk |  | ||||||
| func (p *ECPublicKey) Thumbprint() string { |  | ||||||
| 	return ThumbprintUntypedPublicKey(p.PublicKey) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library |  | ||||||
| func (p *ECPublicKey) KeyID() string { |  | ||||||
| 	return p.KID |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Key returns the PublicKey |  | ||||||
| func (p *ECPublicKey) Key() crypto.PublicKey { |  | ||||||
| 	return p.PublicKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ExpireAt sets the time at which this Public Key should be considered invalid |  | ||||||
| func (p *ECPublicKey) ExpireAt(t time.Time) { |  | ||||||
| 	p.Expiry = t |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ExpiresAt gets the time at which this Public Key should be considered invalid |  | ||||||
| func (p *ECPublicKey) ExpiresAt() time.Time { |  | ||||||
| 	return p.Expiry |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk |  | ||||||
| func (p *RSAPublicKey) Thumbprint() string { |  | ||||||
| 	return ThumbprintUntypedPublicKey(p.PublicKey) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library |  | ||||||
| func (p *RSAPublicKey) KeyID() string { |  | ||||||
| 	return p.KID |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Key returns the PublicKey |  | ||||||
| func (p *RSAPublicKey) Key() crypto.PublicKey { |  | ||||||
| 	return p.PublicKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ExpireAt sets the time at which this Public Key should be considered invalid |  | ||||||
| func (p *RSAPublicKey) ExpireAt(t time.Time) { |  | ||||||
| 	p.Expiry = t |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ExpiresAt gets the time at which this Public Key should be considered invalid |  | ||||||
| func (p *RSAPublicKey) ExpiresAt() time.Time { |  | ||||||
| 	return p.Expiry |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewPublicKey wraps a crypto.PublicKey to make it typesafe. |  | ||||||
| func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey { |  | ||||||
| 	var k PublicKey |  | ||||||
| 	switch p := pub.(type) { |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		eckey := &ECPublicKey{ |  | ||||||
| 			PublicKey: p, |  | ||||||
| 		} |  | ||||||
| 		if 0 != len(kid) { |  | ||||||
| 			eckey.KID = kid[0] |  | ||||||
| 		} else { |  | ||||||
| 			eckey.KID = ThumbprintECPublicKey(p) |  | ||||||
| 		} |  | ||||||
| 		k = eckey |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		rsakey := &RSAPublicKey{ |  | ||||||
| 			PublicKey: p, |  | ||||||
| 		} |  | ||||||
| 		if 0 != len(kid) { |  | ||||||
| 			rsakey.KID = kid[0] |  | ||||||
| 		} else { |  | ||||||
| 			rsakey.KID = ThumbprintRSAPublicKey(p) |  | ||||||
| 		} |  | ||||||
| 		k = rsakey |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		panic(errors.New(ErrDevSwapPrivatePublic)) |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		panic(errors.New(ErrDevSwapPrivatePublic)) |  | ||||||
| 	case *dsa.PublicKey: |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 	case *dsa.PrivateKey: |  | ||||||
| 		panic(ErrInvalidPrivateKey) |  | ||||||
| 	default: |  | ||||||
| 		panic(fmt.Errorf(ErrDevBadKeyType, pub)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return k |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalJWKPublicKey outputs a JWK with its key id (kid) and an optional expiration, |  | ||||||
| // making it suitable for use as an OIDC public key. |  | ||||||
| func MarshalJWKPublicKey(key PublicKey, exp ...time.Time) []byte { |  | ||||||
| 	// thumbprint keys are alphabetically sorted and only include the necessary public parts |  | ||||||
| 	switch k := key.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		return MarshalRSAPublicKey(k, exp...) |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		return MarshalECPublicKey(k, exp...) |  | ||||||
| 	case *dsa.PublicKey: |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", key, key) |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ThumbprintPublicKey returns the SHA256 RFC-spec JWK thumbprint |  | ||||||
| func ThumbprintPublicKey(pub PublicKey) string { |  | ||||||
| 	return ThumbprintUntypedPublicKey(pub.Key()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ThumbprintUntypedPublicKey is a non-typesafe version of ThumbprintPublicKey |  | ||||||
| // (but will still panic, to help you discover bugs in development rather than production). |  | ||||||
| func ThumbprintUntypedPublicKey(pub crypto.PublicKey) string { |  | ||||||
| 	switch p := pub.(type) { |  | ||||||
| 	case PublicKey: |  | ||||||
| 		return ThumbprintUntypedPublicKey(p.Key()) |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		return ThumbprintECPublicKey(p) |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		return ThumbprintRSAPublicKey(p) |  | ||||||
| 	default: |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalECPublicKey will take an EC key and output a JWK, with optional expiration date |  | ||||||
| func MarshalECPublicKey(k *ecdsa.PublicKey, exp ...time.Time) []byte { |  | ||||||
| 	thumb := ThumbprintECPublicKey(k) |  | ||||||
| 	crv := k.Curve.Params().Name |  | ||||||
| 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) |  | ||||||
| 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) |  | ||||||
| 	expstr := "" |  | ||||||
| 	if 0 != len(exp) { |  | ||||||
| 		expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix()) |  | ||||||
| 	} |  | ||||||
| 	return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"crv":%q,"kty":"EC","x":%q,"y":%q}`, thumb, expstr, crv, x, y)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalECPublicKeyWithoutKeyID will output the most minimal version of an EC JWK (no key id, no "use" flag, nada) |  | ||||||
| func MarshalECPublicKeyWithoutKeyID(k *ecdsa.PublicKey) []byte { |  | ||||||
| 	crv := k.Curve.Params().Name |  | ||||||
| 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) |  | ||||||
| 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, crv, x, y)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ThumbprintECPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key |  | ||||||
| func ThumbprintECPublicKey(k *ecdsa.PublicKey) string { |  | ||||||
| 	thumbprintable := MarshalECPublicKeyWithoutKeyID(k) |  | ||||||
| 	sha := sha256.Sum256(thumbprintable) |  | ||||||
| 	return base64.RawURLEncoding.EncodeToString(sha[:]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalRSAPublicKey will take an RSA key and output a JWK, with optional expiration date |  | ||||||
| func MarshalRSAPublicKey(p *rsa.PublicKey, exp ...time.Time) []byte { |  | ||||||
| 	thumb := ThumbprintRSAPublicKey(p) |  | ||||||
| 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()) |  | ||||||
| 	n := base64.RawURLEncoding.EncodeToString(p.N.Bytes()) |  | ||||||
| 	expstr := "" |  | ||||||
| 	if 0 != len(exp) { |  | ||||||
| 		expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix()) |  | ||||||
| 	} |  | ||||||
| 	return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"e":%q,"kty":"RSA","n":%q}`, thumb, expstr, e, n)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalRSAPublicKeyWithoutKeyID will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada) |  | ||||||
| func MarshalRSAPublicKeyWithoutKeyID(p *rsa.PublicKey) []byte { |  | ||||||
| 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()) |  | ||||||
| 	n := base64.RawURLEncoding.EncodeToString(p.N.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, e, n)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ThumbprintRSAPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key |  | ||||||
| func ThumbprintRSAPublicKey(p *rsa.PublicKey) string { |  | ||||||
| 	thumbprintable := MarshalRSAPublicKeyWithoutKeyID(p) |  | ||||||
| 	sha := sha256.Sum256([]byte(thumbprintable)) |  | ||||||
| 	return base64.RawURLEncoding.EncodeToString(sha[:]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParsePrivateKey will try to parse the bytes you give it |  | ||||||
| // in any of the supported formats: PEM, DER, PKCS8, PKCS1, SEC1, and JWK |  | ||||||
| func ParsePrivateKey(block []byte) (PrivateKey, error) { |  | ||||||
| 	blocks, err := getPEMBytes(block) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, ErrParsePrivateKey |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Parse PEM blocks (openssl generates junk metadata blocks for ECs) |  | ||||||
| 	// or the original DER, or the JWK |  | ||||||
| 	for i := range blocks { |  | ||||||
| 		block = blocks[i] |  | ||||||
| 		if key, err := parsePrivateKey(block); nil == err { |  | ||||||
| 			return key, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i := range blocks { |  | ||||||
| 		block = blocks[i] |  | ||||||
| 		if _, err := parsePublicKey(block); nil == err { |  | ||||||
| 			return nil, ErrUnexpectedPublicKey |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// If we didn't parse a key arleady, we failed |  | ||||||
| 	return nil, ErrParsePrivateKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParsePrivateKeyString calls ParsePrivateKey([]byte(key)) for all you lazy folk. |  | ||||||
| func ParsePrivateKeyString(block string) (PrivateKey, error) { |  | ||||||
| 	return ParsePrivateKey([]byte(block)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parsePrivateKey(der []byte) (PrivateKey, error) { |  | ||||||
| 	var key PrivateKey |  | ||||||
| 
 |  | ||||||
| 	//fmt.Println("1. ParsePKCS8PrivateKey") |  | ||||||
| 	xkey, err := x509.ParsePKCS8PrivateKey(der) |  | ||||||
| 	if nil == err { |  | ||||||
| 		switch k := xkey.(type) { |  | ||||||
| 		case *rsa.PrivateKey: |  | ||||||
| 			key = k |  | ||||||
| 		case *ecdsa.PrivateKey: |  | ||||||
| 			key = k |  | ||||||
| 		default: |  | ||||||
| 			err = errors.New("Only RSA and ECDSA (EC) Private Keys are supported") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if nil != err { |  | ||||||
| 		//fmt.Println("2. ParseECPrivateKey") |  | ||||||
| 		key, err = x509.ParseECPrivateKey(der) |  | ||||||
| 		if nil != err { |  | ||||||
| 			//fmt.Println("3. ParsePKCS1PrivateKey") |  | ||||||
| 			key, err = x509.ParsePKCS1PrivateKey(der) |  | ||||||
| 			if nil != err { |  | ||||||
| 				//fmt.Println("4. ParseJWKPrivateKey") |  | ||||||
| 				key, err = ParseJWKPrivateKey(der) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// But did you know? |  | ||||||
| 	// You must return nil explicitly for interfaces |  | ||||||
| 	// https://golang.org/doc/faq#nil_error |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return key, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getPEMBytes(block []byte) ([][]byte, error) { |  | ||||||
| 	var pemblock *pem.Block |  | ||||||
| 	var blocks = make([][]byte, 0, 1) |  | ||||||
| 
 |  | ||||||
| 	// Parse the PEM, if it's a pem |  | ||||||
| 	for { |  | ||||||
| 		pemblock, block = pem.Decode(block) |  | ||||||
| 		if nil != pemblock { |  | ||||||
| 			// got one block, there may be more |  | ||||||
| 			blocks = append(blocks, pemblock.Bytes) |  | ||||||
| 		} else { |  | ||||||
| 			// the last block was not a PEM block |  | ||||||
| 			// therefore the next isn't either |  | ||||||
| 			if 0 != len(block) { |  | ||||||
| 				blocks = append(blocks, block) |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(blocks) > 0 { |  | ||||||
| 		return blocks, nil |  | ||||||
| 	} |  | ||||||
| 	return nil, errors.New("no PEM blocks found") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParsePublicKey will try to parse the bytes you give it |  | ||||||
| // in any of the supported formats: PEM, DER, PKIX/SPKI, PKCS1, x509 Certificate, and JWK |  | ||||||
| func ParsePublicKey(block []byte) (PublicKey, error) { |  | ||||||
| 	blocks, err := getPEMBytes(block) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, ErrParsePublicKey |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Parse PEM blocks (openssl generates junk metadata blocks for ECs) |  | ||||||
| 	// or the original DER, or the JWK |  | ||||||
| 	for i := range blocks { |  | ||||||
| 		block = blocks[i] |  | ||||||
| 		if key, err := parsePublicKey(block); nil == err { |  | ||||||
| 			return key, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i := range blocks { |  | ||||||
| 		block = blocks[i] |  | ||||||
| 		if _, err := parsePrivateKey(block); nil == err { |  | ||||||
| 			return nil, ErrUnexpectedPrivateKey |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// If we didn't parse a key arleady, we failed |  | ||||||
| 	return nil, ErrParsePublicKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParsePublicKeyString calls ParsePublicKey([]byte(key)) for all you lazy folk. |  | ||||||
| func ParsePublicKeyString(block string) (PublicKey, error) { |  | ||||||
| 	return ParsePublicKey([]byte(block)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parsePublicKey(der []byte) (PublicKey, error) { |  | ||||||
| 	cert, err := x509.ParseCertificate(der) |  | ||||||
| 	if nil == err { |  | ||||||
| 		switch k := cert.PublicKey.(type) { |  | ||||||
| 		case *rsa.PublicKey: |  | ||||||
| 			return NewPublicKey(k), nil |  | ||||||
| 		case *ecdsa.PublicKey: |  | ||||||
| 			return NewPublicKey(k), nil |  | ||||||
| 		default: |  | ||||||
| 			return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	//fmt.Println("1. ParsePKIXPublicKey") |  | ||||||
| 	xkey, err := x509.ParsePKIXPublicKey(der) |  | ||||||
| 	if nil == err { |  | ||||||
| 		switch k := xkey.(type) { |  | ||||||
| 		case *rsa.PublicKey: |  | ||||||
| 			return NewPublicKey(k), nil |  | ||||||
| 		case *ecdsa.PublicKey: |  | ||||||
| 			return NewPublicKey(k), nil |  | ||||||
| 		default: |  | ||||||
| 			return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	//fmt.Println("3. ParsePKCS1PrublicKey") |  | ||||||
| 	rkey, err := x509.ParsePKCS1PublicKey(der) |  | ||||||
| 	if nil == err { |  | ||||||
| 		//fmt.Println("4. ParseJWKPublicKey") |  | ||||||
| 		return NewPublicKey(rkey), nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return ParseJWKPublicKey(der) |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		// But did you know? |  | ||||||
| 		// You must return nil explicitly for interfaces |  | ||||||
| 		// https://golang.org/doc/faq#nil_error |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	*/ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON) |  | ||||||
| func NewJWKPublicKey(m map[string]string) (PublicKey, error) { |  | ||||||
| 	switch m["kty"] { |  | ||||||
| 	case "RSA": |  | ||||||
| 		return parseRSAPublicKey(m) |  | ||||||
| 	case "EC": |  | ||||||
| 		return parseECPublicKey(m) |  | ||||||
| 	default: |  | ||||||
| 		return nil, ErrInvalidKeyType |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseJWKPublicKey parses a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message |  | ||||||
| func ParseJWKPublicKey(b []byte) (PublicKey, error) { |  | ||||||
| 	// RSA and EC have "d" as a private part |  | ||||||
| 	if bytes.Contains(b, []byte(`"d"`)) { |  | ||||||
| 		return nil, ErrUnexpectedPrivateKey |  | ||||||
| 	} |  | ||||||
| 	return newJWKPublicKey(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseJWKPublicKeyString calls ParseJWKPublicKey([]byte(key)) for all you lazy folk. |  | ||||||
| func ParseJWKPublicKeyString(s string) (PublicKey, error) { |  | ||||||
| 	if strings.Contains(s, `"d"`) { |  | ||||||
| 		return nil, ErrUnexpectedPrivateKey |  | ||||||
| 	} |  | ||||||
| 	return newJWKPublicKey(s) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DecodeJWKPublicKey stream-decodes a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message |  | ||||||
| func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) { |  | ||||||
| 	m := make(map[string]string) |  | ||||||
| 	if err := json.NewDecoder(r).Decode(&m); nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if d := m["d"]; "" != d { |  | ||||||
| 		return nil, ErrUnexpectedPrivateKey |  | ||||||
| 	} |  | ||||||
| 	return newJWKPublicKey(m) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // the underpinnings of the parser as used by the typesafe wrappers |  | ||||||
| func newJWKPublicKey(data interface{}) (PublicKey, error) { |  | ||||||
| 	var m map[string]string |  | ||||||
| 
 |  | ||||||
| 	switch d := data.(type) { |  | ||||||
| 	case map[string]string: |  | ||||||
| 		m = d |  | ||||||
| 	case string: |  | ||||||
| 		if err := json.Unmarshal([]byte(d), &m); nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	case []byte: |  | ||||||
| 		if err := json.Unmarshal(d, &m); nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		panic("Developer Error: unsupported interface type") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return NewJWKPublicKey(m) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseJWKPrivateKey parses a JSON-encoded JWK and returns a PrivateKey, or a (hopefully) helpful error message |  | ||||||
| func ParseJWKPrivateKey(b []byte) (PrivateKey, error) { |  | ||||||
| 	var m map[string]string |  | ||||||
| 	if err := json.Unmarshal(b, &m); nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch m["kty"] { |  | ||||||
| 	case "RSA": |  | ||||||
| 		return parseRSAPrivateKey(m) |  | ||||||
| 	case "EC": |  | ||||||
| 		return parseECPrivateKey(m) |  | ||||||
| 	default: |  | ||||||
| 		return nil, ErrInvalidKeyType |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseRSAPublicKey(m map[string]string) (*RSAPublicKey, error) { |  | ||||||
| 	// TODO grab expiry? |  | ||||||
| 	kid, _ := m["kid"] |  | ||||||
| 	n, _ := base64.RawURLEncoding.DecodeString(m["n"]) |  | ||||||
| 	e, _ := base64.RawURLEncoding.DecodeString(m["e"]) |  | ||||||
| 	if 0 == len(n) || 0 == len(e) { |  | ||||||
| 		return nil, ErrParseJWK |  | ||||||
| 	} |  | ||||||
| 	ni := &big.Int{} |  | ||||||
| 	ni.SetBytes(n) |  | ||||||
| 	ei := &big.Int{} |  | ||||||
| 	ei.SetBytes(e) |  | ||||||
| 
 |  | ||||||
| 	pub := &rsa.PublicKey{ |  | ||||||
| 		N: ni, |  | ||||||
| 		E: int(ei.Int64()), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &RSAPublicKey{ |  | ||||||
| 		PublicKey: pub, |  | ||||||
| 		KID:       kid, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseRSAPrivateKey(m map[string]string) (key *rsa.PrivateKey, err error) { |  | ||||||
| 	pub, err := parseRSAPublicKey(m) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	d, _ := base64.RawURLEncoding.DecodeString(m["d"]) |  | ||||||
| 	p, _ := base64.RawURLEncoding.DecodeString(m["p"]) |  | ||||||
| 	q, _ := base64.RawURLEncoding.DecodeString(m["q"]) |  | ||||||
| 	dp, _ := base64.RawURLEncoding.DecodeString(m["dp"]) |  | ||||||
| 	dq, _ := base64.RawURLEncoding.DecodeString(m["dq"]) |  | ||||||
| 	qinv, _ := base64.RawURLEncoding.DecodeString(m["qi"]) |  | ||||||
| 	if 0 == len(d) || 0 == len(p) || 0 == len(dp) || 0 == len(dq) || 0 == len(qinv) { |  | ||||||
| 		return nil, ErrParseJWK |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	di := &big.Int{} |  | ||||||
| 	di.SetBytes(d) |  | ||||||
| 	pi := &big.Int{} |  | ||||||
| 	pi.SetBytes(p) |  | ||||||
| 	qi := &big.Int{} |  | ||||||
| 	qi.SetBytes(q) |  | ||||||
| 	dpi := &big.Int{} |  | ||||||
| 	dpi.SetBytes(dp) |  | ||||||
| 	dqi := &big.Int{} |  | ||||||
| 	dqi.SetBytes(dq) |  | ||||||
| 	qinvi := &big.Int{} |  | ||||||
| 	qinvi.SetBytes(qinv) |  | ||||||
| 
 |  | ||||||
| 	key = &rsa.PrivateKey{ |  | ||||||
| 		PublicKey: *pub.PublicKey, |  | ||||||
| 		D:         di, |  | ||||||
| 		Primes:    []*big.Int{pi, qi}, |  | ||||||
| 		Precomputed: rsa.PrecomputedValues{ |  | ||||||
| 			Dp:   dpi, |  | ||||||
| 			Dq:   dqi, |  | ||||||
| 			Qinv: qinvi, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseECPublicKey(m map[string]string) (*ECPublicKey, error) { |  | ||||||
| 	// TODO grab expiry? |  | ||||||
| 	kid, _ := m["kid"] |  | ||||||
| 	x, _ := base64.RawURLEncoding.DecodeString(m["x"]) |  | ||||||
| 	y, _ := base64.RawURLEncoding.DecodeString(m["y"]) |  | ||||||
| 	if 0 == len(x) || 0 == len(y) || 0 == len(m["crv"]) { |  | ||||||
| 		return nil, ErrParseJWK |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	xi := &big.Int{} |  | ||||||
| 	xi.SetBytes(x) |  | ||||||
| 
 |  | ||||||
| 	yi := &big.Int{} |  | ||||||
| 	yi.SetBytes(y) |  | ||||||
| 
 |  | ||||||
| 	var crv elliptic.Curve |  | ||||||
| 	switch m["crv"] { |  | ||||||
| 	case "P-256": |  | ||||||
| 		crv = elliptic.P256() |  | ||||||
| 	case "P-384": |  | ||||||
| 		crv = elliptic.P384() |  | ||||||
| 	case "P-521": |  | ||||||
| 		crv = elliptic.P521() |  | ||||||
| 	default: |  | ||||||
| 		return nil, ErrInvalidCurve |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub := &ecdsa.PublicKey{ |  | ||||||
| 		Curve: crv, |  | ||||||
| 		X:     xi, |  | ||||||
| 		Y:     yi, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &ECPublicKey{ |  | ||||||
| 		PublicKey: pub, |  | ||||||
| 		KID:       kid, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseECPrivateKey(m map[string]string) (*ecdsa.PrivateKey, error) { |  | ||||||
| 	pub, err := parseECPublicKey(m) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	d, _ := base64.RawURLEncoding.DecodeString(m["d"]) |  | ||||||
| 	if 0 == len(d) { |  | ||||||
| 		return nil, ErrParseJWK |  | ||||||
| 	} |  | ||||||
| 	di := &big.Int{} |  | ||||||
| 	di.SetBytes(d) |  | ||||||
| 
 |  | ||||||
| 	return &ecdsa.PrivateKey{ |  | ||||||
| 		PublicKey: *pub.PublicKey, |  | ||||||
| 		D:         di, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										171
									
								
								vendor/git.rootprojects.org/root/keypairs/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										171
									
								
								vendor/git.rootprojects.org/root/keypairs/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,171 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"math/big" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // MarshalPEMPublicKey outputs the given public key as JWK |  | ||||||
| func MarshalPEMPublicKey(pubkey crypto.PublicKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPublicKey(pubkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(block), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalDERPublicKey outputs the given public key as JWK |  | ||||||
| func MarshalDERPublicKey(pubkey crypto.PublicKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPublicKey(pubkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return block.Bytes, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // marshalDERPublicKey outputs the given public key as JWK |  | ||||||
| func marshalDERPublicKey(pubkey crypto.PublicKey) (*pem.Block, error) { |  | ||||||
| 
 |  | ||||||
| 	var der []byte |  | ||||||
| 	var typ string |  | ||||||
| 	var err error |  | ||||||
| 	switch k := pubkey.(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		der = x509.MarshalPKCS1PublicKey(k) |  | ||||||
| 		typ = "RSA PUBLIC KEY" |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		typ = "PUBLIC KEY" |  | ||||||
| 		der, err = x509.MarshalPKIXPublicKey(k) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		panic("Developer Error: impossible key type") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &pem.Block{ |  | ||||||
| 		Bytes: der, |  | ||||||
| 		Type:  typ, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalJWKPrivateKey outputs the given private key as JWK |  | ||||||
| func MarshalJWKPrivateKey(privkey PrivateKey) []byte { |  | ||||||
| 	// thumbprint keys are alphabetically sorted and only include the necessary public parts |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		return MarshalRSAPrivateKey(k) |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		return MarshalECPrivateKey(k) |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 		//return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalDERPrivateKey outputs the given private key as ASN.1 DER |  | ||||||
| func MarshalDERPrivateKey(privkey PrivateKey) ([]byte, error) { |  | ||||||
| 	// thumbprint keys are alphabetically sorted and only include the necessary public parts |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		return x509.MarshalPKCS1PrivateKey(k), nil |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		return x509.MarshalECPrivateKey(k) |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 		//return nil, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func marshalDERPrivateKey(privkey PrivateKey) (*pem.Block, error) { |  | ||||||
| 	var typ string |  | ||||||
| 	var bytes []byte |  | ||||||
| 	var err error |  | ||||||
| 
 |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		if 0 == mathrand.Intn(2) { |  | ||||||
| 			typ = "PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalPKCS8PrivateKey(k) |  | ||||||
| 			if nil != err { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			typ = "RSA PRIVATE KEY" |  | ||||||
| 			bytes = x509.MarshalPKCS1PrivateKey(k) |  | ||||||
| 		} |  | ||||||
| 		return &pem.Block{ |  | ||||||
| 			Type:  typ, |  | ||||||
| 			Bytes: bytes, |  | ||||||
| 		}, nil |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		if 0 == mathrand.Intn(2) { |  | ||||||
| 			typ = "PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalPKCS8PrivateKey(k) |  | ||||||
| 		} else { |  | ||||||
| 			typ = "EC PRIVATE KEY" |  | ||||||
| 			bytes, err = x509.MarshalECPrivateKey(k) |  | ||||||
| 		} |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return &pem.Block{ |  | ||||||
| 			Type:  typ, |  | ||||||
| 			Bytes: bytes, |  | ||||||
| 		}, nil |  | ||||||
| 	default: |  | ||||||
| 		// this is unreachable because we know the types that we pass in |  | ||||||
| 		log.Printf("keytype: %t, %+v\n", privkey, privkey) |  | ||||||
| 		panic(ErrInvalidPublicKey) |  | ||||||
| 		//return nil, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalPEMPrivateKey outputs the given private key as ASN.1 PEM |  | ||||||
| func MarshalPEMPrivateKey(privkey PrivateKey) ([]byte, error) { |  | ||||||
| 	block, err := marshalDERPrivateKey(privkey) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(block), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalECPrivateKey will output the given private key as JWK |  | ||||||
| func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte { |  | ||||||
| 	crv := k.Curve.Params().Name |  | ||||||
| 	d := base64.RawURLEncoding.EncodeToString(k.D.Bytes()) |  | ||||||
| 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) |  | ||||||
| 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf( |  | ||||||
| 		`{"crv":%q,"d":%q,"kty":"EC","x":%q,"y":%q}`, |  | ||||||
| 		crv, d, x, y, |  | ||||||
| 	)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalRSAPrivateKey will output the given private key as JWK |  | ||||||
| func MarshalRSAPrivateKey(pk *rsa.PrivateKey) []byte { |  | ||||||
| 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pk.E)).Bytes()) |  | ||||||
| 	n := base64.RawURLEncoding.EncodeToString(pk.N.Bytes()) |  | ||||||
| 	d := base64.RawURLEncoding.EncodeToString(pk.D.Bytes()) |  | ||||||
| 	p := base64.RawURLEncoding.EncodeToString(pk.Primes[0].Bytes()) |  | ||||||
| 	q := base64.RawURLEncoding.EncodeToString(pk.Primes[1].Bytes()) |  | ||||||
| 	dp := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dp.Bytes()) |  | ||||||
| 	dq := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dq.Bytes()) |  | ||||||
| 	qi := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Qinv.Bytes()) |  | ||||||
| 	return []byte(fmt.Sprintf( |  | ||||||
| 		`{"d":%q,"dp":%q,"dq":%q,"e":%q,"kty":"RSA","n":%q,"p":%q,"q":%q,"qi":%q}`, |  | ||||||
| 		d, dp, dq, e, n, p, q, qi, |  | ||||||
| 	)) |  | ||||||
| } |  | ||||||
							
								
								
									
										46
									
								
								vendor/git.rootprojects.org/root/keypairs/mock.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								vendor/git.rootprojects.org/root/keypairs/mock.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,46 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	mathrand "math/rand" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // this shananigans is only for testing and debug API stuff |  | ||||||
| func (o *keyOptions) maybeMockReader() io.Reader { |  | ||||||
| 	if !allowMocking { |  | ||||||
| 		panic("mock method called when mocking is not allowed") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if 0 == o.mockSeed { |  | ||||||
| 		return randReader |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	log.Println("WARNING: MOCK: using insecure reader") |  | ||||||
| 	return mathrand.New(mathrand.NewSource(o.mockSeed)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const maxRetry = 16 |  | ||||||
| 
 |  | ||||||
| func maybeDerandomizeMockKey(privkey PrivateKey, keylen int, opts *keyOptions) PrivateKey { |  | ||||||
| 	if 0 != opts.mockSeed { |  | ||||||
| 		for i := 0; i < maxRetry; i++ { |  | ||||||
| 			otherkey, _ := rsa.GenerateKey(opts.nextReader(), keylen) |  | ||||||
| 			otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) |  | ||||||
| 			if 0 != otherCmp { |  | ||||||
| 				// There are two possible keys, choose the lesser D value |  | ||||||
| 				// See https://github.com/square/go-jose/issues/189 |  | ||||||
| 				if otherCmp < 0 { |  | ||||||
| 					privkey = otherkey |  | ||||||
| 				} |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			if maxRetry == i-1 { |  | ||||||
| 				log.Printf("error: coinflip landed on heads %d times", maxRetry) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return privkey |  | ||||||
| } |  | ||||||
							
								
								
									
										165
									
								
								vendor/git.rootprojects.org/root/keypairs/sign.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										165
									
								
								vendor/git.rootprojects.org/root/keypairs/sign.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,165 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	mathrand "math/rand" // to be used for good, not evil |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Object is a type alias representing generic JSON data |  | ||||||
| type Object = map[string]interface{} |  | ||||||
| 
 |  | ||||||
| // SignClaims adds `typ`, `kid` (or `jwk`), and `alg` in the header and expects claims for `jti`, `exp`, `iss`, and `iat` |  | ||||||
| func SignClaims(privkey PrivateKey, header Object, claims Object) (*JWS, error) { |  | ||||||
| 	var randsrc io.Reader = randReader |  | ||||||
| 	seed, _ := header["_seed"].(int64) |  | ||||||
| 	if 0 != seed { |  | ||||||
| 		randsrc = mathrand.New(mathrand.NewSource(seed)) |  | ||||||
| 		//delete(header, "_seed") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	protected, header, err := headerToProtected(NewPublicKey(privkey.Public()), header) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	protected64 := base64.RawURLEncoding.EncodeToString(protected) |  | ||||||
| 
 |  | ||||||
| 	payload, err := claimsToPayload(claims) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	payload64 := base64.RawURLEncoding.EncodeToString(payload) |  | ||||||
| 
 |  | ||||||
| 	signable := fmt.Sprintf(`%s.%s`, protected64, payload64) |  | ||||||
| 	hash := sha256.Sum256([]byte(signable)) |  | ||||||
| 
 |  | ||||||
| 	sig := Sign(privkey, hash[:], randsrc) |  | ||||||
| 	sig64 := base64.RawURLEncoding.EncodeToString(sig) |  | ||||||
| 	//log.Printf("\n(Sign)\nSignable: %s", signable) |  | ||||||
| 	//log.Printf("Hash: %s", hash) |  | ||||||
| 	//log.Printf("Sig: %s", sig64) |  | ||||||
| 
 |  | ||||||
| 	return &JWS{ |  | ||||||
| 		Header:    header, |  | ||||||
| 		Claims:    claims, |  | ||||||
| 		Protected: protected64, |  | ||||||
| 		Payload:   payload64, |  | ||||||
| 		Signature: sig64, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) { |  | ||||||
| 	if nil == header { |  | ||||||
| 		header = Object{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Only supporting 2048-bit and P256 keys right now |  | ||||||
| 	// because that's all that's practical and well-supported. |  | ||||||
| 	// No security theatre here. |  | ||||||
| 	alg := "ES256" |  | ||||||
| 	switch pub.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		alg = "RS256" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if selfSign, _ := header["_jwk"].(bool); selfSign { |  | ||||||
| 		delete(header, "_jwk") |  | ||||||
| 		any := Object{} |  | ||||||
| 		_ = json.Unmarshal(MarshalJWKPublicKey(pub), &any) |  | ||||||
| 		header["jwk"] = any |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// TODO what are the acceptable values? JWT. JWS? others? |  | ||||||
| 	header["typ"] = "JWT" |  | ||||||
| 	if _, ok := header["jwk"]; !ok { |  | ||||||
| 		thumbprint := ThumbprintPublicKey(pub) |  | ||||||
| 		kid, _ := header["kid"].(string) |  | ||||||
| 		if "" != kid && thumbprint != kid { |  | ||||||
| 			return nil, nil, errors.New("'kid' should be the key's thumbprint") |  | ||||||
| 		} |  | ||||||
| 		header["kid"] = thumbprint |  | ||||||
| 	} |  | ||||||
| 	header["alg"] = alg |  | ||||||
| 
 |  | ||||||
| 	protected, err := json.Marshal(header) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	return protected, header, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func claimsToPayload(claims Object) ([]byte, error) { |  | ||||||
| 	if nil == claims { |  | ||||||
| 		claims = Object{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var dur time.Duration |  | ||||||
| 	jti, _ := claims["jti"].(string) |  | ||||||
| 	insecure, _ := claims["insecure"].(bool) |  | ||||||
| 
 |  | ||||||
| 	switch exp := claims["exp"].(type) { |  | ||||||
| 	case time.Duration: |  | ||||||
| 		// TODO: MUST this go first? |  | ||||||
| 		// int64(time.Duration) vs time.Duration(int64) |  | ||||||
| 		dur = exp |  | ||||||
| 	case string: |  | ||||||
| 		var err error |  | ||||||
| 		dur, err = time.ParseDuration(exp) |  | ||||||
| 		// TODO s, err := time.ParseDuration(dur) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	case int: |  | ||||||
| 		dur = time.Second * time.Duration(exp) |  | ||||||
| 	case int64: |  | ||||||
| 		dur = time.Second * time.Duration(exp) |  | ||||||
| 	case float64: |  | ||||||
| 		dur = time.Second * time.Duration(exp) |  | ||||||
| 	default: |  | ||||||
| 		dur = 0 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if "" == jti && 0 == dur && !insecure { |  | ||||||
| 		return nil, errors.New("token must have jti or exp as to be expirable / cancellable") |  | ||||||
| 	} |  | ||||||
| 	claims["exp"] = time.Now().Add(dur).Unix() |  | ||||||
| 
 |  | ||||||
| 	return json.Marshal(claims) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Sign signs both RSA and ECDSA. Use `nil` or `crypto/rand.Reader` except for debugging. |  | ||||||
| func Sign(privkey PrivateKey, hash []byte, rand io.Reader) []byte { |  | ||||||
| 	if nil == rand { |  | ||||||
| 		rand = randReader |  | ||||||
| 	} |  | ||||||
| 	var sig []byte |  | ||||||
| 
 |  | ||||||
| 	if len(hash) != 32 { |  | ||||||
| 		panic("only 256-bit hashes for 2048-bit and 256-bit keys are supported") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch k := privkey.(type) { |  | ||||||
| 	case *rsa.PrivateKey: |  | ||||||
| 		sig, _ = rsa.SignPKCS1v15(rand, k, crypto.SHA256, hash) |  | ||||||
| 	case *ecdsa.PrivateKey: |  | ||||||
| 		r, s, _ := ecdsa.Sign(rand, k, hash[:]) |  | ||||||
| 		rb := r.Bytes() |  | ||||||
| 		for len(rb) < 32 { |  | ||||||
| 			rb = append([]byte{0}, rb...) |  | ||||||
| 		} |  | ||||||
| 		sb := s.Bytes() |  | ||||||
| 		for len(rb) < 32 { |  | ||||||
| 			sb = append([]byte{0}, sb...) |  | ||||||
| 		} |  | ||||||
| 		sig = append(rb, sb...) |  | ||||||
| 	} |  | ||||||
| 	return sig |  | ||||||
| } |  | ||||||
							
								
								
									
										174
									
								
								vendor/git.rootprojects.org/root/keypairs/verify.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										174
									
								
								vendor/git.rootprojects.org/root/keypairs/verify.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,174 +0,0 @@ | |||||||
| package keypairs |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"crypto/subtle" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"math/big" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // VerifyClaims will check the signature of a parsed JWT |  | ||||||
| func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) { |  | ||||||
| 	kid, _ := jws.Header["kid"].(string) |  | ||||||
| 	jwkmap, hasJWK := jws.Header["jwk"].(Object) |  | ||||||
| 	//var jwk JWK = nil |  | ||||||
| 
 |  | ||||||
| 	seed, _ := jws.Header["_seed"].(int64) |  | ||||||
| 	seedf64, _ := jws.Header["_seed"].(float64) |  | ||||||
| 	kty, _ := jws.Header["_kty"].(string) |  | ||||||
| 	if 0 == seed { |  | ||||||
| 		seed = int64(seedf64) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var pub PublicKey = nil |  | ||||||
| 	if hasJWK { |  | ||||||
| 		pub, errs = selfsignCheck(jwkmap, errs) |  | ||||||
| 	} else { |  | ||||||
| 		opts := &keyOptions{mockSeed: seed, KeyType: kty} |  | ||||||
| 		pub, errs = pubkeyCheck(pubkey, kid, opts, errs) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jti, _ := jws.Claims["jti"].(string) |  | ||||||
| 	expf64, _ := jws.Claims["exp"].(float64) |  | ||||||
| 	exp := int64(expf64) |  | ||||||
| 	if 0 == exp { |  | ||||||
| 		if "" == jti { |  | ||||||
| 			err := errors.New("one of 'jti' or 'exp' must exist for token expiry") |  | ||||||
| 			errs = append(errs, err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if time.Now().Unix() > exp { |  | ||||||
| 			err := fmt.Errorf("token expired at %d (%s)", exp, time.Unix(exp, 0)) |  | ||||||
| 			errs = append(errs, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	signable := fmt.Sprintf("%s.%s", jws.Protected, jws.Payload) |  | ||||||
| 	hash := sha256.Sum256([]byte(signable)) |  | ||||||
| 	sig, err := base64.RawURLEncoding.DecodeString(jws.Signature) |  | ||||||
| 	if nil != err { |  | ||||||
| 		err := fmt.Errorf("could not decode signature: %w", err) |  | ||||||
| 		errs = append(errs, err) |  | ||||||
| 		return errs |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	//log.Printf("\n(Verify)\nSignable: %s", signable) |  | ||||||
| 	//log.Printf("Hash: %s", hash) |  | ||||||
| 	//log.Printf("Sig: %s", jws.Signature) |  | ||||||
| 	if nil == pub { |  | ||||||
| 		err := fmt.Errorf("token signature could not be verified") |  | ||||||
| 		errs = append(errs, err) |  | ||||||
| 	} else if !Verify(pub, hash[:], sig) { |  | ||||||
| 		err := fmt.Errorf("token signature is not valid") |  | ||||||
| 		errs = append(errs, err) |  | ||||||
| 	} |  | ||||||
| 	return errs |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) { |  | ||||||
| 	var pub PublicKey = nil |  | ||||||
| 	log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk'") |  | ||||||
| 	log.Println("Security TODO: did not check jws.Claims[\"iss\"]") |  | ||||||
| 	kty := jwkmap["kty"] |  | ||||||
| 	var err error |  | ||||||
| 	if "RSA" == kty { |  | ||||||
| 		e, _ := jwkmap["e"].(string) |  | ||||||
| 		n, _ := jwkmap["n"].(string) |  | ||||||
| 		k, _ := (&RSAJWK{ |  | ||||||
| 			Exp: e, |  | ||||||
| 			N:   n, |  | ||||||
| 		}).marshalJWK() |  | ||||||
| 		pub, err = ParseJWKPublicKey(k) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, append(errs, err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		crv, _ := jwkmap["crv"].(string) |  | ||||||
| 		x, _ := jwkmap["x"].(string) |  | ||||||
| 		y, _ := jwkmap["y"].(string) |  | ||||||
| 		k, _ := (&ECJWK{ |  | ||||||
| 			Curve: crv, |  | ||||||
| 			X:     x, |  | ||||||
| 			Y:     y, |  | ||||||
| 		}).marshalJWK() |  | ||||||
| 		pub, err = ParseJWKPublicKey(k) |  | ||||||
| 		if nil != err { |  | ||||||
| 			return nil, append(errs, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return pub, errs |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) (PublicKey, []error) { |  | ||||||
| 	var pub PublicKey = nil |  | ||||||
| 
 |  | ||||||
| 	if "" == kid { |  | ||||||
| 		err := errors.New("token should have 'kid' or 'jwk' in header to identify the public key") |  | ||||||
| 		errs = append(errs, err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if nil == pubkey { |  | ||||||
| 		if allowMocking { |  | ||||||
| 			if 0 == opts.mockSeed { |  | ||||||
| 				err := errors.New("the debug API requires '_seed' to accompany 'kid'") |  | ||||||
| 				errs = append(errs, err) |  | ||||||
| 			} |  | ||||||
| 			if "" == opts.KeyType { |  | ||||||
| 				err := errors.New("the debug API requires '_kty' to accompany '_seed'") |  | ||||||
| 				errs = append(errs, err) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if 0 == opts.mockSeed || "" == opts.KeyType { |  | ||||||
| 				return nil, errs |  | ||||||
| 			} |  | ||||||
| 			privkey := newPrivateKey(opts) |  | ||||||
| 			pub = NewPublicKey(privkey.Public()) |  | ||||||
| 			return pub, errs |  | ||||||
| 		} |  | ||||||
| 		err := errors.New("no matching public key") |  | ||||||
| 		errs = append(errs, err) |  | ||||||
| 	} else { |  | ||||||
| 		pub = pubkey |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if nil != pub && "" != kid { |  | ||||||
| 		if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(pub.Thumbprint())) { |  | ||||||
| 			err := errors.New("'kid' does not match the public key thumbprint") |  | ||||||
| 			errs = append(errs, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return pub, errs |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Verify will check the signature of a hash |  | ||||||
| func Verify(pubkey PublicKey, hash []byte, sig []byte) bool { |  | ||||||
| 
 |  | ||||||
| 	switch pub := pubkey.Key().(type) { |  | ||||||
| 	case *rsa.PublicKey: |  | ||||||
| 		//log.Printf("RSA VERIFY") |  | ||||||
| 		// TODO Size(key) to detect key size ? |  | ||||||
| 		//alg := "SHA256" |  | ||||||
| 		// TODO: this hasn't been tested yet |  | ||||||
| 		if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	case *ecdsa.PublicKey: |  | ||||||
| 		r := &big.Int{} |  | ||||||
| 		r.SetBytes(sig[0:32]) |  | ||||||
| 		s := &big.Int{} |  | ||||||
| 		s.SetBytes(sig[32:]) |  | ||||||
| 		return ecdsa.Verify(pub, hash, r, s) |  | ||||||
| 	default: |  | ||||||
| 		panic("impossible condition: non-rsa/non-ecdsa key") |  | ||||||
| 		//return false |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| .idea |  | ||||||
| *.sw? |  | ||||||
| .vscode |  | ||||||
							
								
								
									
										17
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,17 +0,0 @@ | |||||||
| language: go |  | ||||||
| 
 |  | ||||||
| go: |  | ||||||
|   - 1.10.x |  | ||||||
|   - 1.11.x |  | ||||||
| 
 |  | ||||||
| script: |  | ||||||
|   - go get -d -t ./... |  | ||||||
|   - go vet ./... |  | ||||||
|   - go test ./... |  | ||||||
|   - > |  | ||||||
|     go_version=$(go version); |  | ||||||
|     if [ ${go_version:13:4} = "1.11" ]; then |  | ||||||
|       go get -u golang.org/x/tools/cmd/goimports; |  | ||||||
|       goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :; |  | ||||||
|     fi |  | ||||||
| 
 |  | ||||||
							
								
								
									
										139
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										139
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,139 +0,0 @@ | |||||||
| # Changelog |  | ||||||
| 
 |  | ||||||
| ## v4.0.0 (2019-01-10) |  | ||||||
| 
 |  | ||||||
| - chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 |  | ||||||
| - router: respond with 404 on router with no routes (#362) |  | ||||||
| - router: additional check to ensure wildcard is at the end of a url pattern (#333) |  | ||||||
| - middleware: deprecate use of http.CloseNotifier (#347) |  | ||||||
| - middleware: fix RedirectSlashes to include query params on redirect (#334) |  | ||||||
| - History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.4 (2019-01-07) |  | ||||||
| 
 |  | ||||||
| - Minor middleware improvements. No changes to core library/router. Moving v3 into its |  | ||||||
| - own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 |  | ||||||
| - History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.3 (2018-08-27) |  | ||||||
| 
 |  | ||||||
| - Minor release |  | ||||||
| - See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.2 (2017-12-22) |  | ||||||
| 
 |  | ||||||
| - Support to route trailing slashes on mounted sub-routers (#281) |  | ||||||
| - middleware: new `ContentCharset` to check matching charsets. Thank you |  | ||||||
|   @csucu for your community contribution! |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.1 (2017-11-20) |  | ||||||
| 
 |  | ||||||
| - middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types |  | ||||||
| - middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value |  | ||||||
| - Minor bug fixes |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.0 (2017-10-10) |  | ||||||
| 
 |  | ||||||
| - New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage |  | ||||||
| - Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.2.1 (2017-08-31) |  | ||||||
| 
 |  | ||||||
| - Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface |  | ||||||
|   and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path |  | ||||||
| - Add new `RouteMethod` to `*Context` |  | ||||||
| - Add new `Routes` pointer to `*Context` |  | ||||||
| - Add new `middleware.GetHead` to route missing HEAD requests to GET handler |  | ||||||
| - Updated benchmarks (see README) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.1.5 (2017-08-02) |  | ||||||
| 
 |  | ||||||
| - Setup golint and go vet for the project |  | ||||||
| - As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` |  | ||||||
|   to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.1.0 (2017-07-10) |  | ||||||
| 
 |  | ||||||
| - Fix a few minor issues after v3 release |  | ||||||
| - Move `docgen` sub-pkg to https://github.com/go-chi/docgen |  | ||||||
| - Move `render` sub-pkg to https://github.com/go-chi/render |  | ||||||
| - Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime  |  | ||||||
|   suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in |  | ||||||
|   https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.0.0 (2017-06-21) |  | ||||||
| 
 |  | ||||||
| - Major update to chi library with many exciting updates, but also some *breaking changes* |  | ||||||
| - URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as |  | ||||||
|   `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the |  | ||||||
|   same router |  | ||||||
| - Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: |  | ||||||
|   `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` |  | ||||||
| - Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as |  | ||||||
|   `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like |  | ||||||
|   in `_examples/custom-handler` |  | ||||||
| - Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their |  | ||||||
|   own using file handler with the stdlib, see `_examples/fileserver` for an example |  | ||||||
| - Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` |  | ||||||
| - Moved the chi project to its own organization, to allow chi-related community packages to |  | ||||||
|   be easily discovered and supported, at: https://github.com/go-chi |  | ||||||
| - *NOTE:* please update your import paths to `"github.com/go-chi/chi"` |  | ||||||
| - *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.1.0 (2017-03-30) |  | ||||||
| 
 |  | ||||||
| - Minor improvements and update to the chi core library |  | ||||||
| - Introduced a brand new `chi/render` sub-package to complete the story of building |  | ||||||
|   APIs to offer a pattern for managing well-defined request / response payloads. Please |  | ||||||
|   check out the updated `_examples/rest` example for how it works. |  | ||||||
| - Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.0.0 (2017-01-06) |  | ||||||
| 
 |  | ||||||
| - After many months of v2 being in an RC state with many companies and users running it in |  | ||||||
|   production, the inclusion of some improvements to the middlewares, we are very pleased to |  | ||||||
|   announce v2.0.0 of chi. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.0.0-rc1 (2016-07-26) |  | ||||||
| 
 |  | ||||||
| - Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular |  | ||||||
|   community `"net/context"` package has been included in the standard library as `"context"` and |  | ||||||
|   utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other |  | ||||||
|   request-scoped values. We're very excited about the new context addition and are proud to |  | ||||||
|   introduce chi v2, a minimal and powerful routing package for building large HTTP services, |  | ||||||
|   with zero external dependencies. Chi focuses on idiomatic design and encourages the use of  |  | ||||||
|   stdlib HTTP handlers and middlwares. |  | ||||||
| - chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` |  | ||||||
| - chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` |  | ||||||
| - chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, |  | ||||||
|   which provides direct access to URL routing parameters, the routing path and the matching |  | ||||||
|   routing patterns. |  | ||||||
| - Users upgrading from chi v1 to v2, need to: |  | ||||||
|   1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to |  | ||||||
|      the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` |  | ||||||
|   2. Use `chi.URLParam(r *http.Request, paramKey string) string` |  | ||||||
|      or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v1.0.0 (2016-07-01) |  | ||||||
| 
 |  | ||||||
| - Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v0.9.0 (2016-03-31) |  | ||||||
| 
 |  | ||||||
| - Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) |  | ||||||
| - BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters |  | ||||||
|   has changed to: `chi.URLParam(ctx, "id")` |  | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,31 +0,0 @@ | |||||||
| # Contributing |  | ||||||
| 
 |  | ||||||
| ## Prerequisites |  | ||||||
| 
 |  | ||||||
| 1. [Install Go][go-install]. |  | ||||||
| 2. Download the sources and switch the working directory: |  | ||||||
| 
 |  | ||||||
|     ```bash |  | ||||||
|     go get -u -d github.com/go-chi/chi |  | ||||||
|     cd $GOPATH/src/github.com/go-chi/chi |  | ||||||
|     ``` |  | ||||||
| 
 |  | ||||||
| ## Submitting a Pull Request |  | ||||||
| 
 |  | ||||||
| A typical workflow is: |  | ||||||
| 
 |  | ||||||
| 1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] |  | ||||||
| 2. [Create a topic branch.][branch] |  | ||||||
| 3. Add tests for your change. |  | ||||||
| 4. Run `go test`. If your tests pass, return to the step 3. |  | ||||||
| 5. Implement the change and ensure the steps from the previous step pass. |  | ||||||
| 6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline. |  | ||||||
| 7. [Add, commit and push your changes.][git-help] |  | ||||||
| 8. [Submit a pull request.][pull-req] |  | ||||||
| 
 |  | ||||||
| [go-install]: https://golang.org/doc/install |  | ||||||
| [go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html |  | ||||||
| [fork]: https://help.github.com/articles/fork-a-repo |  | ||||||
| [branch]: http://learn.github.com/p/branching.html |  | ||||||
| [git-help]: https://guides.github.com |  | ||||||
| [pull-req]: https://help.github.com/articles/using-pull-requests |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,20 +0,0 @@ | |||||||
| Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. |  | ||||||
| 
 |  | ||||||
| MIT License |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of |  | ||||||
| this software and associated documentation files (the "Software"), to deal in |  | ||||||
| the Software without restriction, including without limitation the rights to |  | ||||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |  | ||||||
| the Software, and to permit persons to whom the Software is furnished to do so, |  | ||||||
| subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |  | ||||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |  | ||||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |  | ||||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |  | ||||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
							
								
								
									
										438
									
								
								vendor/github.com/go-chi/chi/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										438
									
								
								vendor/github.com/go-chi/chi/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,438 +0,0 @@ | |||||||
| # <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" /> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] |  | ||||||
| 
 |  | ||||||
| `chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's |  | ||||||
| especially good at helping you write large REST API services that are kept maintainable as your |  | ||||||
| project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to |  | ||||||
| handle signaling, cancelation and request-scoped values across a handler chain. |  | ||||||
| 
 |  | ||||||
| The focus of the project has been to seek out an elegant and comfortable design for writing |  | ||||||
| REST API servers, written during the development of the Pressly API service that powers our |  | ||||||
| public API service, which in turn powers all of our client-side applications. |  | ||||||
| 
 |  | ||||||
| The key considerations of chi's design are: project structure, maintainability, standard http |  | ||||||
| handlers (stdlib-only), developer productivity, and deconstructing a large system into many small |  | ||||||
| parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also |  | ||||||
| included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! |  | ||||||
| 
 |  | ||||||
| ## Install |  | ||||||
| 
 |  | ||||||
| `go get -u github.com/go-chi/chi` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Features |  | ||||||
| 
 |  | ||||||
| * **Lightweight** - cloc'd in ~1000 LOC for the chi router |  | ||||||
| * **Fast** - yes, see [benchmarks](#benchmarks) |  | ||||||
| * **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` |  | ||||||
| * **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting |  | ||||||
| * **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts |  | ||||||
| * **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) |  | ||||||
| * **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown |  | ||||||
| * **No external dependencies** - plain ol' Go stdlib + net/http |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Examples |  | ||||||
| 
 |  | ||||||
| See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **As easy as:** |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	r := chi.NewRouter() |  | ||||||
| 	r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		w.Write([]byte("welcome")) |  | ||||||
| 	}) |  | ||||||
| 	http.ListenAndServe(":3000", r) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| **REST Preview:** |  | ||||||
| 
 |  | ||||||
| Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs |  | ||||||
| in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in |  | ||||||
| Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). |  | ||||||
| 
 |  | ||||||
| I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed |  | ||||||
| above, they will show you all the features of chi and serve as a good form of documentation. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| import ( |  | ||||||
|   //... |  | ||||||
|   "context" |  | ||||||
|   "github.com/go-chi/chi" |  | ||||||
|   "github.com/go-chi/chi/middleware" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
|   r := chi.NewRouter() |  | ||||||
| 
 |  | ||||||
|   // A good base middleware stack |  | ||||||
|   r.Use(middleware.RequestID) |  | ||||||
|   r.Use(middleware.RealIP) |  | ||||||
|   r.Use(middleware.Logger) |  | ||||||
|   r.Use(middleware.Recoverer) |  | ||||||
| 
 |  | ||||||
|   // Set a timeout value on the request context (ctx), that will signal |  | ||||||
|   // through ctx.Done() that the request has timed out and further |  | ||||||
|   // processing should be stopped. |  | ||||||
|   r.Use(middleware.Timeout(60 * time.Second)) |  | ||||||
| 
 |  | ||||||
|   r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     w.Write([]byte("hi")) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   // RESTy routes for "articles" resource |  | ||||||
|   r.Route("/articles", func(r chi.Router) { |  | ||||||
|     r.With(paginate).Get("/", listArticles)                           // GET /articles |  | ||||||
|     r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 |  | ||||||
| 
 |  | ||||||
|     r.Post("/", createArticle)                                        // POST /articles |  | ||||||
|     r.Get("/search", searchArticles)                                  // GET /articles/search |  | ||||||
| 
 |  | ||||||
|     // Regexp url parameters: |  | ||||||
|     r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto |  | ||||||
| 
 |  | ||||||
|     // Subrouters: |  | ||||||
|     r.Route("/{articleID}", func(r chi.Router) { |  | ||||||
|       r.Use(ArticleCtx) |  | ||||||
|       r.Get("/", getArticle)                                          // GET /articles/123 |  | ||||||
|       r.Put("/", updateArticle)                                       // PUT /articles/123 |  | ||||||
|       r.Delete("/", deleteArticle)                                    // DELETE /articles/123 |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   // Mount the admin sub-router |  | ||||||
|   r.Mount("/admin", adminRouter()) |  | ||||||
| 
 |  | ||||||
|   http.ListenAndServe(":3333", r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ArticleCtx(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     articleID := chi.URLParam(r, "articleID") |  | ||||||
|     article, err := dbGetArticle(articleID) |  | ||||||
|     if err != nil { |  | ||||||
|       http.Error(w, http.StatusText(404), 404) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     ctx := context.WithValue(r.Context(), "article", article) |  | ||||||
|     next.ServeHTTP(w, r.WithContext(ctx)) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getArticle(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   ctx := r.Context() |  | ||||||
|   article, ok := ctx.Value("article").(*Article) |  | ||||||
|   if !ok { |  | ||||||
|     http.Error(w, http.StatusText(422), 422) |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
|   w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A completely separate router for administrator routes |  | ||||||
| func adminRouter() http.Handler { |  | ||||||
|   r := chi.NewRouter() |  | ||||||
|   r.Use(AdminOnly) |  | ||||||
|   r.Get("/", adminIndex) |  | ||||||
|   r.Get("/accounts", adminListAccounts) |  | ||||||
|   return r |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func AdminOnly(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     ctx := r.Context() |  | ||||||
|     perm, ok := ctx.Value("acl.permission").(YourPermissionType) |  | ||||||
|     if !ok || !perm.IsAdmin() { |  | ||||||
|       http.Error(w, http.StatusText(403), 403) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     next.ServeHTTP(w, r) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Router design |  | ||||||
| 
 |  | ||||||
| chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). |  | ||||||
| The router is fully compatible with `net/http`. |  | ||||||
| 
 |  | ||||||
| Built on top of the tree is the `Router` interface: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // Router consisting of the core routing methods used by chi's Mux, |  | ||||||
| // using only the standard net/http. |  | ||||||
| type Router interface { |  | ||||||
| 	http.Handler |  | ||||||
| 	Routes |  | ||||||
| 
 |  | ||||||
| 	// Use appends one of more middlewares onto the Router stack. |  | ||||||
| 	Use(middlewares ...func(http.Handler) http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// With adds inline middlewares for an endpoint handler. |  | ||||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router |  | ||||||
| 
 |  | ||||||
| 	// Group adds a new inline-Router along the current routing |  | ||||||
| 	// path, with a fresh middleware stack for the inline-Router. |  | ||||||
| 	Group(fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Route mounts a sub-Router along a `pattern`` string. |  | ||||||
| 	Route(pattern string, fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Mount attaches another http.Handler along ./pattern/* |  | ||||||
| 	Mount(pattern string, h http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches |  | ||||||
| 	// all HTTP methods. |  | ||||||
| 	Handle(pattern string, h http.Handler) |  | ||||||
| 	HandleFunc(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// Method and MethodFunc adds routes for `pattern` that matches |  | ||||||
| 	// the `method` HTTP method. |  | ||||||
| 	Method(method, pattern string, h http.Handler) |  | ||||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// HTTP-method routing along `pattern` |  | ||||||
| 	Connect(pattern string, h http.HandlerFunc) |  | ||||||
| 	Delete(pattern string, h http.HandlerFunc) |  | ||||||
| 	Get(pattern string, h http.HandlerFunc) |  | ||||||
| 	Head(pattern string, h http.HandlerFunc) |  | ||||||
| 	Options(pattern string, h http.HandlerFunc) |  | ||||||
| 	Patch(pattern string, h http.HandlerFunc) |  | ||||||
| 	Post(pattern string, h http.HandlerFunc) |  | ||||||
| 	Put(pattern string, h http.HandlerFunc) |  | ||||||
| 	Trace(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// NotFound defines a handler to respond whenever a route could |  | ||||||
| 	// not be found. |  | ||||||
| 	NotFound(h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is |  | ||||||
| 	// not allowed. |  | ||||||
| 	MethodNotAllowed(h http.HandlerFunc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes interface adds two methods for router traversal, which is also |  | ||||||
| // used by the github.com/go-chi/docgen package to generate documentation for Routers. |  | ||||||
| type Routes interface { |  | ||||||
| 	// Routes returns the routing tree in an easily traversable structure. |  | ||||||
| 	Routes() []Route |  | ||||||
| 
 |  | ||||||
| 	// Middlewares returns the list of middlewares in use by the router. |  | ||||||
| 	Middlewares() Middlewares |  | ||||||
| 
 |  | ||||||
| 	// Match searches the routing tree for a handler that matches |  | ||||||
| 	// the method/path - similar to routing a http request, but without |  | ||||||
| 	// executing the handler thereafter. |  | ||||||
| 	Match(rctx *Context, method, path string) bool |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern |  | ||||||
| supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters |  | ||||||
| can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters |  | ||||||
| and `chi.URLParam(r, "*")` for a wildcard parameter. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### Middleware handlers |  | ||||||
| 
 |  | ||||||
| chi's middlewares are just stdlib net/http middleware handlers. There is nothing special |  | ||||||
| about them, which means the router and all the tooling is designed to be compatible and |  | ||||||
| friendly with any middleware in the community. This offers much better extensibility and reuse |  | ||||||
| of packages and is at the heart of chi's purpose. |  | ||||||
| 
 |  | ||||||
| Here is an example of a standard net/http middleware handler using the new request context |  | ||||||
| available in Go. This middleware sets a hypothetical user identifier on the request |  | ||||||
| context and calls the next handler in the chain. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP middleware setting a value on the request context |  | ||||||
| func MyMiddleware(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     ctx := context.WithValue(r.Context(), "user", "123") |  | ||||||
|     next.ServeHTTP(w, r.WithContext(ctx)) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### Request handlers |  | ||||||
| 
 |  | ||||||
| chi uses standard net/http request handlers. This little snippet is an example of a http.Handler |  | ||||||
| func that reads a user identifier from the request context - hypothetically, identifying |  | ||||||
| the user sending an authenticated request, validated+set by a previous middleware handler. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP handler accessing data from the request context. |  | ||||||
| func MyRequestHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   user := r.Context().Value("user").(string) |  | ||||||
|   w.Write([]byte(fmt.Sprintf("hi %s", user))) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### URL parameters |  | ||||||
| 
 |  | ||||||
| chi's router parses and stores URL parameters right onto the request context. Here is |  | ||||||
| an example of how to access URL params in your net/http handlers. And of course, middlewares |  | ||||||
| are able to access the same information. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP handler accessing the url routing parameters. |  | ||||||
| func MyRequestHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   userID := chi.URLParam(r, "userID") // from a route like /users/{userID} |  | ||||||
| 
 |  | ||||||
|   ctx := r.Context() |  | ||||||
|   key := ctx.Value("key").(string) |  | ||||||
| 
 |  | ||||||
|   w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key))) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Middlewares |  | ||||||
| 
 |  | ||||||
| chi comes equipped with an optional `middleware` package, providing a suite of standard |  | ||||||
| `net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible |  | ||||||
| with `net/http` can be used with chi's mux. |  | ||||||
| 
 |  | ||||||
| ### Core middlewares |  | ||||||
| 
 |  | ||||||
| ----------------------------------------------------------------------------------------------------------- |  | ||||||
| | chi/middleware Handler | description                                                                     | |  | ||||||
| |:----------------------|:--------------------------------------------------------------------------------- |  | ||||||
| | AllowContentType      | Explicit whitelist of accepted request Content-Types                            | |  | ||||||
| | Compress              | Gzip compression for clients that accept compressed responses                   | |  | ||||||
| | GetHead               | Automatically route undefined HEAD requests to GET handlers                     | |  | ||||||
| | Heartbeat             | Monitoring endpoint to check the servers pulse                                  | |  | ||||||
| | Logger                | Logs the start and end of each request with the elapsed processing time         | |  | ||||||
| | NoCache               | Sets response headers to prevent clients from caching                           | |  | ||||||
| | Profiler              | Easily attach net/http/pprof to your routers                                    | |  | ||||||
| | RealIP                | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP         | |  | ||||||
| | Recoverer             | Gracefully absorb panics and prints the stack trace                             | |  | ||||||
| | RequestID             | Injects a request ID into the context of each request                           | |  | ||||||
| | RedirectSlashes       | Redirect slashes on routing paths                                               | |  | ||||||
| | SetHeader             | Short-hand middleware to set a response header key/value                        | |  | ||||||
| | StripSlashes          | Strip slashes on routing paths                                                  | |  | ||||||
| | Throttle              | Puts a ceiling on the number of concurrent requests                             | |  | ||||||
| | Timeout               | Signals to the request context when the timeout deadline is reached             | |  | ||||||
| | URLFormat             | Parse extension from url and put it on request context                          | |  | ||||||
| | WithValue             | Short-hand middleware to set a key/value on the request context                 | |  | ||||||
| ----------------------------------------------------------------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| ### Auxiliary middlewares & packages |  | ||||||
| 
 |  | ||||||
| Please see https://github.com/go-chi for additional packages. |  | ||||||
| 
 |  | ||||||
| -------------------------------------------------------------------------------------------------------------------- |  | ||||||
| | package                                            | description                                                 | |  | ||||||
| |:---------------------------------------------------|:------------------------------------------------------------- |  | ||||||
| | [cors](https://github.com/go-chi/cors)             | Cross-origin resource sharing (CORS)                        | |  | ||||||
| | [docgen](https://github.com/go-chi/docgen)         | Print chi.Router routes at runtime                          | |  | ||||||
| | [jwtauth](https://github.com/go-chi/jwtauth)       | JWT authentication                                          | |  | ||||||
| | [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing                           | |  | ||||||
| | [httpcoala](https://github.com/go-chi/httpcoala)   | HTTP request coalescer                                      | |  | ||||||
| | [chi-authz](https://github.com/casbin/chi-authz)   | Request ACL via https://github.com/hsluoyz/casbin           | |  | ||||||
| | [phi](https://github.com/fate-lovely/phi)          | Port chi to [fasthttp](https://github.com/valyala/fasthttp) | |  | ||||||
| -------------------------------------------------------------------------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## context? |  | ||||||
| 
 |  | ||||||
| `context` is a tiny pkg that provides simple interface to signal context across call stacks |  | ||||||
| and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani) |  | ||||||
| and is available in stdlib since go1.7. |  | ||||||
| 
 |  | ||||||
| Learn more at https://blog.golang.org/context |  | ||||||
| 
 |  | ||||||
| and.. |  | ||||||
| * Docs: https://golang.org/pkg/context |  | ||||||
| * Source: https://github.com/golang/go/tree/master/src/context |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Benchmarks |  | ||||||
| 
 |  | ||||||
| The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark |  | ||||||
| 
 |  | ||||||
| Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop |  | ||||||
| 
 |  | ||||||
| ```shell |  | ||||||
| BenchmarkChi_Param            3000000         475 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Param5           2000000         696 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Param20          1000000        1275 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParamWrite       3000000         505 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubStatic     3000000         508 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubParam      2000000         669 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubAll          10000      134627 ns/op     87699 B/op    609 allocs/op |  | ||||||
| BenchmarkChi_GPlusStatic      3000000         402 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlusParam       3000000         500 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlus2Params     3000000         586 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlusAll          200000        7237 ns/op      5616 B/op     39 allocs/op |  | ||||||
| BenchmarkChi_ParseStatic      3000000         408 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParseParam       3000000         488 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Parse2Params     3000000         551 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParseAll          100000       13508 ns/op     11232 B/op     78 allocs/op |  | ||||||
| BenchmarkChi_StaticAll          20000       81933 ns/op     67826 B/op    471 allocs/op |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc |  | ||||||
| 
 |  | ||||||
| NOTE: the allocs in the benchmark above are from the calls to http.Request's |  | ||||||
| `WithContext(context.Context)` method that clones the http.Request, sets the `Context()` |  | ||||||
| on the duplicated (alloc'd) request and returns it the new request object. This is just |  | ||||||
| how setting context on a request in Go works. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Credits |  | ||||||
| 
 |  | ||||||
| * Carl Jackson for https://github.com/zenazn/goji |  | ||||||
|   * Parts of chi's thinking comes from goji, and chi's middleware package |  | ||||||
|     sources from goji. |  | ||||||
| * Armon Dadgar for https://github.com/armon/go-radix |  | ||||||
| * Contributions: [@VojtechVitek](https://github.com/VojtechVitek) |  | ||||||
| 
 |  | ||||||
| We'll be more than happy to see [your contributions](./CONTRIBUTING.md)! |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Beyond REST |  | ||||||
| 
 |  | ||||||
| chi is just a http router that lets you decompose request handling into many smaller layers. |  | ||||||
| Many companies including Pressly.com (of course) use chi to write REST services for their public |  | ||||||
| APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces |  | ||||||
| required to write a complete client-server system or network of microservices. |  | ||||||
| 
 |  | ||||||
| Looking ahead beyond REST, I also recommend some newer works in the field coming from |  | ||||||
| [gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit) |  | ||||||
| and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their |  | ||||||
| own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server |  | ||||||
| communication feel like a single program on a single computer, no need to hand-write a client library |  | ||||||
| and the request/response payloads are typed contracts. NATS is pretty amazing too as a super |  | ||||||
| fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery - |  | ||||||
| an excellent combination with gRPC. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## License |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) |  | ||||||
| 
 |  | ||||||
| Licensed under [MIT License](./LICENSE) |  | ||||||
| 
 |  | ||||||
| [GoDoc]: https://godoc.org/github.com/go-chi/chi |  | ||||||
| [GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg |  | ||||||
| [Travis]: https://travis-ci.org/go-chi/chi |  | ||||||
| [Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master |  | ||||||
							
								
								
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,49 +0,0 @@ | |||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import "net/http" |  | ||||||
| 
 |  | ||||||
| // Chain returns a Middlewares type from a slice of middleware handlers. |  | ||||||
| func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { |  | ||||||
| 	return Middlewares(middlewares) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handler builds and returns a http.Handler from the chain of middlewares, |  | ||||||
| // with `h http.Handler` as the final handler. |  | ||||||
| func (mws Middlewares) Handler(h http.Handler) http.Handler { |  | ||||||
| 	return &ChainHandler{mws, h, chain(mws, h)} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HandlerFunc builds and returns a http.Handler from the chain of middlewares, |  | ||||||
| // with `h http.Handler` as the final handler. |  | ||||||
| func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler { |  | ||||||
| 	return &ChainHandler{mws, h, chain(mws, h)} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ChainHandler is a http.Handler with support for handler composition and |  | ||||||
| // execution. |  | ||||||
| type ChainHandler struct { |  | ||||||
| 	Middlewares Middlewares |  | ||||||
| 	Endpoint    http.Handler |  | ||||||
| 	chain       http.Handler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	c.chain.ServeHTTP(w, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // chain builds a http.Handler composed of an inline middleware stack and endpoint |  | ||||||
| // handler in the order they are passed. |  | ||||||
| func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler { |  | ||||||
| 	// Return ahead of time if there aren't any middlewares for the chain |  | ||||||
| 	if len(middlewares) == 0 { |  | ||||||
| 		return endpoint |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wrap the end handler with the middleware chain |  | ||||||
| 	h := middlewares[len(middlewares)-1](endpoint) |  | ||||||
| 	for i := len(middlewares) - 2; i >= 0; i-- { |  | ||||||
| 		h = middlewares[i](h) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return h |  | ||||||
| } |  | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,134 +0,0 @@ | |||||||
| // |  | ||||||
| // Package chi is a small, idiomatic and composable router for building HTTP services. |  | ||||||
| // |  | ||||||
| // chi requires Go 1.7 or newer. |  | ||||||
| // |  | ||||||
| // Example: |  | ||||||
| //  package main |  | ||||||
| // |  | ||||||
| //  import ( |  | ||||||
| //  	"net/http" |  | ||||||
| // |  | ||||||
| //  	"github.com/go-chi/chi" |  | ||||||
| //  	"github.com/go-chi/chi/middleware" |  | ||||||
| //  ) |  | ||||||
| // |  | ||||||
| //  func main() { |  | ||||||
| //  	r := chi.NewRouter() |  | ||||||
| //  	r.Use(middleware.Logger) |  | ||||||
| //  	r.Use(middleware.Recoverer) |  | ||||||
| // |  | ||||||
| //  	r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| //  		w.Write([]byte("root.")) |  | ||||||
| //  	}) |  | ||||||
| // |  | ||||||
| //  	http.ListenAndServe(":3333", r) |  | ||||||
| //  } |  | ||||||
| // |  | ||||||
| // See github.com/go-chi/chi/_examples/ for more in-depth examples. |  | ||||||
| // |  | ||||||
| // URL patterns allow for easy matching of path components in HTTP |  | ||||||
| // requests. The matching components can then be accessed using |  | ||||||
| // chi.URLParam(). All patterns must begin with a slash. |  | ||||||
| // |  | ||||||
| // A simple named placeholder {name} matches any sequence of characters |  | ||||||
| // up to the next / or the end of the URL. Trailing slashes on paths must |  | ||||||
| // be handled explicitly. |  | ||||||
| // |  | ||||||
| // A placeholder with a name followed by a colon allows a regular |  | ||||||
| // expression match, for example {number:\\d+}. The regular expression |  | ||||||
| // syntax is Go's normal regexp RE2 syntax, except that regular expressions |  | ||||||
| // including { or } are not supported, and / will never be |  | ||||||
| // matched. An anonymous regexp pattern is allowed, using an empty string |  | ||||||
| // before the colon in the placeholder, such as {:\\d+} |  | ||||||
| // |  | ||||||
| // The special placeholder of asterisk matches the rest of the requested |  | ||||||
| // URL. Any trailing characters in the pattern are ignored. This is the only |  | ||||||
| // placeholder which will match / characters. |  | ||||||
| // |  | ||||||
| // Examples: |  | ||||||
| //  "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" |  | ||||||
| //  "/user/{name}/info" matches "/user/jsmith/info" |  | ||||||
| //  "/page/*" matches "/page/intro/latest" |  | ||||||
| //  "/page/*/index" also matches "/page/intro/latest" |  | ||||||
| //  "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" |  | ||||||
| // |  | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import "net/http" |  | ||||||
| 
 |  | ||||||
| // NewRouter returns a new Mux object that implements the Router interface. |  | ||||||
| func NewRouter() *Mux { |  | ||||||
| 	return NewMux() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Router consisting of the core routing methods used by chi's Mux, |  | ||||||
| // using only the standard net/http. |  | ||||||
| type Router interface { |  | ||||||
| 	http.Handler |  | ||||||
| 	Routes |  | ||||||
| 
 |  | ||||||
| 	// Use appends one of more middlewares onto the Router stack. |  | ||||||
| 	Use(middlewares ...func(http.Handler) http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// With adds inline middlewares for an endpoint handler. |  | ||||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router |  | ||||||
| 
 |  | ||||||
| 	// Group adds a new inline-Router along the current routing |  | ||||||
| 	// path, with a fresh middleware stack for the inline-Router. |  | ||||||
| 	Group(fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Route mounts a sub-Router along a `pattern`` string. |  | ||||||
| 	Route(pattern string, fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Mount attaches another http.Handler along ./pattern/* |  | ||||||
| 	Mount(pattern string, h http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches |  | ||||||
| 	// all HTTP methods. |  | ||||||
| 	Handle(pattern string, h http.Handler) |  | ||||||
| 	HandleFunc(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// Method and MethodFunc adds routes for `pattern` that matches |  | ||||||
| 	// the `method` HTTP method. |  | ||||||
| 	Method(method, pattern string, h http.Handler) |  | ||||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// HTTP-method routing along `pattern` |  | ||||||
| 	Connect(pattern string, h http.HandlerFunc) |  | ||||||
| 	Delete(pattern string, h http.HandlerFunc) |  | ||||||
| 	Get(pattern string, h http.HandlerFunc) |  | ||||||
| 	Head(pattern string, h http.HandlerFunc) |  | ||||||
| 	Options(pattern string, h http.HandlerFunc) |  | ||||||
| 	Patch(pattern string, h http.HandlerFunc) |  | ||||||
| 	Post(pattern string, h http.HandlerFunc) |  | ||||||
| 	Put(pattern string, h http.HandlerFunc) |  | ||||||
| 	Trace(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// NotFound defines a handler to respond whenever a route could |  | ||||||
| 	// not be found. |  | ||||||
| 	NotFound(h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is |  | ||||||
| 	// not allowed. |  | ||||||
| 	MethodNotAllowed(h http.HandlerFunc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes interface adds two methods for router traversal, which is also |  | ||||||
| // used by the `docgen` subpackage to generation documentation for Routers. |  | ||||||
| type Routes interface { |  | ||||||
| 	// Routes returns the routing tree in an easily traversable structure. |  | ||||||
| 	Routes() []Route |  | ||||||
| 
 |  | ||||||
| 	// Middlewares returns the list of middlewares in use by the router. |  | ||||||
| 	Middlewares() Middlewares |  | ||||||
| 
 |  | ||||||
| 	// Match searches the routing tree for a handler that matches |  | ||||||
| 	// the method/path - similar to routing a http request, but without |  | ||||||
| 	// executing the handler thereafter. |  | ||||||
| 	Match(rctx *Context, method, path string) bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Middlewares type is a slice of standard middleware handlers with methods |  | ||||||
| // to compose middleware chains and http.Handler's. |  | ||||||
| type Middlewares []func(http.Handler) http.Handler |  | ||||||
							
								
								
									
										161
									
								
								vendor/github.com/go-chi/chi/context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										161
									
								
								vendor/github.com/go-chi/chi/context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,161 +0,0 @@ | |||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// RouteCtxKey is the context.Context key to store the request context. |  | ||||||
| 	RouteCtxKey = &contextKey{"RouteContext"} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Context is the default routing context set on the root node of a |  | ||||||
| // request context to track route patterns, URL parameters and |  | ||||||
| // an optional routing path. |  | ||||||
| type Context struct { |  | ||||||
| 	Routes Routes |  | ||||||
| 
 |  | ||||||
| 	// Routing path/method override used during the route search. |  | ||||||
| 	// See Mux#routeHTTP method. |  | ||||||
| 	RoutePath   string |  | ||||||
| 	RouteMethod string |  | ||||||
| 
 |  | ||||||
| 	// Routing pattern stack throughout the lifecycle of the request, |  | ||||||
| 	// across all connected routers. It is a record of all matching |  | ||||||
| 	// patterns across a stack of sub-routers. |  | ||||||
| 	RoutePatterns []string |  | ||||||
| 
 |  | ||||||
| 	// URLParams are the stack of routeParams captured during the |  | ||||||
| 	// routing lifecycle across a stack of sub-routers. |  | ||||||
| 	URLParams RouteParams |  | ||||||
| 
 |  | ||||||
| 	// The endpoint routing pattern that matched the request URI path |  | ||||||
| 	// or `RoutePath` of the current sub-router. This value will update |  | ||||||
| 	// during the lifecycle of a request passing through a stack of |  | ||||||
| 	// sub-routers. |  | ||||||
| 	routePattern string |  | ||||||
| 
 |  | ||||||
| 	// Route parameters matched for the current sub-router. It is |  | ||||||
| 	// intentionally unexported so it cant be tampered. |  | ||||||
| 	routeParams RouteParams |  | ||||||
| 
 |  | ||||||
| 	// methodNotAllowed hint |  | ||||||
| 	methodNotAllowed bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewRouteContext returns a new routing Context object. |  | ||||||
| func NewRouteContext() *Context { |  | ||||||
| 	return &Context{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Reset a routing context to its initial state. |  | ||||||
| func (x *Context) Reset() { |  | ||||||
| 	x.Routes = nil |  | ||||||
| 	x.RoutePath = "" |  | ||||||
| 	x.RouteMethod = "" |  | ||||||
| 	x.RoutePatterns = x.RoutePatterns[:0] |  | ||||||
| 	x.URLParams.Keys = x.URLParams.Keys[:0] |  | ||||||
| 	x.URLParams.Values = x.URLParams.Values[:0] |  | ||||||
| 
 |  | ||||||
| 	x.routePattern = "" |  | ||||||
| 	x.routeParams.Keys = x.routeParams.Keys[:0] |  | ||||||
| 	x.routeParams.Values = x.routeParams.Values[:0] |  | ||||||
| 	x.methodNotAllowed = false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParam returns the corresponding URL parameter value from the request |  | ||||||
| // routing context. |  | ||||||
| func (x *Context) URLParam(key string) string { |  | ||||||
| 	for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { |  | ||||||
| 		if x.URLParams.Keys[k] == key { |  | ||||||
| 			return x.URLParams.Values[k] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RoutePattern builds the routing pattern string for the particular |  | ||||||
| // request, at the particular point during routing. This means, the value |  | ||||||
| // will change throughout the execution of a request in a router. That is |  | ||||||
| // why its advised to only use this value after calling the next handler. |  | ||||||
| // |  | ||||||
| // For example, |  | ||||||
| // |  | ||||||
| //   func Instrument(next http.Handler) http.Handler { |  | ||||||
| //     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| //       next.ServeHTTP(w, r) |  | ||||||
| //       routePattern := chi.RouteContext(r.Context()).RoutePattern() |  | ||||||
| //       measure(w, r, routePattern) |  | ||||||
| //   	 }) |  | ||||||
| //   } |  | ||||||
| func (x *Context) RoutePattern() string { |  | ||||||
| 	routePattern := strings.Join(x.RoutePatterns, "") |  | ||||||
| 	return strings.Replace(routePattern, "/*/", "/", -1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RouteContext returns chi's routing Context object from a |  | ||||||
| // http.Request Context. |  | ||||||
| func RouteContext(ctx context.Context) *Context { |  | ||||||
| 	return ctx.Value(RouteCtxKey).(*Context) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParam returns the url parameter from a http.Request object. |  | ||||||
| func URLParam(r *http.Request, key string) string { |  | ||||||
| 	if rctx := RouteContext(r.Context()); rctx != nil { |  | ||||||
| 		return rctx.URLParam(key) |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParamFromCtx returns the url parameter from a http.Request Context. |  | ||||||
| func URLParamFromCtx(ctx context.Context, key string) string { |  | ||||||
| 	if rctx := RouteContext(ctx); rctx != nil { |  | ||||||
| 		return rctx.URLParam(key) |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RouteParams is a structure to track URL routing parameters efficiently. |  | ||||||
| type RouteParams struct { |  | ||||||
| 	Keys, Values []string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Add will append a URL parameter to the end of the route param |  | ||||||
| func (s *RouteParams) Add(key, value string) { |  | ||||||
| 	(*s).Keys = append((*s).Keys, key) |  | ||||||
| 	(*s).Values = append((*s).Values, value) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ServerBaseContext wraps an http.Handler to set the request context to the |  | ||||||
| // `baseCtx`. |  | ||||||
| func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler { |  | ||||||
| 	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		ctx := r.Context() |  | ||||||
| 		baseCtx := baseCtx |  | ||||||
| 
 |  | ||||||
| 		// Copy over default net/http server context keys |  | ||||||
| 		if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok { |  | ||||||
| 			baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v) |  | ||||||
| 		} |  | ||||||
| 		if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok { |  | ||||||
| 			baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		h.ServeHTTP(w, r.WithContext(baseCtx)) |  | ||||||
| 	}) |  | ||||||
| 	return fn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // contextKey is a value for use with context.WithValue. It's used as |  | ||||||
| // a pointer so it fits in an interface{} without allocation. This technique |  | ||||||
| // for defining context keys was copied from Go 1.7's new use of context in net/http. |  | ||||||
| type contextKey struct { |  | ||||||
| 	name string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *contextKey) String() string { |  | ||||||
| 	return "chi context value " + k.name |  | ||||||
| } |  | ||||||
							
								
								
									
										460
									
								
								vendor/github.com/go-chi/chi/mux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										460
									
								
								vendor/github.com/go-chi/chi/mux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,460 +0,0 @@ | |||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var _ Router = &Mux{} |  | ||||||
| 
 |  | ||||||
| // Mux is a simple HTTP route multiplexer that parses a request path, |  | ||||||
| // records any URL params, and executes an end handler. It implements |  | ||||||
| // the http.Handler interface and is friendly with the standard library. |  | ||||||
| // |  | ||||||
| // Mux is designed to be fast, minimal and offer a powerful API for building |  | ||||||
| // modular and composable HTTP services with a large set of handlers. It's |  | ||||||
| // particularly useful for writing large REST API services that break a handler |  | ||||||
| // into many smaller parts composed of middlewares and end handlers. |  | ||||||
| type Mux struct { |  | ||||||
| 	// The radix trie router |  | ||||||
| 	tree *node |  | ||||||
| 
 |  | ||||||
| 	// The middleware stack |  | ||||||
| 	middlewares []func(http.Handler) http.Handler |  | ||||||
| 
 |  | ||||||
| 	// Controls the behaviour of middleware chain generation when a mux |  | ||||||
| 	// is registered as an inline group inside another mux. |  | ||||||
| 	inline bool |  | ||||||
| 	parent *Mux |  | ||||||
| 
 |  | ||||||
| 	// The computed mux handler made of the chained middleware stack and |  | ||||||
| 	// the tree router |  | ||||||
| 	handler http.Handler |  | ||||||
| 
 |  | ||||||
| 	// Routing context pool |  | ||||||
| 	pool *sync.Pool |  | ||||||
| 
 |  | ||||||
| 	// Custom route not found handler |  | ||||||
| 	notFoundHandler http.HandlerFunc |  | ||||||
| 
 |  | ||||||
| 	// Custom method not allowed handler |  | ||||||
| 	methodNotAllowedHandler http.HandlerFunc |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMux returns a newly initialized Mux object that implements the Router |  | ||||||
| // interface. |  | ||||||
| func NewMux() *Mux { |  | ||||||
| 	mux := &Mux{tree: &node{}, pool: &sync.Pool{}} |  | ||||||
| 	mux.pool.New = func() interface{} { |  | ||||||
| 		return NewRouteContext() |  | ||||||
| 	} |  | ||||||
| 	return mux |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ServeHTTP is the single method of the http.Handler interface that makes |  | ||||||
| // Mux interoperable with the standard library. It uses a sync.Pool to get and |  | ||||||
| // reuse routing contexts for each request. |  | ||||||
| func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	// Ensure the mux has some routes defined on the mux |  | ||||||
| 	if mx.handler == nil { |  | ||||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if a routing context already exists from a parent router. |  | ||||||
| 	rctx, _ := r.Context().Value(RouteCtxKey).(*Context) |  | ||||||
| 	if rctx != nil { |  | ||||||
| 		mx.handler.ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Fetch a RouteContext object from the sync pool, and call the computed |  | ||||||
| 	// mx.handler that is comprised of mx.middlewares + mx.routeHTTP. |  | ||||||
| 	// Once the request is finished, reset the routing context and put it back |  | ||||||
| 	// into the pool for reuse from another request. |  | ||||||
| 	rctx = mx.pool.Get().(*Context) |  | ||||||
| 	rctx.Reset() |  | ||||||
| 	rctx.Routes = mx |  | ||||||
| 	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx)) |  | ||||||
| 	mx.handler.ServeHTTP(w, r) |  | ||||||
| 	mx.pool.Put(rctx) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Use appends a middleware handler to the Mux middleware stack. |  | ||||||
| // |  | ||||||
| // The middleware stack for any Mux will execute before searching for a matching |  | ||||||
| // route to a specific handler, which provides opportunity to respond early, |  | ||||||
| // change the course of the request execution, or set request-scoped values for |  | ||||||
| // the next http.Handler. |  | ||||||
| func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { |  | ||||||
| 	if mx.handler != nil { |  | ||||||
| 		panic("chi: all middlewares must be defined before routes on a mux") |  | ||||||
| 	} |  | ||||||
| 	mx.middlewares = append(mx.middlewares, middlewares...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handle adds the route `pattern` that matches any http method to |  | ||||||
| // execute the `handler` http.Handler. |  | ||||||
| func (mx *Mux) Handle(pattern string, handler http.Handler) { |  | ||||||
| 	mx.handle(mALL, pattern, handler) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HandleFunc adds the route `pattern` that matches any http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mALL, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Method adds the route `pattern` that matches `method` http method to |  | ||||||
| // execute the `handler` http.Handler. |  | ||||||
| func (mx *Mux) Method(method, pattern string, handler http.Handler) { |  | ||||||
| 	m, ok := methodMap[strings.ToUpper(method)] |  | ||||||
| 	if !ok { |  | ||||||
| 		panic(fmt.Sprintf("chi: '%s' http method is not supported.", method)) |  | ||||||
| 	} |  | ||||||
| 	mx.handle(m, pattern, handler) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodFunc adds the route `pattern` that matches `method` http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.Method(method, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Connect adds the route `pattern` that matches a CONNECT http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mCONNECT, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Delete adds the route `pattern` that matches a DELETE http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mDELETE, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get adds the route `pattern` that matches a GET http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mGET, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Head adds the route `pattern` that matches a HEAD http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mHEAD, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Options adds the route `pattern` that matches a OPTIONS http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mOPTIONS, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Patch adds the route `pattern` that matches a PATCH http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPATCH, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Post adds the route `pattern` that matches a POST http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPOST, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Put adds the route `pattern` that matches a PUT http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPUT, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Trace adds the route `pattern` that matches a TRACE http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mTRACE, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NotFound sets a custom http.HandlerFunc for routing paths that could |  | ||||||
| // not be found. The default 404 handler is `http.NotFound`. |  | ||||||
| func (mx *Mux) NotFound(handlerFn http.HandlerFunc) { |  | ||||||
| 	// Build NotFound handler chain |  | ||||||
| 	m := mx |  | ||||||
| 	hFn := handlerFn |  | ||||||
| 	if mx.inline && mx.parent != nil { |  | ||||||
| 		m = mx.parent |  | ||||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Update the notFoundHandler from this point forward |  | ||||||
| 	m.notFoundHandler = hFn |  | ||||||
| 	m.updateSubRoutes(func(subMux *Mux) { |  | ||||||
| 		if subMux.notFoundHandler == nil { |  | ||||||
| 			subMux.NotFound(hFn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the |  | ||||||
| // method is unresolved. The default handler returns a 405 with an empty body. |  | ||||||
| func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { |  | ||||||
| 	// Build MethodNotAllowed handler chain |  | ||||||
| 	m := mx |  | ||||||
| 	hFn := handlerFn |  | ||||||
| 	if mx.inline && mx.parent != nil { |  | ||||||
| 		m = mx.parent |  | ||||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Update the methodNotAllowedHandler from this point forward |  | ||||||
| 	m.methodNotAllowedHandler = hFn |  | ||||||
| 	m.updateSubRoutes(func(subMux *Mux) { |  | ||||||
| 		if subMux.methodNotAllowedHandler == nil { |  | ||||||
| 			subMux.MethodNotAllowed(hFn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // With adds inline middlewares for an endpoint handler. |  | ||||||
| func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { |  | ||||||
| 	// Similarly as in handle(), we must build the mux handler once further |  | ||||||
| 	// middleware registration isn't allowed for this stack, like now. |  | ||||||
| 	if !mx.inline && mx.handler == nil { |  | ||||||
| 		mx.buildRouteHandler() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Copy middlewares from parent inline muxs |  | ||||||
| 	var mws Middlewares |  | ||||||
| 	if mx.inline { |  | ||||||
| 		mws = make(Middlewares, len(mx.middlewares)) |  | ||||||
| 		copy(mws, mx.middlewares) |  | ||||||
| 	} |  | ||||||
| 	mws = append(mws, middlewares...) |  | ||||||
| 
 |  | ||||||
| 	im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws} |  | ||||||
| 
 |  | ||||||
| 	return im |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Group creates a new inline-Mux with a fresh middleware stack. It's useful |  | ||||||
| // for a group of handlers along the same routing path that use an additional |  | ||||||
| // set of middlewares. See _examples/. |  | ||||||
| func (mx *Mux) Group(fn func(r Router)) Router { |  | ||||||
| 	im := mx.With().(*Mux) |  | ||||||
| 	if fn != nil { |  | ||||||
| 		fn(im) |  | ||||||
| 	} |  | ||||||
| 	return im |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Route creates a new Mux with a fresh middleware stack and mounts it |  | ||||||
| // along the `pattern` as a subrouter. Effectively, this is a short-hand |  | ||||||
| // call to Mount. See _examples/. |  | ||||||
| func (mx *Mux) Route(pattern string, fn func(r Router)) Router { |  | ||||||
| 	subRouter := NewRouter() |  | ||||||
| 	if fn != nil { |  | ||||||
| 		fn(subRouter) |  | ||||||
| 	} |  | ||||||
| 	mx.Mount(pattern, subRouter) |  | ||||||
| 	return subRouter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Mount attaches another http.Handler or chi Router as a subrouter along a routing |  | ||||||
| // path. It's very useful to split up a large API as many independent routers and |  | ||||||
| // compose them as a single service using Mount. See _examples/. |  | ||||||
| // |  | ||||||
| // Note that Mount() simply sets a wildcard along the `pattern` that will continue |  | ||||||
| // routing at the `handler`, which in most cases is another chi.Router. As a result, |  | ||||||
| // if you define two Mount() routes on the exact same pattern the mount will panic. |  | ||||||
| func (mx *Mux) Mount(pattern string, handler http.Handler) { |  | ||||||
| 	// Provide runtime safety for ensuring a pattern isn't mounted on an existing |  | ||||||
| 	// routing pattern. |  | ||||||
| 	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { |  | ||||||
| 		panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Assign sub-Router's with the parent not found & method not allowed handler if not specified. |  | ||||||
| 	subr, ok := handler.(*Mux) |  | ||||||
| 	if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil { |  | ||||||
| 		subr.NotFound(mx.notFoundHandler) |  | ||||||
| 	} |  | ||||||
| 	if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil { |  | ||||||
| 		subr.MethodNotAllowed(mx.methodNotAllowedHandler) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wrap the sub-router in a handlerFunc to scope the request path for routing. |  | ||||||
| 	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		rctx := RouteContext(r.Context()) |  | ||||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) |  | ||||||
| 		handler.ServeHTTP(w, r) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	if pattern == "" || pattern[len(pattern)-1] != '/' { |  | ||||||
| 		mx.handle(mALL|mSTUB, pattern, mountHandler) |  | ||||||
| 		mx.handle(mALL|mSTUB, pattern+"/", mountHandler) |  | ||||||
| 		pattern += "/" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	method := mALL |  | ||||||
| 	subroutes, _ := handler.(Routes) |  | ||||||
| 	if subroutes != nil { |  | ||||||
| 		method |= mSTUB |  | ||||||
| 	} |  | ||||||
| 	n := mx.handle(method, pattern+"*", mountHandler) |  | ||||||
| 
 |  | ||||||
| 	if subroutes != nil { |  | ||||||
| 		n.subroutes = subroutes |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes returns a slice of routing information from the tree, |  | ||||||
| // useful for traversing available routes of a router. |  | ||||||
| func (mx *Mux) Routes() []Route { |  | ||||||
| 	return mx.tree.routes() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Middlewares returns a slice of middleware handler functions. |  | ||||||
| func (mx *Mux) Middlewares() Middlewares { |  | ||||||
| 	return mx.middlewares |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Match searches the routing tree for a handler that matches the method/path. |  | ||||||
| // It's similar to routing a http request, but without executing the handler |  | ||||||
| // thereafter. |  | ||||||
| // |  | ||||||
| // Note: the *Context state is updated during execution, so manage |  | ||||||
| // the state carefully or make a NewRouteContext(). |  | ||||||
| func (mx *Mux) Match(rctx *Context, method, path string) bool { |  | ||||||
| 	m, ok := methodMap[method] |  | ||||||
| 	if !ok { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	node, _, h := mx.tree.FindRoute(rctx, m, path) |  | ||||||
| 
 |  | ||||||
| 	if node != nil && node.subroutes != nil { |  | ||||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) |  | ||||||
| 		return node.subroutes.Match(rctx, method, rctx.RoutePath) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return h != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NotFoundHandler returns the default Mux 404 responder whenever a route |  | ||||||
| // cannot be found. |  | ||||||
| func (mx *Mux) NotFoundHandler() http.HandlerFunc { |  | ||||||
| 	if mx.notFoundHandler != nil { |  | ||||||
| 		return mx.notFoundHandler |  | ||||||
| 	} |  | ||||||
| 	return http.NotFound |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodNotAllowedHandler returns the default Mux 405 responder whenever |  | ||||||
| // a method cannot be resolved for a route. |  | ||||||
| func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc { |  | ||||||
| 	if mx.methodNotAllowedHandler != nil { |  | ||||||
| 		return mx.methodNotAllowedHandler |  | ||||||
| 	} |  | ||||||
| 	return methodNotAllowedHandler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // buildRouteHandler builds the single mux handler that is a chain of the middleware |  | ||||||
| // stack, as defined by calls to Use(), and the tree router (Mux) itself. After this |  | ||||||
| // point, no other middlewares can be registered on this Mux's stack. But you can still |  | ||||||
| // compose additional middlewares via Group()'s or using a chained middleware handler. |  | ||||||
| func (mx *Mux) buildRouteHandler() { |  | ||||||
| 	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // handle registers a http.Handler in the routing tree for a particular http method |  | ||||||
| // and routing pattern. |  | ||||||
| func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node { |  | ||||||
| 	if len(pattern) == 0 || pattern[0] != '/' { |  | ||||||
| 		panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Build the final routing handler for this Mux. |  | ||||||
| 	if !mx.inline && mx.handler == nil { |  | ||||||
| 		mx.buildRouteHandler() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Build endpoint handler with inline middlewares for the route |  | ||||||
| 	var h http.Handler |  | ||||||
| 	if mx.inline { |  | ||||||
| 		mx.handler = http.HandlerFunc(mx.routeHTTP) |  | ||||||
| 		h = Chain(mx.middlewares...).Handler(handler) |  | ||||||
| 	} else { |  | ||||||
| 		h = handler |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Add the endpoint to the tree and return the node |  | ||||||
| 	return mx.tree.InsertRoute(method, pattern, h) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // routeHTTP routes a http.Request through the Mux routing tree to serve |  | ||||||
| // the matching handler for a particular http method. |  | ||||||
| func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	// Grab the route context object |  | ||||||
| 	rctx := r.Context().Value(RouteCtxKey).(*Context) |  | ||||||
| 
 |  | ||||||
| 	// The request routing path |  | ||||||
| 	routePath := rctx.RoutePath |  | ||||||
| 	if routePath == "" { |  | ||||||
| 		if r.URL.RawPath != "" { |  | ||||||
| 			routePath = r.URL.RawPath |  | ||||||
| 		} else { |  | ||||||
| 			routePath = r.URL.Path |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if method is supported by chi |  | ||||||
| 	if rctx.RouteMethod == "" { |  | ||||||
| 		rctx.RouteMethod = r.Method |  | ||||||
| 	} |  | ||||||
| 	method, ok := methodMap[rctx.RouteMethod] |  | ||||||
| 	if !ok { |  | ||||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Find the route |  | ||||||
| 	if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { |  | ||||||
| 		h.ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if rctx.methodNotAllowed { |  | ||||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) |  | ||||||
| 	} else { |  | ||||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (mx *Mux) nextRoutePath(rctx *Context) string { |  | ||||||
| 	routePath := "/" |  | ||||||
| 	nx := len(rctx.routeParams.Keys) - 1 // index of last param in list |  | ||||||
| 	if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx { |  | ||||||
| 		routePath += rctx.routeParams.Values[nx] |  | ||||||
| 	} |  | ||||||
| 	return routePath |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Recursively update data on child routers. |  | ||||||
| func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { |  | ||||||
| 	for _, r := range mx.tree.routes() { |  | ||||||
| 		subMux, ok := r.SubRoutes.(*Mux) |  | ||||||
| 		if !ok { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		fn(subMux) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // methodNotAllowedHandler is a helper function to respond with a 405, |  | ||||||
| // method not allowed. |  | ||||||
| func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	w.WriteHeader(405) |  | ||||||
| 	w.Write(nil) |  | ||||||
| } |  | ||||||
							
								
								
									
										847
									
								
								vendor/github.com/go-chi/chi/tree.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										847
									
								
								vendor/github.com/go-chi/chi/tree.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,847 +0,0 @@ | |||||||
| package chi |  | ||||||
| 
 |  | ||||||
| // Radix tree implementation below is a based on the original work by |  | ||||||
| // Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go |  | ||||||
| // (MIT licensed). It's been heavily modified for use as a HTTP routing tree. |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"math" |  | ||||||
| 	"net/http" |  | ||||||
| 	"regexp" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type methodTyp int |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	mSTUB methodTyp = 1 << iota |  | ||||||
| 	mCONNECT |  | ||||||
| 	mDELETE |  | ||||||
| 	mGET |  | ||||||
| 	mHEAD |  | ||||||
| 	mOPTIONS |  | ||||||
| 	mPATCH |  | ||||||
| 	mPOST |  | ||||||
| 	mPUT |  | ||||||
| 	mTRACE |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var mALL = mCONNECT | mDELETE | mGET | mHEAD | |  | ||||||
| 	mOPTIONS | mPATCH | mPOST | mPUT | mTRACE |  | ||||||
| 
 |  | ||||||
| var methodMap = map[string]methodTyp{ |  | ||||||
| 	http.MethodConnect: mCONNECT, |  | ||||||
| 	http.MethodDelete:  mDELETE, |  | ||||||
| 	http.MethodGet:     mGET, |  | ||||||
| 	http.MethodHead:    mHEAD, |  | ||||||
| 	http.MethodOptions: mOPTIONS, |  | ||||||
| 	http.MethodPatch:   mPATCH, |  | ||||||
| 	http.MethodPost:    mPOST, |  | ||||||
| 	http.MethodPut:     mPUT, |  | ||||||
| 	http.MethodTrace:   mTRACE, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RegisterMethod adds support for custom HTTP method handlers, available |  | ||||||
| // via Router#Method and Router#MethodFunc |  | ||||||
| func RegisterMethod(method string) { |  | ||||||
| 	if method == "" { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	method = strings.ToUpper(method) |  | ||||||
| 	if _, ok := methodMap[method]; ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	n := len(methodMap) |  | ||||||
| 	if n > strconv.IntSize { |  | ||||||
| 		panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) |  | ||||||
| 	} |  | ||||||
| 	mt := methodTyp(math.Exp2(float64(n))) |  | ||||||
| 	methodMap[method] = mt |  | ||||||
| 	mALL |= mt |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type nodeTyp uint8 |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	ntStatic   nodeTyp = iota // /home |  | ||||||
| 	ntRegexp                  // /{id:[0-9]+} |  | ||||||
| 	ntParam                   // /{user} |  | ||||||
| 	ntCatchAll                // /api/v1/* |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type node struct { |  | ||||||
| 	// node type: static, regexp, param, catchAll |  | ||||||
| 	typ nodeTyp |  | ||||||
| 
 |  | ||||||
| 	// first byte of the prefix |  | ||||||
| 	label byte |  | ||||||
| 
 |  | ||||||
| 	// first byte of the child prefix |  | ||||||
| 	tail byte |  | ||||||
| 
 |  | ||||||
| 	// prefix is the common prefix we ignore |  | ||||||
| 	prefix string |  | ||||||
| 
 |  | ||||||
| 	// regexp matcher for regexp nodes |  | ||||||
| 	rex *regexp.Regexp |  | ||||||
| 
 |  | ||||||
| 	// HTTP handler endpoints on the leaf node |  | ||||||
| 	endpoints endpoints |  | ||||||
| 
 |  | ||||||
| 	// subroutes on the leaf node |  | ||||||
| 	subroutes Routes |  | ||||||
| 
 |  | ||||||
| 	// child nodes should be stored in-order for iteration, |  | ||||||
| 	// in groups of the node type. |  | ||||||
| 	children [ntCatchAll + 1]nodes |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // endpoints is a mapping of http method constants to handlers |  | ||||||
| // for a given route. |  | ||||||
| type endpoints map[methodTyp]*endpoint |  | ||||||
| 
 |  | ||||||
| type endpoint struct { |  | ||||||
| 	// endpoint handler |  | ||||||
| 	handler http.Handler |  | ||||||
| 
 |  | ||||||
| 	// pattern is the routing pattern for handler nodes |  | ||||||
| 	pattern string |  | ||||||
| 
 |  | ||||||
| 	// parameter keys recorded on handler nodes |  | ||||||
| 	paramKeys []string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s endpoints) Value(method methodTyp) *endpoint { |  | ||||||
| 	mh, ok := s[method] |  | ||||||
| 	if !ok { |  | ||||||
| 		mh = &endpoint{} |  | ||||||
| 		s[method] = mh |  | ||||||
| 	} |  | ||||||
| 	return mh |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node { |  | ||||||
| 	var parent *node |  | ||||||
| 	search := pattern |  | ||||||
| 
 |  | ||||||
| 	for { |  | ||||||
| 		// Handle key exhaustion |  | ||||||
| 		if len(search) == 0 { |  | ||||||
| 			// Insert or update the node's leaf handler |  | ||||||
| 			n.setEndpoint(method, handler, pattern) |  | ||||||
| 			return n |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// We're going to be searching for a wild node next, |  | ||||||
| 		// in this case, we need to get the tail |  | ||||||
| 		var label = search[0] |  | ||||||
| 		var segTail byte |  | ||||||
| 		var segEndIdx int |  | ||||||
| 		var segTyp nodeTyp |  | ||||||
| 		var segRexpat string |  | ||||||
| 		if label == '{' || label == '*' { |  | ||||||
| 			segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var prefix string |  | ||||||
| 		if segTyp == ntRegexp { |  | ||||||
| 			prefix = segRexpat |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Look for the edge to attach to |  | ||||||
| 		parent = n |  | ||||||
| 		n = n.getEdge(segTyp, label, segTail, prefix) |  | ||||||
| 
 |  | ||||||
| 		// No edge, create one |  | ||||||
| 		if n == nil { |  | ||||||
| 			child := &node{label: label, tail: segTail, prefix: search} |  | ||||||
| 			hn := parent.addChild(child, search) |  | ||||||
| 			hn.setEndpoint(method, handler, pattern) |  | ||||||
| 
 |  | ||||||
| 			return hn |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Found an edge to match the pattern |  | ||||||
| 
 |  | ||||||
| 		if n.typ > ntStatic { |  | ||||||
| 			// We found a param node, trim the param from the search path and continue. |  | ||||||
| 			// This param/wild pattern segment would already be on the tree from a previous |  | ||||||
| 			// call to addChild when creating a new node. |  | ||||||
| 			search = search[segEndIdx:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Static nodes fall below here. |  | ||||||
| 		// Determine longest prefix of the search key on match. |  | ||||||
| 		commonPrefix := longestPrefix(search, n.prefix) |  | ||||||
| 		if commonPrefix == len(n.prefix) { |  | ||||||
| 			// the common prefix is as long as the current node's prefix we're attempting to insert. |  | ||||||
| 			// keep the search going. |  | ||||||
| 			search = search[commonPrefix:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Split the node |  | ||||||
| 		child := &node{ |  | ||||||
| 			typ:    ntStatic, |  | ||||||
| 			prefix: search[:commonPrefix], |  | ||||||
| 		} |  | ||||||
| 		parent.replaceChild(search[0], segTail, child) |  | ||||||
| 
 |  | ||||||
| 		// Restore the existing node |  | ||||||
| 		n.label = n.prefix[commonPrefix] |  | ||||||
| 		n.prefix = n.prefix[commonPrefix:] |  | ||||||
| 		child.addChild(n, n.prefix) |  | ||||||
| 
 |  | ||||||
| 		// If the new key is a subset, set the method/handler on this node and finish. |  | ||||||
| 		search = search[commonPrefix:] |  | ||||||
| 		if len(search) == 0 { |  | ||||||
| 			child.setEndpoint(method, handler, pattern) |  | ||||||
| 			return child |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Create a new edge for the node |  | ||||||
| 		subchild := &node{ |  | ||||||
| 			typ:    ntStatic, |  | ||||||
| 			label:  search[0], |  | ||||||
| 			prefix: search, |  | ||||||
| 		} |  | ||||||
| 		hn := child.addChild(subchild, search) |  | ||||||
| 		hn.setEndpoint(method, handler, pattern) |  | ||||||
| 		return hn |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // addChild appends the new `child` node to the tree using the `pattern` as the trie key. |  | ||||||
| // For a URL router like chi's, we split the static, param, regexp and wildcard segments |  | ||||||
| // into different nodes. In addition, addChild will recursively call itself until every |  | ||||||
| // pattern segment is added to the url pattern tree as individual nodes, depending on type. |  | ||||||
| func (n *node) addChild(child *node, prefix string) *node { |  | ||||||
| 	search := prefix |  | ||||||
| 
 |  | ||||||
| 	// handler leaf node added to the tree is the child. |  | ||||||
| 	// this may be overridden later down the flow |  | ||||||
| 	hn := child |  | ||||||
| 
 |  | ||||||
| 	// Parse next segment |  | ||||||
| 	segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) |  | ||||||
| 
 |  | ||||||
| 	// Add child depending on next up segment |  | ||||||
| 	switch segTyp { |  | ||||||
| 
 |  | ||||||
| 	case ntStatic: |  | ||||||
| 		// Search prefix is all static (that is, has no params in path) |  | ||||||
| 		// noop |  | ||||||
| 
 |  | ||||||
| 	default: |  | ||||||
| 		// Search prefix contains a param, regexp or wildcard |  | ||||||
| 
 |  | ||||||
| 		if segTyp == ntRegexp { |  | ||||||
| 			rex, err := regexp.Compile(segRexpat) |  | ||||||
| 			if err != nil { |  | ||||||
| 				panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat)) |  | ||||||
| 			} |  | ||||||
| 			child.prefix = segRexpat |  | ||||||
| 			child.rex = rex |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if segStartIdx == 0 { |  | ||||||
| 			// Route starts with a param |  | ||||||
| 			child.typ = segTyp |  | ||||||
| 
 |  | ||||||
| 			if segTyp == ntCatchAll { |  | ||||||
| 				segStartIdx = -1 |  | ||||||
| 			} else { |  | ||||||
| 				segStartIdx = segEndIdx |  | ||||||
| 			} |  | ||||||
| 			if segStartIdx < 0 { |  | ||||||
| 				segStartIdx = len(search) |  | ||||||
| 			} |  | ||||||
| 			child.tail = segTail // for params, we set the tail |  | ||||||
| 
 |  | ||||||
| 			if segStartIdx != len(search) { |  | ||||||
| 				// add static edge for the remaining part, split the end. |  | ||||||
| 				// its not possible to have adjacent param nodes, so its certainly |  | ||||||
| 				// going to be a static node next. |  | ||||||
| 
 |  | ||||||
| 				search = search[segStartIdx:] // advance search position |  | ||||||
| 
 |  | ||||||
| 				nn := &node{ |  | ||||||
| 					typ:    ntStatic, |  | ||||||
| 					label:  search[0], |  | ||||||
| 					prefix: search, |  | ||||||
| 				} |  | ||||||
| 				hn = child.addChild(nn, search) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		} else if segStartIdx > 0 { |  | ||||||
| 			// Route has some param |  | ||||||
| 
 |  | ||||||
| 			// starts with a static segment |  | ||||||
| 			child.typ = ntStatic |  | ||||||
| 			child.prefix = search[:segStartIdx] |  | ||||||
| 			child.rex = nil |  | ||||||
| 
 |  | ||||||
| 			// add the param edge node |  | ||||||
| 			search = search[segStartIdx:] |  | ||||||
| 
 |  | ||||||
| 			nn := &node{ |  | ||||||
| 				typ:   segTyp, |  | ||||||
| 				label: search[0], |  | ||||||
| 				tail:  segTail, |  | ||||||
| 			} |  | ||||||
| 			hn = child.addChild(nn, search) |  | ||||||
| 
 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	n.children[child.typ] = append(n.children[child.typ], child) |  | ||||||
| 	n.children[child.typ].Sort() |  | ||||||
| 	return hn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) replaceChild(label, tail byte, child *node) { |  | ||||||
| 	for i := 0; i < len(n.children[child.typ]); i++ { |  | ||||||
| 		if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail { |  | ||||||
| 			n.children[child.typ][i] = child |  | ||||||
| 			n.children[child.typ][i].label = label |  | ||||||
| 			n.children[child.typ][i].tail = tail |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	panic("chi: replacing missing child") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { |  | ||||||
| 	nds := n.children[ntyp] |  | ||||||
| 	for i := 0; i < len(nds); i++ { |  | ||||||
| 		if nds[i].label == label && nds[i].tail == tail { |  | ||||||
| 			if ntyp == ntRegexp && nds[i].prefix != prefix { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			return nds[i] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) { |  | ||||||
| 	// Set the handler for the method type on the node |  | ||||||
| 	if n.endpoints == nil { |  | ||||||
| 		n.endpoints = make(endpoints, 0) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	paramKeys := patParamKeys(pattern) |  | ||||||
| 
 |  | ||||||
| 	if method&mSTUB == mSTUB { |  | ||||||
| 		n.endpoints.Value(mSTUB).handler = handler |  | ||||||
| 	} |  | ||||||
| 	if method&mALL == mALL { |  | ||||||
| 		h := n.endpoints.Value(mALL) |  | ||||||
| 		h.handler = handler |  | ||||||
| 		h.pattern = pattern |  | ||||||
| 		h.paramKeys = paramKeys |  | ||||||
| 		for _, m := range methodMap { |  | ||||||
| 			h := n.endpoints.Value(m) |  | ||||||
| 			h.handler = handler |  | ||||||
| 			h.pattern = pattern |  | ||||||
| 			h.paramKeys = paramKeys |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		h := n.endpoints.Value(method) |  | ||||||
| 		h.handler = handler |  | ||||||
| 		h.pattern = pattern |  | ||||||
| 		h.paramKeys = paramKeys |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) { |  | ||||||
| 	// Reset the context routing pattern and params |  | ||||||
| 	rctx.routePattern = "" |  | ||||||
| 	rctx.routeParams.Keys = rctx.routeParams.Keys[:0] |  | ||||||
| 	rctx.routeParams.Values = rctx.routeParams.Values[:0] |  | ||||||
| 
 |  | ||||||
| 	// Find the routing handlers for the path |  | ||||||
| 	rn := n.findRoute(rctx, method, path) |  | ||||||
| 	if rn == nil { |  | ||||||
| 		return nil, nil, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Record the routing params in the request lifecycle |  | ||||||
| 	rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...) |  | ||||||
| 	rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...) |  | ||||||
| 
 |  | ||||||
| 	// Record the routing pattern in the request lifecycle |  | ||||||
| 	if rn.endpoints[method].pattern != "" { |  | ||||||
| 		rctx.routePattern = rn.endpoints[method].pattern |  | ||||||
| 		rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return rn, rn.endpoints, rn.endpoints[method].handler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Recursive edge traversal by checking all nodeTyp groups along the way. |  | ||||||
| // It's like searching through a multi-dimensional radix trie. |  | ||||||
| func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { |  | ||||||
| 	nn := n |  | ||||||
| 	search := path |  | ||||||
| 
 |  | ||||||
| 	for t, nds := range nn.children { |  | ||||||
| 		ntyp := nodeTyp(t) |  | ||||||
| 		if len(nds) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var xn *node |  | ||||||
| 		xsearch := search |  | ||||||
| 
 |  | ||||||
| 		var label byte |  | ||||||
| 		if search != "" { |  | ||||||
| 			label = search[0] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		switch ntyp { |  | ||||||
| 		case ntStatic: |  | ||||||
| 			xn = nds.findEdge(label) |  | ||||||
| 			if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			xsearch = xsearch[len(xn.prefix):] |  | ||||||
| 
 |  | ||||||
| 		case ntParam, ntRegexp: |  | ||||||
| 			// short-circuit and return no matching route for empty param values |  | ||||||
| 			if xsearch == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// serially loop through each node grouped by the tail delimiter |  | ||||||
| 			for idx := 0; idx < len(nds); idx++ { |  | ||||||
| 				xn = nds[idx] |  | ||||||
| 
 |  | ||||||
| 				// label for param nodes is the delimiter byte |  | ||||||
| 				p := strings.IndexByte(xsearch, xn.tail) |  | ||||||
| 
 |  | ||||||
| 				if p < 0 { |  | ||||||
| 					if xn.tail == '/' { |  | ||||||
| 						p = len(xsearch) |  | ||||||
| 					} else { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if ntyp == ntRegexp && xn.rex != nil { |  | ||||||
| 					if xn.rex.Match([]byte(xsearch[:p])) == false { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} else if strings.IndexByte(xsearch[:p], '/') != -1 { |  | ||||||
| 					// avoid a match across path segments |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) |  | ||||||
| 				xsearch = xsearch[p:] |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		default: |  | ||||||
| 			// catch-all nodes |  | ||||||
| 			rctx.routeParams.Values = append(rctx.routeParams.Values, search) |  | ||||||
| 			xn = nds[0] |  | ||||||
| 			xsearch = "" |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if xn == nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// did we find it yet? |  | ||||||
| 		if len(xsearch) == 0 { |  | ||||||
| 			if xn.isLeaf() { |  | ||||||
| 				h, _ := xn.endpoints[method] |  | ||||||
| 				if h != nil && h.handler != nil { |  | ||||||
| 					rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) |  | ||||||
| 					return xn |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// flag that the routing context found a route, but not a corresponding |  | ||||||
| 				// supported method |  | ||||||
| 				rctx.methodNotAllowed = true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// recursively find the next node.. |  | ||||||
| 		fin := xn.findRoute(rctx, method, xsearch) |  | ||||||
| 		if fin != nil { |  | ||||||
| 			return fin |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Did not find final handler, let's remove the param here if it was set |  | ||||||
| 		if xn.typ > ntStatic { |  | ||||||
| 			if len(rctx.routeParams.Values) > 0 { |  | ||||||
| 				rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) findEdge(ntyp nodeTyp, label byte) *node { |  | ||||||
| 	nds := n.children[ntyp] |  | ||||||
| 	num := len(nds) |  | ||||||
| 	idx := 0 |  | ||||||
| 
 |  | ||||||
| 	switch ntyp { |  | ||||||
| 	case ntStatic, ntParam, ntRegexp: |  | ||||||
| 		i, j := 0, num-1 |  | ||||||
| 		for i <= j { |  | ||||||
| 			idx = i + (j-i)/2 |  | ||||||
| 			if label > nds[idx].label { |  | ||||||
| 				i = idx + 1 |  | ||||||
| 			} else if label < nds[idx].label { |  | ||||||
| 				j = idx - 1 |  | ||||||
| 			} else { |  | ||||||
| 				i = num // breaks cond |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if nds[idx].label != label { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		return nds[idx] |  | ||||||
| 
 |  | ||||||
| 	default: // catch all |  | ||||||
| 		return nds[idx] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) isEmpty() bool { |  | ||||||
| 	for _, nds := range n.children { |  | ||||||
| 		if len(nds) > 0 { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) isLeaf() bool { |  | ||||||
| 	return n.endpoints != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) findPattern(pattern string) bool { |  | ||||||
| 	nn := n |  | ||||||
| 	for _, nds := range nn.children { |  | ||||||
| 		if len(nds) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		n = nn.findEdge(nds[0].typ, pattern[0]) |  | ||||||
| 		if n == nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var idx int |  | ||||||
| 		var xpattern string |  | ||||||
| 
 |  | ||||||
| 		switch n.typ { |  | ||||||
| 		case ntStatic: |  | ||||||
| 			idx = longestPrefix(pattern, n.prefix) |  | ||||||
| 			if idx < len(n.prefix) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		case ntParam, ntRegexp: |  | ||||||
| 			idx = strings.IndexByte(pattern, '}') + 1 |  | ||||||
| 
 |  | ||||||
| 		case ntCatchAll: |  | ||||||
| 			idx = longestPrefix(pattern, "*") |  | ||||||
| 
 |  | ||||||
| 		default: |  | ||||||
| 			panic("chi: unknown node type") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		xpattern = pattern[idx:] |  | ||||||
| 		if len(xpattern) == 0 { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return n.findPattern(xpattern) |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) routes() []Route { |  | ||||||
| 	rts := []Route{} |  | ||||||
| 
 |  | ||||||
| 	n.walk(func(eps endpoints, subroutes Routes) bool { |  | ||||||
| 		if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Group methodHandlers by unique patterns |  | ||||||
| 		pats := make(map[string]endpoints, 0) |  | ||||||
| 
 |  | ||||||
| 		for mt, h := range eps { |  | ||||||
| 			if h.pattern == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			p, ok := pats[h.pattern] |  | ||||||
| 			if !ok { |  | ||||||
| 				p = endpoints{} |  | ||||||
| 				pats[h.pattern] = p |  | ||||||
| 			} |  | ||||||
| 			p[mt] = h |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for p, mh := range pats { |  | ||||||
| 			hs := make(map[string]http.Handler, 0) |  | ||||||
| 			if mh[mALL] != nil && mh[mALL].handler != nil { |  | ||||||
| 				hs["*"] = mh[mALL].handler |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			for mt, h := range mh { |  | ||||||
| 				if h.handler == nil { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				m := methodTypString(mt) |  | ||||||
| 				if m == "" { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				hs[m] = h.handler |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			rt := Route{p, hs, subroutes} |  | ||||||
| 			rts = append(rts, rt) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return false |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	return rts |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool { |  | ||||||
| 	// Visit the leaf values if any |  | ||||||
| 	if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Recurse on the children |  | ||||||
| 	for _, ns := range n.children { |  | ||||||
| 		for _, cn := range ns { |  | ||||||
| 			if cn.walk(fn) { |  | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // patNextSegment returns the next segment details from a pattern: |  | ||||||
| // node type, param key, regexp string, param tail byte, param starting index, param ending index |  | ||||||
| func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { |  | ||||||
| 	ps := strings.Index(pattern, "{") |  | ||||||
| 	ws := strings.Index(pattern, "*") |  | ||||||
| 
 |  | ||||||
| 	if ps < 0 && ws < 0 { |  | ||||||
| 		return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Sanity check |  | ||||||
| 	if ws >= 0 && ws != len(pattern)-1 { |  | ||||||
| 		panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead") |  | ||||||
| 	} |  | ||||||
| 	if ps >= 0 && ws >= 0 && ws < ps { |  | ||||||
| 		panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var tail byte = '/' // Default endpoint tail to / byte |  | ||||||
| 
 |  | ||||||
| 	if ps >= 0 { |  | ||||||
| 		// Param/Regexp pattern is next |  | ||||||
| 		nt := ntParam |  | ||||||
| 
 |  | ||||||
| 		// Read to closing } taking into account opens and closes in curl count (cc) |  | ||||||
| 		cc := 0 |  | ||||||
| 		pe := ps |  | ||||||
| 		for i, c := range pattern[ps:] { |  | ||||||
| 			if c == '{' { |  | ||||||
| 				cc++ |  | ||||||
| 			} else if c == '}' { |  | ||||||
| 				cc-- |  | ||||||
| 				if cc == 0 { |  | ||||||
| 					pe = ps + i |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if pe == ps { |  | ||||||
| 			panic("chi: route param closing delimiter '}' is missing") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		key := pattern[ps+1 : pe] |  | ||||||
| 		pe++ // set end to next position |  | ||||||
| 
 |  | ||||||
| 		if pe < len(pattern) { |  | ||||||
| 			tail = pattern[pe] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var rexpat string |  | ||||||
| 		if idx := strings.Index(key, ":"); idx >= 0 { |  | ||||||
| 			nt = ntRegexp |  | ||||||
| 			rexpat = key[idx+1:] |  | ||||||
| 			key = key[:idx] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if len(rexpat) > 0 { |  | ||||||
| 			if rexpat[0] != '^' { |  | ||||||
| 				rexpat = "^" + rexpat |  | ||||||
| 			} |  | ||||||
| 			if rexpat[len(rexpat)-1] != '$' { |  | ||||||
| 				rexpat = rexpat + "$" |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nt, key, rexpat, tail, ps, pe |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wildcard pattern as finale |  | ||||||
| 	// TODO: should we panic if there is stuff after the * ??? |  | ||||||
| 	return ntCatchAll, "*", "", 0, ws, len(pattern) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func patParamKeys(pattern string) []string { |  | ||||||
| 	pat := pattern |  | ||||||
| 	paramKeys := []string{} |  | ||||||
| 	for { |  | ||||||
| 		ptyp, paramKey, _, _, _, e := patNextSegment(pat) |  | ||||||
| 		if ptyp == ntStatic { |  | ||||||
| 			return paramKeys |  | ||||||
| 		} |  | ||||||
| 		for i := 0; i < len(paramKeys); i++ { |  | ||||||
| 			if paramKeys[i] == paramKey { |  | ||||||
| 				panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		paramKeys = append(paramKeys, paramKey) |  | ||||||
| 		pat = pat[e:] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // longestPrefix finds the length of the shared prefix |  | ||||||
| // of two strings |  | ||||||
| func longestPrefix(k1, k2 string) int { |  | ||||||
| 	max := len(k1) |  | ||||||
| 	if l := len(k2); l < max { |  | ||||||
| 		max = l |  | ||||||
| 	} |  | ||||||
| 	var i int |  | ||||||
| 	for i = 0; i < max; i++ { |  | ||||||
| 		if k1[i] != k2[i] { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return i |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func methodTypString(method methodTyp) string { |  | ||||||
| 	for s, t := range methodMap { |  | ||||||
| 		if method == t { |  | ||||||
| 			return s |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type nodes []*node |  | ||||||
| 
 |  | ||||||
| // Sort the list of nodes by label |  | ||||||
| func (ns nodes) Sort()              { sort.Sort(ns); ns.tailSort() } |  | ||||||
| func (ns nodes) Len() int           { return len(ns) } |  | ||||||
| func (ns nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] } |  | ||||||
| func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label } |  | ||||||
| 
 |  | ||||||
| // tailSort pushes nodes with '/' as the tail to the end of the list for param nodes. |  | ||||||
| // The list order determines the traversal order. |  | ||||||
| func (ns nodes) tailSort() { |  | ||||||
| 	for i := len(ns) - 1; i >= 0; i-- { |  | ||||||
| 		if ns[i].typ > ntStatic && ns[i].tail == '/' { |  | ||||||
| 			ns.Swap(i, len(ns)-1) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ns nodes) findEdge(label byte) *node { |  | ||||||
| 	num := len(ns) |  | ||||||
| 	idx := 0 |  | ||||||
| 	i, j := 0, num-1 |  | ||||||
| 	for i <= j { |  | ||||||
| 		idx = i + (j-i)/2 |  | ||||||
| 		if label > ns[idx].label { |  | ||||||
| 			i = idx + 1 |  | ||||||
| 		} else if label < ns[idx].label { |  | ||||||
| 			j = idx - 1 |  | ||||||
| 		} else { |  | ||||||
| 			i = num // breaks cond |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if ns[idx].label != label { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return ns[idx] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Route describes the details of a routing handler. |  | ||||||
| type Route struct { |  | ||||||
| 	Pattern   string |  | ||||||
| 	Handlers  map[string]http.Handler |  | ||||||
| 	SubRoutes Routes |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WalkFunc is the type of the function called for each method and route visited by Walk. |  | ||||||
| type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error |  | ||||||
| 
 |  | ||||||
| // Walk walks any router tree that implements Routes interface. |  | ||||||
| func Walk(r Routes, walkFn WalkFunc) error { |  | ||||||
| 	return walk(r, walkFn, "") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error { |  | ||||||
| 	for _, route := range r.Routes() { |  | ||||||
| 		mws := make([]func(http.Handler) http.Handler, len(parentMw)) |  | ||||||
| 		copy(mws, parentMw) |  | ||||||
| 		mws = append(mws, r.Middlewares()...) |  | ||||||
| 
 |  | ||||||
| 		if route.SubRoutes != nil { |  | ||||||
| 			if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for method, handler := range route.Handlers { |  | ||||||
| 			if method == "*" { |  | ||||||
| 				// Ignore a "catchAll" method, since we pass down all the specific methods for each route. |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			fullRoute := parentRoute + route.Pattern |  | ||||||
| 
 |  | ||||||
| 			if chain, ok := handler.(*ChainHandler); ok { |  | ||||||
| 				if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				if err := walkFn(method, fullRoute, handler, mws...); err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/google/uuid/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/google/uuid/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,9 +0,0 @@ | |||||||
| language: go |  | ||||||
| 
 |  | ||||||
| go: |  | ||||||
|   - 1.4.3 |  | ||||||
|   - 1.5.3 |  | ||||||
|   - tip |  | ||||||
| 
 |  | ||||||
| script: |  | ||||||
|   - go test -v ./... |  | ||||||
							
								
								
									
										10
									
								
								vendor/github.com/google/uuid/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/google/uuid/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,10 +0,0 @@ | |||||||
| # How to contribute |  | ||||||
| 
 |  | ||||||
| We definitely welcome patches and contribution to this project! |  | ||||||
| 
 |  | ||||||
| ### Legal requirements |  | ||||||
| 
 |  | ||||||
| In order to protect both you and ourselves, you will need to sign the |  | ||||||
| [Contributor License Agreement](https://cla.developers.google.com/clas). |  | ||||||
| 
 |  | ||||||
| You may have already signed it for other Google projects. |  | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/google/uuid/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/google/uuid/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,9 +0,0 @@ | |||||||
| Paul Borman <borman@google.com> |  | ||||||
| bmatsuo |  | ||||||
| shawnps |  | ||||||
| theory |  | ||||||
| jboverfelt |  | ||||||
| dsymonds |  | ||||||
| cd1 |  | ||||||
| wallclockbuilder |  | ||||||
| dansouza |  | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/google/uuid/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/google/uuid/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,27 +0,0 @@ | |||||||
| Copyright (c) 2009,2014 Google Inc. All rights reserved. |  | ||||||
| 
 |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are |  | ||||||
| met: |  | ||||||
| 
 |  | ||||||
|    * Redistributions of source code must retain the above copyright |  | ||||||
| notice, this list of conditions and the following disclaimer. |  | ||||||
|    * Redistributions in binary form must reproduce the above |  | ||||||
| copyright notice, this list of conditions and the following disclaimer |  | ||||||
| in the documentation and/or other materials provided with the |  | ||||||
| distribution. |  | ||||||
|    * Neither the name of Google Inc. nor the names of its |  | ||||||
| contributors may be used to endorse or promote products derived from |  | ||||||
| this software without specific prior written permission. |  | ||||||
| 
 |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |  | ||||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | ||||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |  | ||||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |  | ||||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |  | ||||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
							
								
								
									
										19
									
								
								vendor/github.com/google/uuid/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/google/uuid/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,19 +0,0 @@ | |||||||
| # uuid  |  | ||||||
| The uuid package generates and inspects UUIDs based on |  | ||||||
| [RFC 4122](http://tools.ietf.org/html/rfc4122) |  | ||||||
| and DCE 1.1: Authentication and Security Services.  |  | ||||||
| 
 |  | ||||||
| This package is based on the github.com/pborman/uuid package (previously named |  | ||||||
| code.google.com/p/go-uuid).  It differs from these earlier packages in that |  | ||||||
| a UUID is a 16 byte array rather than a byte slice.  One loss due to this |  | ||||||
| change is the ability to represent an invalid UUID (vs a NIL UUID). |  | ||||||
| 
 |  | ||||||
| ###### Install |  | ||||||
| `go get github.com/google/uuid` |  | ||||||
| 
 |  | ||||||
| ###### Documentation  |  | ||||||
| [](http://godoc.org/github.com/google/uuid) |  | ||||||
| 
 |  | ||||||
| Full `go doc` style documentation for the package can be viewed online without |  | ||||||
| installing this package by using the GoDoc site here:  |  | ||||||
| http://godoc.org/github.com/google/uuid |  | ||||||
							
								
								
									
										80
									
								
								vendor/github.com/google/uuid/dce.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										80
									
								
								vendor/github.com/google/uuid/dce.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,80 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // A Domain represents a Version 2 domain |  | ||||||
| type Domain byte |  | ||||||
| 
 |  | ||||||
| // Domain constants for DCE Security (Version 2) UUIDs. |  | ||||||
| const ( |  | ||||||
| 	Person = Domain(0) |  | ||||||
| 	Group  = Domain(1) |  | ||||||
| 	Org    = Domain(2) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewDCESecurity returns a DCE Security (Version 2) UUID. |  | ||||||
| // |  | ||||||
| // The domain should be one of Person, Group or Org. |  | ||||||
| // On a POSIX system the id should be the users UID for the Person |  | ||||||
| // domain and the users GID for the Group.  The meaning of id for |  | ||||||
| // the domain Org or on non-POSIX systems is site defined. |  | ||||||
| // |  | ||||||
| // For a given domain/id pair the same token may be returned for up to |  | ||||||
| // 7 minutes and 10 seconds. |  | ||||||
| func NewDCESecurity(domain Domain, id uint32) (UUID, error) { |  | ||||||
| 	uuid, err := NewUUID() |  | ||||||
| 	if err == nil { |  | ||||||
| 		uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 |  | ||||||
| 		uuid[9] = byte(domain) |  | ||||||
| 		binary.BigEndian.PutUint32(uuid[0:], id) |  | ||||||
| 	} |  | ||||||
| 	return uuid, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewDCEPerson returns a DCE Security (Version 2) UUID in the person |  | ||||||
| // domain with the id returned by os.Getuid. |  | ||||||
| // |  | ||||||
| //  NewDCESecurity(Person, uint32(os.Getuid())) |  | ||||||
| func NewDCEPerson() (UUID, error) { |  | ||||||
| 	return NewDCESecurity(Person, uint32(os.Getuid())) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewDCEGroup returns a DCE Security (Version 2) UUID in the group |  | ||||||
| // domain with the id returned by os.Getgid. |  | ||||||
| // |  | ||||||
| //  NewDCESecurity(Group, uint32(os.Getgid())) |  | ||||||
| func NewDCEGroup() (UUID, error) { |  | ||||||
| 	return NewDCESecurity(Group, uint32(os.Getgid())) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Domain returns the domain for a Version 2 UUID.  Domains are only defined |  | ||||||
| // for Version 2 UUIDs. |  | ||||||
| func (uuid UUID) Domain() Domain { |  | ||||||
| 	return Domain(uuid[9]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 |  | ||||||
| // UUIDs. |  | ||||||
| func (uuid UUID) ID() uint32 { |  | ||||||
| 	return binary.BigEndian.Uint32(uuid[0:4]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (d Domain) String() string { |  | ||||||
| 	switch d { |  | ||||||
| 	case Person: |  | ||||||
| 		return "Person" |  | ||||||
| 	case Group: |  | ||||||
| 		return "Group" |  | ||||||
| 	case Org: |  | ||||||
| 		return "Org" |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("Domain%d", int(d)) |  | ||||||
| } |  | ||||||
							
								
								
									
										12
									
								
								vendor/github.com/google/uuid/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/google/uuid/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,12 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| // Package uuid generates and inspects UUIDs. |  | ||||||
| // |  | ||||||
| // UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security |  | ||||||
| // Services. |  | ||||||
| // |  | ||||||
| // A UUID is a 16 byte (128 bit) array.  UUIDs may be used as keys to |  | ||||||
| // maps or compared directly. |  | ||||||
| package uuid |  | ||||||
							
								
								
									
										1
									
								
								vendor/github.com/google/uuid/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/google/uuid/go.mod
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| module github.com/google/uuid |  | ||||||
							
								
								
									
										53
									
								
								vendor/github.com/google/uuid/hash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/google/uuid/hash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,53 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/md5" |  | ||||||
| 	"crypto/sha1" |  | ||||||
| 	"hash" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Well known namespace IDs and UUIDs |  | ||||||
| var ( |  | ||||||
| 	NameSpaceDNS  = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NameSpaceURL  = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NameSpaceOID  = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	Nil           UUID // empty UUID, all zeros |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewHash returns a new UUID derived from the hash of space concatenated with |  | ||||||
| // data generated by h.  The hash should be at least 16 byte in length.  The |  | ||||||
| // first 16 bytes of the hash are used to form the UUID.  The version of the |  | ||||||
| // UUID will be the lower 4 bits of version.  NewHash is used to implement |  | ||||||
| // NewMD5 and NewSHA1. |  | ||||||
| func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { |  | ||||||
| 	h.Reset() |  | ||||||
| 	h.Write(space[:]) |  | ||||||
| 	h.Write(data) |  | ||||||
| 	s := h.Sum(nil) |  | ||||||
| 	var uuid UUID |  | ||||||
| 	copy(uuid[:], s) |  | ||||||
| 	uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) |  | ||||||
| 	uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant |  | ||||||
| 	return uuid |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMD5 returns a new MD5 (Version 3) UUID based on the |  | ||||||
| // supplied name space and data.  It is the same as calling: |  | ||||||
| // |  | ||||||
| //  NewHash(md5.New(), space, data, 3) |  | ||||||
| func NewMD5(space UUID, data []byte) UUID { |  | ||||||
| 	return NewHash(md5.New(), space, data, 3) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewSHA1 returns a new SHA1 (Version 5) UUID based on the |  | ||||||
| // supplied name space and data.  It is the same as calling: |  | ||||||
| // |  | ||||||
| //  NewHash(sha1.New(), space, data, 5) |  | ||||||
| func NewSHA1(space UUID, data []byte) UUID { |  | ||||||
| 	return NewHash(sha1.New(), space, data, 5) |  | ||||||
| } |  | ||||||
							
								
								
									
										37
									
								
								vendor/github.com/google/uuid/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/google/uuid/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,37 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import "fmt" |  | ||||||
| 
 |  | ||||||
| // MarshalText implements encoding.TextMarshaler. |  | ||||||
| func (uuid UUID) MarshalText() ([]byte, error) { |  | ||||||
| 	var js [36]byte |  | ||||||
| 	encodeHex(js[:], uuid) |  | ||||||
| 	return js[:], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // UnmarshalText implements encoding.TextUnmarshaler. |  | ||||||
| func (uuid *UUID) UnmarshalText(data []byte) error { |  | ||||||
| 	id, err := ParseBytes(data) |  | ||||||
| 	if err == nil { |  | ||||||
| 		*uuid = id |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalBinary implements encoding.BinaryMarshaler. |  | ||||||
| func (uuid UUID) MarshalBinary() ([]byte, error) { |  | ||||||
| 	return uuid[:], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // UnmarshalBinary implements encoding.BinaryUnmarshaler. |  | ||||||
| func (uuid *UUID) UnmarshalBinary(data []byte) error { |  | ||||||
| 	if len(data) != 16 { |  | ||||||
| 		return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) |  | ||||||
| 	} |  | ||||||
| 	copy(uuid[:], data) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										90
									
								
								vendor/github.com/google/uuid/node.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										90
									
								
								vendor/github.com/google/uuid/node.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,90 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"sync" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	nodeMu sync.Mutex |  | ||||||
| 	ifname string  // name of interface being used |  | ||||||
| 	nodeID [6]byte // hardware for version 1 UUIDs |  | ||||||
| 	zeroID [6]byte // nodeID with only 0's |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NodeInterface returns the name of the interface from which the NodeID was |  | ||||||
| // derived.  The interface "user" is returned if the NodeID was set by |  | ||||||
| // SetNodeID. |  | ||||||
| func NodeInterface() string { |  | ||||||
| 	defer nodeMu.Unlock() |  | ||||||
| 	nodeMu.Lock() |  | ||||||
| 	return ifname |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. |  | ||||||
| // If name is "" then the first usable interface found will be used or a random |  | ||||||
| // Node ID will be generated.  If a named interface cannot be found then false |  | ||||||
| // is returned. |  | ||||||
| // |  | ||||||
| // SetNodeInterface never fails when name is "". |  | ||||||
| func SetNodeInterface(name string) bool { |  | ||||||
| 	defer nodeMu.Unlock() |  | ||||||
| 	nodeMu.Lock() |  | ||||||
| 	return setNodeInterface(name) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func setNodeInterface(name string) bool { |  | ||||||
| 	iname, addr := getHardwareInterface(name) // null implementation for js |  | ||||||
| 	if iname != "" && addr != nil { |  | ||||||
| 		ifname = iname |  | ||||||
| 		copy(nodeID[:], addr) |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// We found no interfaces with a valid hardware address.  If name |  | ||||||
| 	// does not specify a specific interface generate a random Node ID |  | ||||||
| 	// (section 4.1.6) |  | ||||||
| 	if name == "" { |  | ||||||
| 		ifname = "random" |  | ||||||
| 		randomBits(nodeID[:]) |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NodeID returns a slice of a copy of the current Node ID, setting the Node ID |  | ||||||
| // if not already set. |  | ||||||
| func NodeID() []byte { |  | ||||||
| 	defer nodeMu.Unlock() |  | ||||||
| 	nodeMu.Lock() |  | ||||||
| 	if nodeID == zeroID { |  | ||||||
| 		setNodeInterface("") |  | ||||||
| 	} |  | ||||||
| 	nid := nodeID |  | ||||||
| 	return nid[:] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetNodeID sets the Node ID to be used for Version 1 UUIDs.  The first 6 bytes |  | ||||||
| // of id are used.  If id is less than 6 bytes then false is returned and the |  | ||||||
| // Node ID is not set. |  | ||||||
| func SetNodeID(id []byte) bool { |  | ||||||
| 	if len(id) < 6 { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	defer nodeMu.Unlock() |  | ||||||
| 	nodeMu.Lock() |  | ||||||
| 	copy(nodeID[:], id) |  | ||||||
| 	ifname = "user" |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NodeID returns the 6 byte node id encoded in uuid.  It returns nil if uuid is |  | ||||||
| // not valid.  The NodeID is only well defined for version 1 and 2 UUIDs. |  | ||||||
| func (uuid UUID) NodeID() []byte { |  | ||||||
| 	var node [6]byte |  | ||||||
| 	copy(node[:], uuid[10:]) |  | ||||||
| 	return node[:] |  | ||||||
| } |  | ||||||
							
								
								
									
										12
									
								
								vendor/github.com/google/uuid/node_js.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/google/uuid/node_js.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,12 +0,0 @@ | |||||||
| // Copyright 2017 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| // +build js |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| // getHardwareInterface returns nil values for the JS version of the code. |  | ||||||
| // This remvoves the "net" dependency, because it is not used in the browser. |  | ||||||
| // Using the "net" library inflates the size of the transpiled JS code by 673k bytes. |  | ||||||
| func getHardwareInterface(name string) (string, []byte) { return "", nil } |  | ||||||
							
								
								
									
										33
									
								
								vendor/github.com/google/uuid/node_net.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/google/uuid/node_net.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,33 +0,0 @@ | |||||||
| // Copyright 2017 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| // +build !js |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import "net" |  | ||||||
| 
 |  | ||||||
| var interfaces []net.Interface // cached list of interfaces |  | ||||||
| 
 |  | ||||||
| // getHardwareInterface returns the name and hardware address of interface name. |  | ||||||
| // If name is "" then the name and hardware address of one of the system's |  | ||||||
| // interfaces is returned.  If no interfaces are found (name does not exist or |  | ||||||
| // there are no interfaces) then "", nil is returned. |  | ||||||
| // |  | ||||||
| // Only addresses of at least 6 bytes are returned. |  | ||||||
| func getHardwareInterface(name string) (string, []byte) { |  | ||||||
| 	if interfaces == nil { |  | ||||||
| 		var err error |  | ||||||
| 		interfaces, err = net.Interfaces() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	for _, ifs := range interfaces { |  | ||||||
| 		if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { |  | ||||||
| 			return ifs.Name, ifs.HardwareAddr |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "", nil |  | ||||||
| } |  | ||||||
							
								
								
									
										59
									
								
								vendor/github.com/google/uuid/sql.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/google/uuid/sql.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,59 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"database/sql/driver" |  | ||||||
| 	"fmt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Scan implements sql.Scanner so UUIDs can be read from databases transparently |  | ||||||
| // Currently, database types that map to string and []byte are supported. Please |  | ||||||
| // consult database-specific driver documentation for matching types. |  | ||||||
| func (uuid *UUID) Scan(src interface{}) error { |  | ||||||
| 	switch src := src.(type) { |  | ||||||
| 	case nil: |  | ||||||
| 		return nil |  | ||||||
| 
 |  | ||||||
| 	case string: |  | ||||||
| 		// if an empty UUID comes from a table, we return a null UUID |  | ||||||
| 		if src == "" { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// see Parse for required string format |  | ||||||
| 		u, err := Parse(src) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("Scan: %v", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		*uuid = u |  | ||||||
| 
 |  | ||||||
| 	case []byte: |  | ||||||
| 		// if an empty UUID comes from a table, we return a null UUID |  | ||||||
| 		if len(src) == 0 { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// assumes a simple slice of bytes if 16 bytes |  | ||||||
| 		// otherwise attempts to parse |  | ||||||
| 		if len(src) != 16 { |  | ||||||
| 			return uuid.Scan(string(src)) |  | ||||||
| 		} |  | ||||||
| 		copy((*uuid)[:], src) |  | ||||||
| 
 |  | ||||||
| 	default: |  | ||||||
| 		return fmt.Errorf("Scan: unable to scan type %T into UUID", src) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Value implements sql.Valuer so that UUIDs can be written to databases |  | ||||||
| // transparently. Currently, UUIDs map to strings. Please consult |  | ||||||
| // database-specific driver documentation for matching types. |  | ||||||
| func (uuid UUID) Value() (driver.Value, error) { |  | ||||||
| 	return uuid.String(), nil |  | ||||||
| } |  | ||||||
							
								
								
									
										123
									
								
								vendor/github.com/google/uuid/time.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/google/uuid/time.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,123 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // A Time represents a time as the number of 100's of nanoseconds since 15 Oct |  | ||||||
| // 1582. |  | ||||||
| type Time int64 |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	lillian    = 2299160          // Julian day of 15 Oct 1582 |  | ||||||
| 	unix       = 2440587          // Julian day of 1 Jan 1970 |  | ||||||
| 	epoch      = unix - lillian   // Days between epochs |  | ||||||
| 	g1582      = epoch * 86400    // seconds between epochs |  | ||||||
| 	g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	timeMu   sync.Mutex |  | ||||||
| 	lasttime uint64 // last time we returned |  | ||||||
| 	clockSeq uint16 // clock sequence for this run |  | ||||||
| 
 |  | ||||||
| 	timeNow = time.Now // for testing |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // UnixTime converts t the number of seconds and nanoseconds using the Unix |  | ||||||
| // epoch of 1 Jan 1970. |  | ||||||
| func (t Time) UnixTime() (sec, nsec int64) { |  | ||||||
| 	sec = int64(t - g1582ns100) |  | ||||||
| 	nsec = (sec % 10000000) * 100 |  | ||||||
| 	sec /= 10000000 |  | ||||||
| 	return sec, nsec |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and |  | ||||||
| // clock sequence as well as adjusting the clock sequence as needed.  An error |  | ||||||
| // is returned if the current time cannot be determined. |  | ||||||
| func GetTime() (Time, uint16, error) { |  | ||||||
| 	defer timeMu.Unlock() |  | ||||||
| 	timeMu.Lock() |  | ||||||
| 	return getTime() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getTime() (Time, uint16, error) { |  | ||||||
| 	t := timeNow() |  | ||||||
| 
 |  | ||||||
| 	// If we don't have a clock sequence already, set one. |  | ||||||
| 	if clockSeq == 0 { |  | ||||||
| 		setClockSequence(-1) |  | ||||||
| 	} |  | ||||||
| 	now := uint64(t.UnixNano()/100) + g1582ns100 |  | ||||||
| 
 |  | ||||||
| 	// If time has gone backwards with this clock sequence then we |  | ||||||
| 	// increment the clock sequence |  | ||||||
| 	if now <= lasttime { |  | ||||||
| 		clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 |  | ||||||
| 	} |  | ||||||
| 	lasttime = now |  | ||||||
| 	return Time(now), clockSeq, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ClockSequence returns the current clock sequence, generating one if not |  | ||||||
| // already set.  The clock sequence is only used for Version 1 UUIDs. |  | ||||||
| // |  | ||||||
| // The uuid package does not use global static storage for the clock sequence or |  | ||||||
| // the last time a UUID was generated.  Unless SetClockSequence is used, a new |  | ||||||
| // random clock sequence is generated the first time a clock sequence is |  | ||||||
| // requested by ClockSequence, GetTime, or NewUUID.  (section 4.2.1.1) |  | ||||||
| func ClockSequence() int { |  | ||||||
| 	defer timeMu.Unlock() |  | ||||||
| 	timeMu.Lock() |  | ||||||
| 	return clockSequence() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func clockSequence() int { |  | ||||||
| 	if clockSeq == 0 { |  | ||||||
| 		setClockSequence(-1) |  | ||||||
| 	} |  | ||||||
| 	return int(clockSeq & 0x3fff) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetClockSequence sets the clock sequence to the lower 14 bits of seq.  Setting to |  | ||||||
| // -1 causes a new sequence to be generated. |  | ||||||
| func SetClockSequence(seq int) { |  | ||||||
| 	defer timeMu.Unlock() |  | ||||||
| 	timeMu.Lock() |  | ||||||
| 	setClockSequence(seq) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func setClockSequence(seq int) { |  | ||||||
| 	if seq == -1 { |  | ||||||
| 		var b [2]byte |  | ||||||
| 		randomBits(b[:]) // clock sequence |  | ||||||
| 		seq = int(b[0])<<8 | int(b[1]) |  | ||||||
| 	} |  | ||||||
| 	oldSeq := clockSeq |  | ||||||
| 	clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant |  | ||||||
| 	if oldSeq != clockSeq { |  | ||||||
| 		lasttime = 0 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in |  | ||||||
| // uuid.  The time is only defined for version 1 and 2 UUIDs. |  | ||||||
| func (uuid UUID) Time() Time { |  | ||||||
| 	time := int64(binary.BigEndian.Uint32(uuid[0:4])) |  | ||||||
| 	time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 |  | ||||||
| 	time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 |  | ||||||
| 	return Time(time) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ClockSequence returns the clock sequence encoded in uuid. |  | ||||||
| // The clock sequence is only well defined for version 1 and 2 UUIDs. |  | ||||||
| func (uuid UUID) ClockSequence() int { |  | ||||||
| 	return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff |  | ||||||
| } |  | ||||||
							
								
								
									
										43
									
								
								vendor/github.com/google/uuid/util.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/google/uuid/util.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,43 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"io" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // randomBits completely fills slice b with random data. |  | ||||||
| func randomBits(b []byte) { |  | ||||||
| 	if _, err := io.ReadFull(rander, b); err != nil { |  | ||||||
| 		panic(err.Error()) // rand should never fail |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // xvalues returns the value of a byte as a hexadecimal digit or 255. |  | ||||||
| var xvalues = [256]byte{ |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| 	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // xtob converts hex characters x1 and x2 into a byte. |  | ||||||
| func xtob(x1, x2 byte) (byte, bool) { |  | ||||||
| 	b1 := xvalues[x1] |  | ||||||
| 	b2 := xvalues[x2] |  | ||||||
| 	return (b1 << 4) | b2, b1 != 255 && b2 != 255 |  | ||||||
| } |  | ||||||
							
								
								
									
										245
									
								
								vendor/github.com/google/uuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										245
									
								
								vendor/github.com/google/uuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,245 +0,0 @@ | |||||||
| // Copyright 2018 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC |  | ||||||
| // 4122. |  | ||||||
| type UUID [16]byte |  | ||||||
| 
 |  | ||||||
| // A Version represents a UUID's version. |  | ||||||
| type Version byte |  | ||||||
| 
 |  | ||||||
| // A Variant represents a UUID's variant. |  | ||||||
| type Variant byte |  | ||||||
| 
 |  | ||||||
| // Constants returned by Variant. |  | ||||||
| const ( |  | ||||||
| 	Invalid   = Variant(iota) // Invalid UUID |  | ||||||
| 	RFC4122                   // The variant specified in RFC4122 |  | ||||||
| 	Reserved                  // Reserved, NCS backward compatibility. |  | ||||||
| 	Microsoft                 // Reserved, Microsoft Corporation backward compatibility. |  | ||||||
| 	Future                    // Reserved for future definition. |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var rander = rand.Reader // random function |  | ||||||
| 
 |  | ||||||
| // Parse decodes s into a UUID or returns an error.  Both the standard UUID |  | ||||||
| // forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and |  | ||||||
| // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the |  | ||||||
| // Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex |  | ||||||
| // encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. |  | ||||||
| func Parse(s string) (UUID, error) { |  | ||||||
| 	var uuid UUID |  | ||||||
| 	switch len(s) { |  | ||||||
| 	// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 	case 36: |  | ||||||
| 
 |  | ||||||
| 	// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 	case 36 + 9: |  | ||||||
| 		if strings.ToLower(s[:9]) != "urn:uuid:" { |  | ||||||
| 			return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) |  | ||||||
| 		} |  | ||||||
| 		s = s[9:] |  | ||||||
| 
 |  | ||||||
| 	// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} |  | ||||||
| 	case 36 + 2: |  | ||||||
| 		s = s[1:] |  | ||||||
| 
 |  | ||||||
| 	// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |  | ||||||
| 	case 32: |  | ||||||
| 		var ok bool |  | ||||||
| 		for i := range uuid { |  | ||||||
| 			uuid[i], ok = xtob(s[i*2], s[i*2+1]) |  | ||||||
| 			if !ok { |  | ||||||
| 				return uuid, errors.New("invalid UUID format") |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return uuid, nil |  | ||||||
| 	default: |  | ||||||
| 		return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) |  | ||||||
| 	} |  | ||||||
| 	// s is now at least 36 bytes long |  | ||||||
| 	// it must be of the form  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 	if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { |  | ||||||
| 		return uuid, errors.New("invalid UUID format") |  | ||||||
| 	} |  | ||||||
| 	for i, x := range [16]int{ |  | ||||||
| 		0, 2, 4, 6, |  | ||||||
| 		9, 11, |  | ||||||
| 		14, 16, |  | ||||||
| 		19, 21, |  | ||||||
| 		24, 26, 28, 30, 32, 34} { |  | ||||||
| 		v, ok := xtob(s[x], s[x+1]) |  | ||||||
| 		if !ok { |  | ||||||
| 			return uuid, errors.New("invalid UUID format") |  | ||||||
| 		} |  | ||||||
| 		uuid[i] = v |  | ||||||
| 	} |  | ||||||
| 	return uuid, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseBytes is like Parse, except it parses a byte slice instead of a string. |  | ||||||
| func ParseBytes(b []byte) (UUID, error) { |  | ||||||
| 	var uuid UUID |  | ||||||
| 	switch len(b) { |  | ||||||
| 	case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 	case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 		if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { |  | ||||||
| 			return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) |  | ||||||
| 		} |  | ||||||
| 		b = b[9:] |  | ||||||
| 	case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} |  | ||||||
| 		b = b[1:] |  | ||||||
| 	case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |  | ||||||
| 		var ok bool |  | ||||||
| 		for i := 0; i < 32; i += 2 { |  | ||||||
| 			uuid[i/2], ok = xtob(b[i], b[i+1]) |  | ||||||
| 			if !ok { |  | ||||||
| 				return uuid, errors.New("invalid UUID format") |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return uuid, nil |  | ||||||
| 	default: |  | ||||||
| 		return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) |  | ||||||
| 	} |  | ||||||
| 	// s is now at least 36 bytes long |  | ||||||
| 	// it must be of the form  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| 	if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { |  | ||||||
| 		return uuid, errors.New("invalid UUID format") |  | ||||||
| 	} |  | ||||||
| 	for i, x := range [16]int{ |  | ||||||
| 		0, 2, 4, 6, |  | ||||||
| 		9, 11, |  | ||||||
| 		14, 16, |  | ||||||
| 		19, 21, |  | ||||||
| 		24, 26, 28, 30, 32, 34} { |  | ||||||
| 		v, ok := xtob(b[x], b[x+1]) |  | ||||||
| 		if !ok { |  | ||||||
| 			return uuid, errors.New("invalid UUID format") |  | ||||||
| 		} |  | ||||||
| 		uuid[i] = v |  | ||||||
| 	} |  | ||||||
| 	return uuid, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MustParse is like Parse but panics if the string cannot be parsed. |  | ||||||
| // It simplifies safe initialization of global variables holding compiled UUIDs. |  | ||||||
| func MustParse(s string) UUID { |  | ||||||
| 	uuid, err := Parse(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(`uuid: Parse(` + s + `): ` + err.Error()) |  | ||||||
| 	} |  | ||||||
| 	return uuid |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FromBytes creates a new UUID from a byte slice. Returns an error if the slice |  | ||||||
| // does not have a length of 16. The bytes are copied from the slice. |  | ||||||
| func FromBytes(b []byte) (uuid UUID, err error) { |  | ||||||
| 	err = uuid.UnmarshalBinary(b) |  | ||||||
| 	return uuid, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Must returns uuid if err is nil and panics otherwise. |  | ||||||
| func Must(uuid UUID, err error) UUID { |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	return uuid |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |  | ||||||
| // , or "" if uuid is invalid. |  | ||||||
| func (uuid UUID) String() string { |  | ||||||
| 	var buf [36]byte |  | ||||||
| 	encodeHex(buf[:], uuid) |  | ||||||
| 	return string(buf[:]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URN returns the RFC 2141 URN form of uuid, |  | ||||||
| // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,  or "" if uuid is invalid. |  | ||||||
| func (uuid UUID) URN() string { |  | ||||||
| 	var buf [36 + 9]byte |  | ||||||
| 	copy(buf[:], "urn:uuid:") |  | ||||||
| 	encodeHex(buf[9:], uuid) |  | ||||||
| 	return string(buf[:]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func encodeHex(dst []byte, uuid UUID) { |  | ||||||
| 	hex.Encode(dst, uuid[:4]) |  | ||||||
| 	dst[8] = '-' |  | ||||||
| 	hex.Encode(dst[9:13], uuid[4:6]) |  | ||||||
| 	dst[13] = '-' |  | ||||||
| 	hex.Encode(dst[14:18], uuid[6:8]) |  | ||||||
| 	dst[18] = '-' |  | ||||||
| 	hex.Encode(dst[19:23], uuid[8:10]) |  | ||||||
| 	dst[23] = '-' |  | ||||||
| 	hex.Encode(dst[24:], uuid[10:]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Variant returns the variant encoded in uuid. |  | ||||||
| func (uuid UUID) Variant() Variant { |  | ||||||
| 	switch { |  | ||||||
| 	case (uuid[8] & 0xc0) == 0x80: |  | ||||||
| 		return RFC4122 |  | ||||||
| 	case (uuid[8] & 0xe0) == 0xc0: |  | ||||||
| 		return Microsoft |  | ||||||
| 	case (uuid[8] & 0xe0) == 0xe0: |  | ||||||
| 		return Future |  | ||||||
| 	default: |  | ||||||
| 		return Reserved |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Version returns the version of uuid. |  | ||||||
| func (uuid UUID) Version() Version { |  | ||||||
| 	return Version(uuid[6] >> 4) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (v Version) String() string { |  | ||||||
| 	if v > 15 { |  | ||||||
| 		return fmt.Sprintf("BAD_VERSION_%d", v) |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("VERSION_%d", v) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (v Variant) String() string { |  | ||||||
| 	switch v { |  | ||||||
| 	case RFC4122: |  | ||||||
| 		return "RFC4122" |  | ||||||
| 	case Reserved: |  | ||||||
| 		return "Reserved" |  | ||||||
| 	case Microsoft: |  | ||||||
| 		return "Microsoft" |  | ||||||
| 	case Future: |  | ||||||
| 		return "Future" |  | ||||||
| 	case Invalid: |  | ||||||
| 		return "Invalid" |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("BadVariant%d", int(v)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetRand sets the random number generator to r, which implements io.Reader. |  | ||||||
| // If r.Read returns an error when the package requests random data then |  | ||||||
| // a panic will be issued. |  | ||||||
| // |  | ||||||
| // Calling SetRand with nil sets the random number generator to the default |  | ||||||
| // generator. |  | ||||||
| func SetRand(r io.Reader) { |  | ||||||
| 	if r == nil { |  | ||||||
| 		rander = rand.Reader |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	rander = r |  | ||||||
| } |  | ||||||
							
								
								
									
										44
									
								
								vendor/github.com/google/uuid/version1.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								vendor/github.com/google/uuid/version1.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,44 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/binary" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewUUID returns a Version 1 UUID based on the current NodeID and clock |  | ||||||
| // sequence, and the current time.  If the NodeID has not been set by SetNodeID |  | ||||||
| // or SetNodeInterface then it will be set automatically.  If the NodeID cannot |  | ||||||
| // be set NewUUID returns nil.  If clock sequence has not been set by |  | ||||||
| // SetClockSequence then it will be set automatically.  If GetTime fails to |  | ||||||
| // return the current NewUUID returns nil and an error. |  | ||||||
| // |  | ||||||
| // In most cases, New should be used. |  | ||||||
| func NewUUID() (UUID, error) { |  | ||||||
| 	nodeMu.Lock() |  | ||||||
| 	if nodeID == zeroID { |  | ||||||
| 		setNodeInterface("") |  | ||||||
| 	} |  | ||||||
| 	nodeMu.Unlock() |  | ||||||
| 
 |  | ||||||
| 	var uuid UUID |  | ||||||
| 	now, seq, err := GetTime() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return uuid, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	timeLow := uint32(now & 0xffffffff) |  | ||||||
| 	timeMid := uint16((now >> 32) & 0xffff) |  | ||||||
| 	timeHi := uint16((now >> 48) & 0x0fff) |  | ||||||
| 	timeHi |= 0x1000 // Version 1 |  | ||||||
| 
 |  | ||||||
| 	binary.BigEndian.PutUint32(uuid[0:], timeLow) |  | ||||||
| 	binary.BigEndian.PutUint16(uuid[4:], timeMid) |  | ||||||
| 	binary.BigEndian.PutUint16(uuid[6:], timeHi) |  | ||||||
| 	binary.BigEndian.PutUint16(uuid[8:], seq) |  | ||||||
| 	copy(uuid[10:], nodeID[:]) |  | ||||||
| 
 |  | ||||||
| 	return uuid, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										38
									
								
								vendor/github.com/google/uuid/version4.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/google/uuid/version4.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,38 +0,0 @@ | |||||||
| // Copyright 2016 Google Inc.  All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import "io" |  | ||||||
| 
 |  | ||||||
| // New creates a new random UUID or panics.  New is equivalent to |  | ||||||
| // the expression |  | ||||||
| // |  | ||||||
| //    uuid.Must(uuid.NewRandom()) |  | ||||||
| func New() UUID { |  | ||||||
| 	return Must(NewRandom()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewRandom returns a Random (Version 4) UUID. |  | ||||||
| // |  | ||||||
| // The strength of the UUIDs is based on the strength of the crypto/rand |  | ||||||
| // package. |  | ||||||
| // |  | ||||||
| // A note about uniqueness derived from the UUID Wikipedia entry: |  | ||||||
| // |  | ||||||
| //  Randomly generated UUIDs have 122 random bits.  One's annual risk of being |  | ||||||
| //  hit by a meteorite is estimated to be one chance in 17 billion, that |  | ||||||
| //  means the probability is about 0.00000000006 (6 × 10−11), |  | ||||||
| //  equivalent to the odds of creating a few tens of trillions of UUIDs in a |  | ||||||
| //  year and having one duplicate. |  | ||||||
| func NewRandom() (UUID, error) { |  | ||||||
| 	var uuid UUID |  | ||||||
| 	_, err := io.ReadFull(rander, uuid[:]) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil, err |  | ||||||
| 	} |  | ||||||
| 	uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 |  | ||||||
| 	uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 |  | ||||||
| 	return uuid, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								vendor/github.com/joho/godotenv/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/joho/godotenv/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| .DS_Store |  | ||||||
							
								
								
									
										8
									
								
								vendor/github.com/joho/godotenv/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/joho/godotenv/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,8 +0,0 @@ | |||||||
| language: go |  | ||||||
| 
 |  | ||||||
| go: |  | ||||||
|   - 1.x |  | ||||||
| 
 |  | ||||||
| os: |  | ||||||
|   - linux |  | ||||||
|   - osx |  | ||||||
							
								
								
									
										23
									
								
								vendor/github.com/joho/godotenv/LICENCE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/joho/godotenv/LICENCE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,23 +0,0 @@ | |||||||
| Copyright (c) 2013 John Barton |  | ||||||
| 
 |  | ||||||
| MIT License |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| a copy of this software and associated documentation files (the |  | ||||||
| "Software"), to deal in the Software without restriction, including |  | ||||||
| without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be |  | ||||||
| included in all copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
							
								
								
									
										163
									
								
								vendor/github.com/joho/godotenv/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										163
									
								
								vendor/github.com/joho/godotenv/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,163 +0,0 @@ | |||||||
| # GoDotEnv [](https://travis-ci.org/joho/godotenv) [](https://ci.appveyor.com/project/joho/godotenv) [](https://goreportcard.com/report/github.com/joho/godotenv) |  | ||||||
| 
 |  | ||||||
| A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) |  | ||||||
| 
 |  | ||||||
| From the original Library: |  | ||||||
| 
 |  | ||||||
| > Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. |  | ||||||
| > |  | ||||||
| > But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. |  | ||||||
| 
 |  | ||||||
| It can be used as a library (for loading in env for your own daemons etc) or as a bin command. |  | ||||||
| 
 |  | ||||||
| There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows. |  | ||||||
| 
 |  | ||||||
| ## Installation |  | ||||||
| 
 |  | ||||||
| As a library |  | ||||||
| 
 |  | ||||||
| ```shell |  | ||||||
| go get github.com/joho/godotenv |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| or if you want to use it as a bin command |  | ||||||
| ```shell |  | ||||||
| go get github.com/joho/godotenv/cmd/godotenv |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| Add your application configuration to your `.env` file in the root of your project: |  | ||||||
| 
 |  | ||||||
| ```shell |  | ||||||
| S3_BUCKET=YOURS3BUCKET |  | ||||||
| SECRET_KEY=YOURSECRETKEYGOESHERE |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Then in your Go app you can do something like |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "github.com/joho/godotenv" |  | ||||||
|     "log" |  | ||||||
|     "os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
|   err := godotenv.Load() |  | ||||||
|   if err != nil { |  | ||||||
|     log.Fatal("Error loading .env file") |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   s3Bucket := os.Getenv("S3_BUCKET") |  | ||||||
|   secretKey := os.Getenv("SECRET_KEY") |  | ||||||
| 
 |  | ||||||
|   // now do something with s3 or whatever |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| import _ "github.com/joho/godotenv/autoload" |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| _ = godotenv.Load("somerandomfile") |  | ||||||
| _ = godotenv.Load("filenumberone.env", "filenumbertwo.env") |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) |  | ||||||
| 
 |  | ||||||
| ```shell |  | ||||||
| # I am a comment and that is OK |  | ||||||
| SOME_VAR=someval |  | ||||||
| FOO=BAR # comments at line end are OK too |  | ||||||
| export BAR=BAZ |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Or finally you can do YAML(ish) style |  | ||||||
| 
 |  | ||||||
| ```yaml |  | ||||||
| FOO: bar |  | ||||||
| BAR: baz |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| as a final aside, if you don't want godotenv munging your env you can just get a map back instead |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| var myEnv map[string]string |  | ||||||
| myEnv, err := godotenv.Read() |  | ||||||
| 
 |  | ||||||
| s3Bucket := myEnv["S3_BUCKET"] |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ... or from an `io.Reader` instead of a local file |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| reader := getRemoteFile() |  | ||||||
| myEnv, err := godotenv.Parse(reader) |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ... or from a `string` if you so desire |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| content := getRemoteFileContent() |  | ||||||
| myEnv, err := godotenv.Unmarshal(content) |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Command Mode |  | ||||||
| 
 |  | ||||||
| Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| godotenv -f /some/path/to/.env some_command with some args |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` |  | ||||||
| 
 |  | ||||||
| ### Writing Env Files |  | ||||||
| 
 |  | ||||||
| Godotenv can also write a map representing the environment to a correctly-formatted and escaped file |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| env, err := godotenv.Unmarshal("KEY=value") |  | ||||||
| err := godotenv.Write(env, "./.env") |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ... or to a string |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| env, err := godotenv.Unmarshal("KEY=value") |  | ||||||
| content, err := godotenv.Marshal(env) |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Contributing |  | ||||||
| 
 |  | ||||||
| Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases. |  | ||||||
| 
 |  | ||||||
| *code changes without tests will not be accepted* |  | ||||||
| 
 |  | ||||||
| 1. Fork it |  | ||||||
| 2. Create your feature branch (`git checkout -b my-new-feature`) |  | ||||||
| 3. Commit your changes (`git commit -am 'Added some feature'`) |  | ||||||
| 4. Push to the branch (`git push origin my-new-feature`) |  | ||||||
| 5. Create new Pull Request |  | ||||||
| 
 |  | ||||||
| ## Releases |  | ||||||
| 
 |  | ||||||
| Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`. |  | ||||||
| 
 |  | ||||||
| Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1` |  | ||||||
| 
 |  | ||||||
| ## CI |  | ||||||
| 
 |  | ||||||
| Linux: [](https://travis-ci.org/joho/godotenv) Windows: [](https://ci.appveyor.com/project/joho/godotenv) |  | ||||||
| 
 |  | ||||||
| ## Who? |  | ||||||
| 
 |  | ||||||
| The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library. |  | ||||||
							
								
								
									
										15
									
								
								vendor/github.com/joho/godotenv/autoload/autoload.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/joho/godotenv/autoload/autoload.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,15 +0,0 @@ | |||||||
| package autoload |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 	You can just read the .env file on import just by doing |  | ||||||
| 
 |  | ||||||
| 		import _ "github.com/joho/godotenv/autoload" |  | ||||||
| 
 |  | ||||||
| 	And bob's your mother's brother |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| import "github.com/joho/godotenv" |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	godotenv.Load() |  | ||||||
| } |  | ||||||
							
								
								
									
										346
									
								
								vendor/github.com/joho/godotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										346
									
								
								vendor/github.com/joho/godotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,346 +0,0 @@ | |||||||
| // Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) |  | ||||||
| // |  | ||||||
| // Examples/readme can be found on the github page at https://github.com/joho/godotenv |  | ||||||
| // |  | ||||||
| // The TL;DR is that you make a .env file that looks something like |  | ||||||
| // |  | ||||||
| // 		SOME_ENV_VAR=somevalue |  | ||||||
| // |  | ||||||
| // and then in your go code you can call |  | ||||||
| // |  | ||||||
| // 		godotenv.Load() |  | ||||||
| // |  | ||||||
| // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR") |  | ||||||
| package godotenv |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"regexp" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const doubleQuoteSpecialChars = "\\\n\r\"!$`" |  | ||||||
| 
 |  | ||||||
| // Load will read your env file(s) and load them into ENV for this process. |  | ||||||
| // |  | ||||||
| // Call this function as close as possible to the start of your program (ideally in main) |  | ||||||
| // |  | ||||||
| // If you call Load without any args it will default to loading .env in the current path |  | ||||||
| // |  | ||||||
| // You can otherwise tell it which files to load (there can be more than one) like |  | ||||||
| // |  | ||||||
| //		godotenv.Load("fileone", "filetwo") |  | ||||||
| // |  | ||||||
| // It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults |  | ||||||
| func Load(filenames ...string) (err error) { |  | ||||||
| 	filenames = filenamesOrDefault(filenames) |  | ||||||
| 
 |  | ||||||
| 	for _, filename := range filenames { |  | ||||||
| 		err = loadFile(filename, false) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return // return early on a spazout |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Overload will read your env file(s) and load them into ENV for this process. |  | ||||||
| // |  | ||||||
| // Call this function as close as possible to the start of your program (ideally in main) |  | ||||||
| // |  | ||||||
| // If you call Overload without any args it will default to loading .env in the current path |  | ||||||
| // |  | ||||||
| // You can otherwise tell it which files to load (there can be more than one) like |  | ||||||
| // |  | ||||||
| //		godotenv.Overload("fileone", "filetwo") |  | ||||||
| // |  | ||||||
| // It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars. |  | ||||||
| func Overload(filenames ...string) (err error) { |  | ||||||
| 	filenames = filenamesOrDefault(filenames) |  | ||||||
| 
 |  | ||||||
| 	for _, filename := range filenames { |  | ||||||
| 		err = loadFile(filename, true) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return // return early on a spazout |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Read all env (with same file loading semantics as Load) but return values as |  | ||||||
| // a map rather than automatically writing values into env |  | ||||||
| func Read(filenames ...string) (envMap map[string]string, err error) { |  | ||||||
| 	filenames = filenamesOrDefault(filenames) |  | ||||||
| 	envMap = make(map[string]string) |  | ||||||
| 
 |  | ||||||
| 	for _, filename := range filenames { |  | ||||||
| 		individualEnvMap, individualErr := readFile(filename) |  | ||||||
| 
 |  | ||||||
| 		if individualErr != nil { |  | ||||||
| 			err = individualErr |  | ||||||
| 			return // return early on a spazout |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for key, value := range individualEnvMap { |  | ||||||
| 			envMap[key] = value |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Parse reads an env file from io.Reader, returning a map of keys and values. |  | ||||||
| func Parse(r io.Reader) (envMap map[string]string, err error) { |  | ||||||
| 	envMap = make(map[string]string) |  | ||||||
| 
 |  | ||||||
| 	var lines []string |  | ||||||
| 	scanner := bufio.NewScanner(r) |  | ||||||
| 	for scanner.Scan() { |  | ||||||
| 		lines = append(lines, scanner.Text()) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err = scanner.Err(); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, fullLine := range lines { |  | ||||||
| 		if !isIgnoredLine(fullLine) { |  | ||||||
| 			var key, value string |  | ||||||
| 			key, value, err = parseLine(fullLine, envMap) |  | ||||||
| 
 |  | ||||||
| 			if err != nil { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			envMap[key] = value |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //Unmarshal reads an env file from a string, returning a map of keys and values. |  | ||||||
| func Unmarshal(str string) (envMap map[string]string, err error) { |  | ||||||
| 	return Parse(strings.NewReader(str)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Exec loads env vars from the specified filenames (empty map falls back to default) |  | ||||||
| // then executes the cmd specified. |  | ||||||
| // |  | ||||||
| // Simply hooks up os.Stdin/err/out to the command and calls Run() |  | ||||||
| // |  | ||||||
| // If you want more fine grained control over your command it's recommended |  | ||||||
| // that you use `Load()` or `Read()` and the `os/exec` package yourself. |  | ||||||
| func Exec(filenames []string, cmd string, cmdArgs []string) error { |  | ||||||
| 	Load(filenames...) |  | ||||||
| 
 |  | ||||||
| 	command := exec.Command(cmd, cmdArgs...) |  | ||||||
| 	command.Stdin = os.Stdin |  | ||||||
| 	command.Stdout = os.Stdout |  | ||||||
| 	command.Stderr = os.Stderr |  | ||||||
| 	return command.Run() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Write serializes the given environment and writes it to a file |  | ||||||
| func Write(envMap map[string]string, filename string) error { |  | ||||||
| 	content, error := Marshal(envMap) |  | ||||||
| 	if error != nil { |  | ||||||
| 		return error |  | ||||||
| 	} |  | ||||||
| 	file, error := os.Create(filename) |  | ||||||
| 	if error != nil { |  | ||||||
| 		return error |  | ||||||
| 	} |  | ||||||
| 	_, err := file.WriteString(content) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Marshal outputs the given environment as a dotenv-formatted environment file. |  | ||||||
| // Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped. |  | ||||||
| func Marshal(envMap map[string]string) (string, error) { |  | ||||||
| 	lines := make([]string, 0, len(envMap)) |  | ||||||
| 	for k, v := range envMap { |  | ||||||
| 		lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) |  | ||||||
| 	} |  | ||||||
| 	sort.Strings(lines) |  | ||||||
| 	return strings.Join(lines, "\n"), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func filenamesOrDefault(filenames []string) []string { |  | ||||||
| 	if len(filenames) == 0 { |  | ||||||
| 		return []string{".env"} |  | ||||||
| 	} |  | ||||||
| 	return filenames |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func loadFile(filename string, overload bool) error { |  | ||||||
| 	envMap, err := readFile(filename) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	currentEnv := map[string]bool{} |  | ||||||
| 	rawEnv := os.Environ() |  | ||||||
| 	for _, rawEnvLine := range rawEnv { |  | ||||||
| 		key := strings.Split(rawEnvLine, "=")[0] |  | ||||||
| 		currentEnv[key] = true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for key, value := range envMap { |  | ||||||
| 		if !currentEnv[key] || overload { |  | ||||||
| 			os.Setenv(key, value) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func readFile(filename string) (envMap map[string]string, err error) { |  | ||||||
| 	file, err := os.Open(filename) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer file.Close() |  | ||||||
| 
 |  | ||||||
| 	return Parse(file) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseLine(line string, envMap map[string]string) (key string, value string, err error) { |  | ||||||
| 	if len(line) == 0 { |  | ||||||
| 		err = errors.New("zero length string") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// ditch the comments (but keep quoted hashes) |  | ||||||
| 	if strings.Contains(line, "#") { |  | ||||||
| 		segmentsBetweenHashes := strings.Split(line, "#") |  | ||||||
| 		quotesAreOpen := false |  | ||||||
| 		var segmentsToKeep []string |  | ||||||
| 		for _, segment := range segmentsBetweenHashes { |  | ||||||
| 			if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { |  | ||||||
| 				if quotesAreOpen { |  | ||||||
| 					quotesAreOpen = false |  | ||||||
| 					segmentsToKeep = append(segmentsToKeep, segment) |  | ||||||
| 				} else { |  | ||||||
| 					quotesAreOpen = true |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if len(segmentsToKeep) == 0 || quotesAreOpen { |  | ||||||
| 				segmentsToKeep = append(segmentsToKeep, segment) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		line = strings.Join(segmentsToKeep, "#") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	firstEquals := strings.Index(line, "=") |  | ||||||
| 	firstColon := strings.Index(line, ":") |  | ||||||
| 	splitString := strings.SplitN(line, "=", 2) |  | ||||||
| 	if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) { |  | ||||||
| 		//this is a yaml-style line |  | ||||||
| 		splitString = strings.SplitN(line, ":", 2) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(splitString) != 2 { |  | ||||||
| 		err = errors.New("Can't separate key from value") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Parse the key |  | ||||||
| 	key = splitString[0] |  | ||||||
| 	if strings.HasPrefix(key, "export") { |  | ||||||
| 		key = strings.TrimPrefix(key, "export") |  | ||||||
| 	} |  | ||||||
| 	key = strings.Trim(key, " ") |  | ||||||
| 
 |  | ||||||
| 	// Parse the value |  | ||||||
| 	value = parseValue(splitString[1], envMap) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseValue(value string, envMap map[string]string) string { |  | ||||||
| 
 |  | ||||||
| 	// trim |  | ||||||
| 	value = strings.Trim(value, " ") |  | ||||||
| 
 |  | ||||||
| 	// check if we've got quoted values or possible escapes |  | ||||||
| 	if len(value) > 1 { |  | ||||||
| 		rs := regexp.MustCompile(`\A'(.*)'\z`) |  | ||||||
| 		singleQuotes := rs.FindStringSubmatch(value) |  | ||||||
| 
 |  | ||||||
| 		rd := regexp.MustCompile(`\A"(.*)"\z`) |  | ||||||
| 		doubleQuotes := rd.FindStringSubmatch(value) |  | ||||||
| 
 |  | ||||||
| 		if singleQuotes != nil || doubleQuotes != nil { |  | ||||||
| 			// pull the quotes off the edges |  | ||||||
| 			value = value[1 : len(value)-1] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if doubleQuotes != nil { |  | ||||||
| 			// expand newlines |  | ||||||
| 			escapeRegex := regexp.MustCompile(`\\.`) |  | ||||||
| 			value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string { |  | ||||||
| 				c := strings.TrimPrefix(match, `\`) |  | ||||||
| 				switch c { |  | ||||||
| 				case "n": |  | ||||||
| 					return "\n" |  | ||||||
| 				case "r": |  | ||||||
| 					return "\r" |  | ||||||
| 				default: |  | ||||||
| 					return match |  | ||||||
| 				} |  | ||||||
| 			}) |  | ||||||
| 			// unescape characters |  | ||||||
| 			e := regexp.MustCompile(`\\([^$])`) |  | ||||||
| 			value = e.ReplaceAllString(value, "$1") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if singleQuotes == nil { |  | ||||||
| 			value = expandVariables(value, envMap) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return value |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func expandVariables(v string, m map[string]string) string { |  | ||||||
| 	r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) |  | ||||||
| 
 |  | ||||||
| 	return r.ReplaceAllStringFunc(v, func(s string) string { |  | ||||||
| 		submatch := r.FindStringSubmatch(s) |  | ||||||
| 
 |  | ||||||
| 		if submatch == nil { |  | ||||||
| 			return s |  | ||||||
| 		} |  | ||||||
| 		if submatch[1] == "\\" || submatch[2] == "(" { |  | ||||||
| 			return submatch[0][1:] |  | ||||||
| 		} else if submatch[4] != "" { |  | ||||||
| 			return m[submatch[4]] |  | ||||||
| 		} |  | ||||||
| 		return s |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isIgnoredLine(line string) bool { |  | ||||||
| 	trimmedLine := strings.Trim(line, " \n\t") |  | ||||||
| 	return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func doubleQuoteEscape(line string) string { |  | ||||||
| 	for _, c := range doubleQuoteSpecialChars { |  | ||||||
| 		toReplace := "\\" + string(c) |  | ||||||
| 		if c == '\n' { |  | ||||||
| 			toReplace = `\n` |  | ||||||
| 		} |  | ||||||
| 		if c == '\r' { |  | ||||||
| 			toReplace = `\r` |  | ||||||
| 		} |  | ||||||
| 		line = strings.Replace(line, string(c), toReplace, -1) |  | ||||||
| 	} |  | ||||||
| 	return line |  | ||||||
| } |  | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/mailgun/mailgun-go/v3/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mailgun/mailgun-go/v3/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| .DS_Store |  | ||||||
| .idea/ |  | ||||||
| cmd/mailgun/mailgun |  | ||||||
							
								
								
									
										7
									
								
								vendor/github.com/mailgun/mailgun-go/v3/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/mailgun/mailgun-go/v3/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,7 +0,0 @@ | |||||||
| language: go |  | ||||||
| 
 |  | ||||||
| env: |  | ||||||
|   - GO111MODULE=on |  | ||||||
| 
 |  | ||||||
| go: |  | ||||||
|   - 1.11.x |  | ||||||
							
								
								
									
										175
									
								
								vendor/github.com/mailgun/mailgun-go/v3/CHANGELOG
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										175
									
								
								vendor/github.com/mailgun/mailgun-go/v3/CHANGELOG
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,175 +0,0 @@ | |||||||
| # Changelog |  | ||||||
| All notable changes to this project will be documented in this file. |  | ||||||
| 
 |  | ||||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |  | ||||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |  | ||||||
| 
 |  | ||||||
| ## [3.6.3] - 2019-12-03 |  | ||||||
| ### Changes |  | ||||||
| * Calls to get stats now use epoch as the time format |  | ||||||
| 
 |  | ||||||
| ## [3.6.2] - 2019-11-18 |  | ||||||
| ### Added |  | ||||||
| * Added `AddTemplateVariable()` to make adding variables to templates  |  | ||||||
|   less confusing and error prone. |  | ||||||
| 
 |  | ||||||
| ## [3.6.1] - 2019-10-24 |  | ||||||
| ### Added |  | ||||||
| * Added `VerifyWebhookSignature()` to mailgun interface |  | ||||||
| 
 |  | ||||||
| ## [3.6.1-rc.3] - 2019-07-16 |  | ||||||
| ### Added |  | ||||||
| * APIBaseEU and APIBaseUS to help customers change regions |  | ||||||
| * Documented how to change regions in the README |  | ||||||
| 
 |  | ||||||
| ## [3.6.1-rc.2] - 2019-07-01 |  | ||||||
| ### Changes |  | ||||||
| * Fix the JSON response for `GetMember()` |  | ||||||
| * Typo in format string in max number of tags error |  | ||||||
| 
 |  | ||||||
| ## [3.6.0] - 2019-06-26 |  | ||||||
| ### Added |  | ||||||
| * Added UpdateClickTracking() to modify click tracking for a domain |  | ||||||
| * Added UpdateUnsubscribeTracking() to modify unsubscribe tracking for a domain |  | ||||||
| * Added UpdateOpenTracking() to modify open tracking for a domain |  | ||||||
| 
 |  | ||||||
| ## [3.5.0] - 2019-05-21 |  | ||||||
| ### Added |  | ||||||
| * Added notice in README about go dep bug. |  | ||||||
| * Added endpoints for webhooks in mock server |  | ||||||
| ### Changes |  | ||||||
| * Change names of some parameters on public methods to make their use clearer. |  | ||||||
| * Changed signature of `GetWebhook()` now returns []string. |  | ||||||
| * Changed signature of `ListWebhooks()` now returns map[string][]string. |  | ||||||
| * Both `GetWebhooks()` and `ListWebhooks()` now handle new and legacy webhooks properly. |  | ||||||
| 
 |  | ||||||
| ## [3.4.0] - 2019-04-23 |  | ||||||
| ### Added |  | ||||||
| * Added `Message.SetTemplate()` to allow sending with the body of a template. |  | ||||||
| ### Changes |  | ||||||
| * Changed signature of `CreateDomain()` moved password into `CreateDomainOptions` |  | ||||||
| 
 |  | ||||||
| ## [3.4.0] - 2019-04-23 |  | ||||||
| ### Added |  | ||||||
| * Added `Message.SetTemplate()` to allow sending with the body of a template. |  | ||||||
| ### Changes |  | ||||||
| * Changed signature of `CreateDomain()` moved password into `CreateDomainOptions` |  | ||||||
| 
 |  | ||||||
| ## [3.3.2] - 2019-03-28 |  | ||||||
| ### Changes |  | ||||||
| * Uncommented DeliveryStatus.Code and change it to an integer (See #175) |  | ||||||
| * Added UserVariables to all Message events (See #176) |  | ||||||
| 
 |  | ||||||
| ## [3.3.1] - 2019-03-13 |  | ||||||
| ### Changes |  | ||||||
| * Updated Template calls to reflect the most recent Template API changes. |  | ||||||
| * GetStoredMessage() now accepts a URL instead of an id |  | ||||||
| * Deprecated GetStoredMessageForURL() |  | ||||||
| * Deprecated GetStoredMessageRawForURL() |  | ||||||
| * Fixed GetUnsubscribed() |  | ||||||
| 
 |  | ||||||
| ### Added |  | ||||||
| * Added `GetStoredAttachment()` |  | ||||||
| 
 |  | ||||||
| ### Removed |  | ||||||
| * Method `DeleteStoredMessage()` mailgun API no long allows this call |  | ||||||
| 
 |  | ||||||
| ## [3.3.0] - 2019-01-28 |  | ||||||
| ### Changes |  | ||||||
| * Changed signature of CreateDomain() Now returns JSON response |  | ||||||
| * Changed signature of GetDomain() Now returns a single DomainResponse |  | ||||||
| * Clarified installation notes for non golang module users |  | ||||||
| * Changed 'Public Key' to 'Public Validation Key' in readme |  | ||||||
| * Fixed issue with Next() for limit/skip based iterators |  | ||||||
| 
 |  | ||||||
| ### Added |  | ||||||
| * Added VerifyDomain() |  | ||||||
| 
 |  | ||||||
| ## [3.2.0] - 2019-01-21 |  | ||||||
| ### Changes |  | ||||||
| * Deprecated mg.VerifyWebhookRequest() |  | ||||||
| 
 |  | ||||||
| ### Added |  | ||||||
| * Added mailgun.ParseEvent() |  | ||||||
| * Added mailgun.ParseEvents() |  | ||||||
| * Added mg.VerifyWebhookSignature() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## [3.1.0] - 2019-01-16 |  | ||||||
| ### Changes |  | ||||||
| * Removed context.Context from ListDomains() signature |  | ||||||
| * ListEventOptions.Begin and End are no longer pointers to time.Time |  | ||||||
| 
 |  | ||||||
| ### Added |  | ||||||
| * Added mg.ReSend() to public Mailgun interface |  | ||||||
| * Added Message.SetSkipVerification() |  | ||||||
| * Added Message.SetRequireTLS() |  | ||||||
| 
 |  | ||||||
| ## [3.0.0] - 2019-01-15 |  | ||||||
| ### Added |  | ||||||
| * Added CHANGELOG |  | ||||||
| * Added `AddDomainIP()` |  | ||||||
| * Added `ListDomainIPS()` |  | ||||||
| * Added `DeleteDomainIP()` |  | ||||||
| * Added `ListIPS()` |  | ||||||
| * Added `GetIP()` |  | ||||||
| * Added `GetDomainTracking()` |  | ||||||
| * Added `GetDomainConnection()` |  | ||||||
| * Added `UpdateDomainConnection()` |  | ||||||
| * Added `CreateExport()` |  | ||||||
| * Added `ListExports()` |  | ||||||
| * Added `GetExports()` |  | ||||||
| * Added `GetExportLink()` |  | ||||||
| * Added `CreateTemplate()` |  | ||||||
| * Added `GetTemplate()` |  | ||||||
| * Added `UpdateTemplate()` |  | ||||||
| * Added `DeleteTemplate()` |  | ||||||
| * Added `ListTemplates()` |  | ||||||
| * Added `AddTemplateVersion()` |  | ||||||
| * Added `GetTemplateVersion()` |  | ||||||
| * Added `UpdateTemplateVersion()` |  | ||||||
| * Added `DeleteTemplateVersion()` |  | ||||||
| * Added `ListTemplateVersions()` |  | ||||||
| 
 |  | ||||||
| ### Changed |  | ||||||
| * Added a `mailgun.MockServer` which duplicates part of the mailgun API; suitable for testing |  | ||||||
| * `ListMailingLists()` now uses the `/pages` API and returns an iterator |  | ||||||
| * `ListMembers()` now uses the `/pages` API and returns an iterator |  | ||||||
| * Renamed public interface methods to be consistent. IE: `GetThing(), ListThing(), CreateThing()` |  | ||||||
| * Moved event objects into the `mailgun/events` package, so names like |  | ||||||
|   `MailingList` returned by API calls and `MailingList` as an event object |  | ||||||
|   don't conflict and confuse users. |  | ||||||
| * Now using context.Context for all network operations |  | ||||||
| * Test suite will run without MG_ env vars defined |  | ||||||
| * ListRoutes() now uses the iterator interface |  | ||||||
| * Added SkipNetworkTest() |  | ||||||
| * Renamed GetStatsTotals() to GetStats() |  | ||||||
| * Renamed GetUnsubscribes to ListUnsubscribes() |  | ||||||
| * Renamed Unsubscribe() to CreateUnsubscribe() |  | ||||||
| * Renamed RemoveUnsubscribe() to DeleteUnsubscribe() |  | ||||||
| * GetStats() now takes an `*opt` argument to pass optional parameters |  | ||||||
| * Modified GetUnsubscribe() to follow the API |  | ||||||
| * Now using golang modules |  | ||||||
| * ListCredentials() now returns an iterator |  | ||||||
| * ListUnsubscribes() now returns an paging iterator |  | ||||||
| * CreateDomain now accepts CreateDomainOption{} |  | ||||||
| * CreateDomain() now supports all optional parameters not just spam_action and wildcard. |  | ||||||
| * ListComplaints() now returns a page iterator |  | ||||||
| * Renamed `TagItem` to `Tag` |  | ||||||
| * ListBounces() now returns a page iterator |  | ||||||
| * API responses with CreatedAt fields are now unmarshalled into RFC2822 |  | ||||||
| * DomainList() now returns an iterator |  | ||||||
| * Updated godoc documentation |  | ||||||
| * Renamed ApiBase to APIBase |  | ||||||
| * Updated copyright to 2019 |  | ||||||
| * `ListEvents()` now returns a list of typed events |  | ||||||
| 
 |  | ||||||
| ### Removed |  | ||||||
| * Removed more deprecated types |  | ||||||
| * Removed gobuffalo/envy dependency |  | ||||||
| * Remove mention of the CLI in the README |  | ||||||
| * Removed mailgun cli from project |  | ||||||
| * Removed GetCode() from `Bounce` struct. Verified API returns 'string' and not 'int' |  | ||||||
| * Removed deprecated methods NewMessage and NewMIMEMessage |  | ||||||
| * Removed ginkgo and gomega tests |  | ||||||
| * Removed GetStats() As the /stats endpoint is depreciated |  | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/mailgun/mailgun-go/v3/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mailgun/mailgun-go/v3/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,27 +0,0 @@ | |||||||
| Copyright (c) 2013-2016, Michael Banzon |  | ||||||
| All rights reserved. |  | ||||||
| 
 |  | ||||||
| Redistribution and use in source and binary forms, with or without modification, |  | ||||||
| are permitted provided that the following conditions are met: |  | ||||||
| 
 |  | ||||||
| * Redistributions of source code must retain the above copyright notice, this |  | ||||||
|   list of conditions and the following disclaimer. |  | ||||||
| 
 |  | ||||||
| * Redistributions in binary form must reproduce the above copyright notice, this |  | ||||||
|   list of conditions and the following disclaimer in the documentation and/or |  | ||||||
|   other materials provided with the distribution. |  | ||||||
| 
 |  | ||||||
| * Neither the names of Mailgun, Michael Banzon, nor the names of its |  | ||||||
|   contributors may be used to endorse or promote products derived from |  | ||||||
|   this software without specific prior written permission. |  | ||||||
| 
 |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |  | ||||||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |  | ||||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |  | ||||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |  | ||||||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |  | ||||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |  | ||||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |  | ||||||
| ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user