package mockid import ( "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "io" "math/big" "net/url" "os" "strconv" "time" "git.coolaj86.com/coolaj86/go-mockid/xkeypairs" "git.rootprojects.org/root/keypairs" //jwt "github.com/dgrijalva/jwt-go" ) // TestMain will overwrite this var rndsrc io.Reader = rand.Reader type PublicJWK struct { Crv string `json:"crv"` KeyID string `json:"kid,omitempty"` Kty string `json:"kty,omitempty"` X string `json:"x"` Y string `json:"y"` } type KVDB interface { Load(key interface{}) (value interface{}, ok bool, err error) Store(key interface{}, value interface{}) (err error) Delete(key interface{}) (err error) Vacuum() (err error) } type InspectableToken struct { Public keypairs.PublicKey `json:"jwk"` Protected map[string]interface{} `json:"protected"` 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) /* go func() { for { nonce := <- nonCh nonces[nonce] = time.Now().Unix() } }() */ go func() { for { time.Sleep(15 * time.Second) hashcashes.vacuum() } }() } func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (string, string, string) { thumbprint := keypairs.ThumbprintPublicKey(keypairs.NewPublicKey(privkey.Public())) // TODO keypairs.Alg(key) alg := "ES256" switch privkey.(type) { case *rsa.PrivateKey: alg = "RS256" } protected := fmt.Sprintf(`{"typ":"JWT","alg":%q,"kid":"%s"}`, alg, thumbprint) protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected)) exp, err := xkeypairs.ParseDuration(query.Get("exp")) if nil != err { // cryptic error code // TODO propagate error exp = 422 } payload := fmt.Sprintf( `{"iss":"%s/","sub":"dummy","exp":%s}`, host, strconv.FormatInt(time.Now().Add(time.Duration(exp)*time.Second).Unix(), 10), ) payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload)) hash := sha256.Sum256([]byte(fmt.Sprintf(`%s.%s`, protected64, payload64))) sig := JOSESign(privkey, 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() 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 func JOSEVerify(pubkey keypairs.PublicKey, hash []byte, sig []byte) bool { switch pub := pubkey.Key().(type) { case *rsa.PublicKey: // 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:]) fmt.Println("debug: sig len:", len(sig)) fmt.Println("debug: r, s:", r, s) return ecdsa.Verify(pub, hash, r, s) default: panic("impossible condition: non-rsa/non-ecdsa key") return false } }