can now self-sign JWS and JWT
このコミットが含まれているのは:
		
						コミット
						a1b4ad1202
					
				| @ -13,12 +13,16 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type Object = map[string]interface{} | ||||||
|  | 
 | ||||||
| // options are the things that we may need to know about a request to fulfill it properly | // options are the things that we may need to know about a request to fulfill it properly | ||||||
| type options struct { | type options struct { | ||||||
| 	Key     string `json:"key"` | 	Key     string `json:"key"` | ||||||
| 	KeyType string `json:"kty"` | 	KeyType string `json:"kty"` | ||||||
| 	Seed    int64  `json:"-"` | 	Seed    int64  `json:"-"` | ||||||
| 	SeedStr string `json:"seed"` | 	SeedStr string `json:"seed"` | ||||||
|  | 	Claims  Object `json:"claims"` | ||||||
|  | 	Header  Object `json:"header"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // this shananigans is only for testing and debug API stuff | // this shananigans is only for testing and debug API stuff | ||||||
| @ -55,6 +59,9 @@ func getOpts(r *http.Request) (*options, error) { | |||||||
| 		Key:  key, | 		Key:  key, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	opts.Claims, _ = tok["claims"].(Object) | ||||||
|  | 	opts.Header, _ = tok["header"].(Object) | ||||||
|  | 
 | ||||||
| 	var n int | 	var n int | ||||||
| 	if 0 != seed { | 	if 0 != seed { | ||||||
| 		n = opts.nextReader().(*mathrand.Rand).Intn(2) | 		n = opts.nextReader().(*mathrand.Rand).Intn(2) | ||||||
|  | |||||||
							
								
								
									
										57
									
								
								mockid/api/sign.go
									
									
									
									
									
										ノーマルファイル
									
								
							
							
						
						
									
										57
									
								
								mockid/api/sign.go
									
									
									
									
									
										ノーマルファイル
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"git.coolaj86.com/coolaj86/go-mockid/xkeypairs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // 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 := xkeypairs.SignClaims(privkey, header, opts.Claims) | ||||||
|  | 	if nil != err { | ||||||
|  | 		http.Error(w, err.Error(), http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var b []byte | ||||||
|  | 	if jwt { | ||||||
|  | 		s := xkeypairs.JWSToJWT(jws) | ||||||
|  | 		w.Write(append([]byte(s), '\n')) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	b, _ = json.Marshal(jws) | ||||||
|  | 	w.Write(append(b, '\n')) | ||||||
|  | } | ||||||
| @ -115,6 +115,29 @@ func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (strin | |||||||
| 	return protected, payload, token | 	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() | ||||||
|  | 		fmt.Println("debug:") | ||||||
|  | 		fmt.Println(r, s) | ||||||
|  | 		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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // TODO: move to keypairs | // TODO: move to keypairs | ||||||
| 
 | 
 | ||||||
| func JOSEVerify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { | func JOSEVerify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { | ||||||
| @ -143,29 +166,6 @@ func JOSEVerify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { | |||||||
| 	return verified | 	return verified | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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() |  | ||||||
| 		fmt.Println("debug:") |  | ||||||
| 		fmt.Println(r, s) |  | ||||||
| 		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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func issueNonce(w http.ResponseWriter, r *http.Request) { | func issueNonce(w http.ResponseWriter, r *http.Request) { | ||||||
| 	b := make([]byte, 16) | 	b := make([]byte, 16) | ||||||
| 	_, _ = rand.Read(b) | 	_, _ = rand.Read(b) | ||||||
|  | |||||||
| @ -61,6 +61,40 @@ func TestMain(m *testing.M) { | |||||||
| 	os.Exit(m.Run()) | 	os.Exit(m.Run()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | //func TestSelfSignWithoutExp(t *testing.T) | ||||||
|  | //func TestSelfSignWithJTIWithoutExp(t *testing.T) | ||||||
|  | 
 | ||||||
|  | func TestSelfSign(t *testing.T) { | ||||||
|  | 	client := srv.Client() | ||||||
|  | 	//urlstr, _ := url.Parse(srv.URL + "/jose.jws.json") | ||||||
|  | 	urlstr, _ := url.Parse(srv.URL + "/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) { | func TestGenerateJWK(t *testing.T) { | ||||||
| 	client := srv.Client() | 	client := srv.Client() | ||||||
| 	urlstr, _ := url.Parse(srv.URL + "/private.jwk.json") | 	urlstr, _ := url.Parse(srv.URL + "/private.jwk.json") | ||||||
| @ -74,6 +108,10 @@ func TestGenerateJWK(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	data, err := ioutil.ReadAll(res.Body) | 	data, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| @ -128,6 +166,11 @@ func TestGenWithSeed(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	dataA, err := ioutil.ReadAll(res.Body) | 	dataA, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		//t.Fatal(err) | 		//t.Fatal(err) | ||||||
| @ -150,6 +193,11 @@ func TestGenWithSeed(t *testing.T) { | |||||||
| 			t.Error(err) | 			t.Error(err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		if 200 != res.StatusCode { | ||||||
|  | 			t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		dataB, err := ioutil.ReadAll(res.Body) | 		dataB, err := ioutil.ReadAll(res.Body) | ||||||
| 		if nil != err { | 		if nil != err { | ||||||
| 			//t.Fatal(err) | 			//t.Fatal(err) | ||||||
| @ -180,6 +228,11 @@ func TestGenWithRand(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	dataA, err := ioutil.ReadAll(res.Body) | 	dataA, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		//t.Fatal(err) | 		//t.Fatal(err) | ||||||
| @ -200,6 +253,11 @@ func TestGenWithRand(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	dataB, err := ioutil.ReadAll(res.Body) | 	dataB, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		//t.Fatal(err) | 		//t.Fatal(err) | ||||||
| @ -226,6 +284,10 @@ func TestGeneratePEM(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	data, err := ioutil.ReadAll(res.Body) | 	data, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| @ -266,6 +328,10 @@ func TestPublicJWKWithKey(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	data, err := ioutil.ReadAll(res.Body) | 	data, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| @ -319,6 +385,10 @@ func TestPublicPEMWithSeed(t *testing.T) { | |||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if 200 != res.StatusCode { | ||||||
|  | 		t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	data, err := ioutil.ReadAll(res.Body) | 	data, err := ioutil.ReadAll(res.Body) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
|  | |||||||
| @ -181,6 +181,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { | |||||||
| 		fmt.Fprintf(w, token) | 		fmt.Fprintf(w, token) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	// TODO add /debug prefix | ||||||
| 	http.HandleFunc("/private.jwk.json", api.GeneratePrivateJWK) | 	http.HandleFunc("/private.jwk.json", api.GeneratePrivateJWK) | ||||||
| 	http.HandleFunc("/priv.der", api.GeneratePrivateDER) | 	http.HandleFunc("/priv.der", api.GeneratePrivateDER) | ||||||
| 	http.HandleFunc("/priv.pem", api.GeneratePrivatePEM) | 	http.HandleFunc("/priv.pem", api.GeneratePrivatePEM) | ||||||
| @ -189,6 +190,9 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { | |||||||
| 	http.HandleFunc("/pub.der", api.GeneratePublicDER) | 	http.HandleFunc("/pub.der", api.GeneratePublicDER) | ||||||
| 	http.HandleFunc("/pub.pem", api.GeneratePublicPEM) | 	http.HandleFunc("/pub.pem", api.GeneratePublicPEM) | ||||||
| 
 | 
 | ||||||
|  | 	http.HandleFunc("/jose.jws.json", api.SignJWS) | ||||||
|  | 	http.HandleFunc("/jose.jws.jwt", api.SignJWT) | ||||||
|  | 
 | ||||||
| 	http.HandleFunc("/inspect_token", func(w http.ResponseWriter, r *http.Request) { | 	http.HandleFunc("/inspect_token", func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		token := r.Header.Get("Authorization") | 		token := r.Header.Get("Authorization") | ||||||
| 		log.Printf("%s %s %s\n", r.Method, r.URL.Path, token) | 		log.Printf("%s %s %s\n", r.Method, r.URL.Path, token) | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ func ParseDuration(exp string) (int, error) { | |||||||
| 	if "" == exp { | 	if "" == exp { | ||||||
| 		exp = "15m" | 		exp = "15m" | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	mult := 1 | 	mult := 1 | ||||||
| 	switch exp[len(exp)-1] { | 	switch exp[len(exp)-1] { | ||||||
| 	case 'w': | 	case 'w': | ||||||
| @ -37,9 +38,11 @@ func ParseDuration(exp string) (int, error) { | |||||||
| 		exp += "s" | 		exp += "s" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// 15m => num=15, mult=1*60 | ||||||
| 	num, err := strconv.Atoi(exp[:len(exp)-1]) | 	num, err := strconv.Atoi(exp[:len(exp)-1]) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return num * mult, nil | 	return num * mult, nil | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										170
									
								
								xkeypairs/sign.go
									
									
									
									
									
										ノーマルファイル
									
								
							
							
						
						
									
										170
									
								
								xkeypairs/sign.go
									
									
									
									
									
										ノーマルファイル
									
								
							| @ -0,0 +1,170 @@ | |||||||
|  | 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" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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{} | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  | 
 | ||||||
|  | 	hash := sha256.Sum256([]byte(fmt.Sprintf( | ||||||
|  | 		`%s.%s`, | ||||||
|  | 		protected64, | ||||||
|  | 		payload64, | ||||||
|  | 	))) | ||||||
|  | 
 | ||||||
|  | 	sig := Sign(randsrc, privkey, hash[:]) | ||||||
|  | 	sig64 := base64.RawURLEncoding.EncodeToString(sig) | ||||||
|  | 
 | ||||||
|  | 	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 := ParseDuration(dur) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		exp = time.Now().Add(time.Duration(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() | ||||||
|  | 		fmt.Println("debug:") | ||||||
|  | 		fmt.Println(r, s) | ||||||
|  | 		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 | ||||||
|  | } | ||||||
		読み込み中…
	
	
			
			x
			
			
		
	
		新しいイシューから参照
	
	ユーザーをブロックする