go-mockid/mockid/mockid.go

173 lines
4.2 KiB
Go
Raw Permalink Normal View History

2020-04-10 19:29:01 +00:00
package mockid
import (
2020-05-11 05:26:32 +00:00
"crypto"
2020-04-10 19:29:01 +00:00
"crypto/ecdsa"
"crypto/rand"
2020-05-11 04:40:46 +00:00
"crypto/rsa"
2020-04-10 19:29:01 +00:00
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
2020-04-10 19:29:01 +00:00
"math/big"
"net/url"
2020-05-13 09:41:03 +00:00
"os"
2020-04-10 19:29:01 +00:00
"strconv"
"time"
"git.rootprojects.org/root/keypairs"
2020-05-11 04:40:46 +00:00
//jwt "github.com/dgrijalva/jwt-go"
2020-04-10 19:29:01 +00:00
)
// TestMain will overwrite this
var rndsrc io.Reader = rand.Reader
2020-04-10 19:29:01 +00:00
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)
}
2020-05-11 04:40:46 +00:00
type InspectableToken struct {
2020-05-11 04:58:12 +00:00
Public keypairs.PublicKey `json:"jwk"`
2020-05-11 04:40:46 +00:00
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(
2020-05-11 04:58:12 +00:00
`{"jwk":%s,"protected":%s,"payload":%s,"signature":%q,"verified":%t,"errors":%s}`,
2020-05-11 04:40:46 +00:00
pub, header, payload, t.Signature, t.Verified, errs,
)), nil
}
2020-05-13 09:41:03 +00:00
var defaultFrom string
var defaultReplyTo string
var salt []byte
2020-04-10 19:29:01 +00:00
func Init() {
2020-05-13 09:41:03 +00:00
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()
}
}()
2020-04-10 19:29:01 +00:00
}
2020-05-11 04:40:46 +00:00
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)
2020-04-10 19:29:01 +00:00
protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected))
2020-09-16 22:37:19 +00:00
exp, err := time.ParseDuration(query.Get("exp"))
2020-04-10 19:29:01 +00:00
if nil != err {
// cryptic error code
// TODO propagate error
exp = 422
}
payload := fmt.Sprintf(
`{"iss":"%s/","sub":"dummy","exp":%s}`,
2020-09-16 22:37:19 +00:00
host, strconv.FormatInt(time.Now().Add(exp*time.Second).Unix(), 10),
2020-04-10 19:29:01 +00:00
)
payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload))
hash := sha256.Sum256([]byte(fmt.Sprintf(`%s.%s`, protected64, payload64)))
2020-05-11 04:40:46 +00:00
sig := JOSESign(privkey, hash[:])
sig64 := base64.RawURLEncoding.EncodeToString(sig)
token := fmt.Sprintf("%s.%s.%s\n", protected64, payload64, sig64)
2020-04-10 19:29:01 +00:00
return protected, payload, token
}
2020-08-04 07:09:43 +00:00
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()
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
}
2020-05-11 04:40:46 +00:00
// TODO: move to keypairs
2020-04-10 19:29:01 +00:00
2020-05-11 05:26:32 +00:00
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"
2020-05-13 09:41:03 +00:00
// TODO: this hasn't been tested yet
2020-05-11 05:26:32 +00:00
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err {
2020-08-05 09:06:45 +00:00
return false
2020-05-11 05:26:32 +00:00
}
2020-08-05 09:06:45 +00:00
return true
2020-05-11 05:26:32 +00:00
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)
2020-08-05 09:06:45 +00:00
return ecdsa.Verify(pub, hash, r, s)
2020-05-11 05:26:32 +00:00
default:
panic("impossible condition: non-rsa/non-ecdsa key")
2020-08-05 09:06:45 +00:00
return false
2020-05-11 05:26:32 +00:00
}
}