add debug routes for PEM and DER private keys

This commit is contained in:
AJ ONeal 2020-08-01 07:32:17 +00:00
parent 153851b41d
commit 075ade3dec
8 changed files with 269 additions and 10 deletions

1
.ignore Normal file
View File

@ -0,0 +1 @@
vendor

1
go-test.sh Normal file
View File

@ -0,0 +1 @@
go test -mod=vendor -v ./...

View File

@ -5,10 +5,12 @@ import (
"fmt" "fmt"
"io/ioutil" "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" "git.rootprojects.org/root/keypairs"
@ -21,6 +23,8 @@ func main() {
var port int var port int
var host string var host string
rand.Seed(time.Now().UnixNano())
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")
prefixFlag := flag.String("jwkspath", "", "The path to the JWKs storage directory") prefixFlag := flag.String("jwkspath", "", "The path to the JWKs storage directory")

View File

@ -3,10 +3,13 @@ package mockid
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/pem"
"fmt" "fmt"
"log" "log"
"math/big" "math/big"
mathrand "math/rand"
"git.rootprojects.org/root/keypairs" "git.rootprojects.org/root/keypairs"
) )
@ -27,6 +30,75 @@ func MarshalJWKPrivateKey(privkey keypairs.PrivateKey) []byte {
} }
} }
// 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(1) {
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(1) {
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 // MarshalECPrivateKey will output the given private key as JWK
func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte { func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte {
crv := k.Curve.Params().Name crv := k.Curve.Params().Name

View File

@ -9,6 +9,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"math/big" "math/big"
"net/http" "net/http"
"net/url" "net/url"
@ -21,6 +22,9 @@ import (
//jwt "github.com/dgrijalva/jwt-go" //jwt "github.com/dgrijalva/jwt-go"
) )
// TestMain will overwrite this
var rndsrc io.Reader = rand.Reader
type PublicJWK struct { type PublicJWK struct {
Crv string `json:"crv"` Crv string `json:"crv"`
KeyID string `json:"kid,omitempty"` KeyID string `json:"kid,omitempty"`
@ -145,7 +149,7 @@ func JOSESign(privkey keypairs.PrivateKey, hash []byte) []byte {
case *rsa.PrivateKey: case *rsa.PrivateKey:
panic("TODO: implement rsa sign") panic("TODO: implement rsa sign")
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
r, s, _ := ecdsa.Sign(rand.Reader, k, hash[:]) r, s, _ := ecdsa.Sign(rndsrc, k, hash[:])
rb := r.Bytes() rb := r.Bytes()
fmt.Println("debug:") fmt.Println("debug:")
fmt.Println(r, s) fmt.Println(r, s)

View File

@ -3,22 +3,41 @@ package mockid
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rsa"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
mathrand "math/rand"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"os" "os"
"testing" "testing"
"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 var srv *httptest.Server
type TestReader struct{}
func (TestReader) Read(p []byte) (n int, err error) {
return mathrand.Read(p)
}
var testrnd = TestReader{}
func init() {
rndsrc = testrnd
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
mathrand.Seed(0) // Predictable results
os.Setenv("SALT", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") os.Setenv("SALT", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
jwksPrefix := "public-jwks" jwksPrefix := "public-jwks"
err := os.MkdirAll(jwksPrefix, 0755) err := os.MkdirAll(jwksPrefix, 0755)
@ -27,7 +46,7 @@ func TestMain(m *testing.M) {
os.Exit(1) os.Exit(1)
} }
privkey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) privkey, _ := ecdsa.GenerateKey(elliptic.P256(), rndsrc)
mux := Route(jwksPrefix, privkey) mux := Route(jwksPrefix, privkey)
srv = httptest.NewServer(mux) srv = httptest.NewServer(mux)
@ -38,7 +57,7 @@ func TestMain(m *testing.M) {
os.Exit(m.Run()) os.Exit(m.Run())
} }
func TestTest(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")
//fmt.Println("URL:", srv.URL, urlstr) //fmt.Println("URL:", srv.URL, urlstr)
@ -49,12 +68,14 @@ func TestTest(t *testing.T) {
if nil != err { if nil != err {
//t.Fatal(err) //t.Fatal(err)
t.Error(err) t.Error(err)
return
} }
data, err := ioutil.ReadAll(res.Body) data, err := ioutil.ReadAll(res.Body)
if nil != err { if nil != err {
//t.Fatal(err) //t.Fatal(err)
t.Error(err) t.Error(err)
return
} }
jwk := map[string]string{} jwk := map[string]string{}
@ -62,10 +83,70 @@ func TestTest(t *testing.T) {
if nil != err { if nil != err {
//t.Fatal(err) //t.Fatal(err)
t.Error(err) t.Error(err)
return
} }
if "" == jwk["d"] { if "" == jwk["d"] {
t.Fatal("Missing key 'd' from supposed private key") 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) //fmt.Printf("%#v\n", jwk)
} }
func TestGeneratePEM(t *testing.T) {
client := srv.Client()
urlstr, _ := url.Parse(srv.URL + "/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
}
data, err := ioutil.ReadAll(res.Body)
if nil != err {
//t.Fatal(err)
t.Error(err)
return
}
key, err := 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"))
}
//fmt.Printf("%#v\n", key)
}

View File

@ -1,6 +1,15 @@
package mockid package mockid
import "strconv" import (
"strconv"
"git.rootprojects.org/root/keypairs"
)
func ParsePEMPrivateKey(block []byte) (keypairs.PrivateKey, error) {
// TODO do not parse DER or JWK
return keypairs.ParsePrivateKey(block)
}
func parseExp(exp string) (int, error) { func parseExp(exp string) (int, error) {
if "" == exp { if "" == exp {

View File

@ -1,16 +1,20 @@
package mockid package mockid
import ( import (
"crypto/rand" "crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa" "crypto/rsa"
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
mathrand "math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -180,17 +184,100 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
fmt.Fprintf(w, token) fmt.Fprintf(w, token)
}) })
getKty := func(r *http.Request) (string, 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 "", errors.New("Bad Request: invalid json body")
}
defer r.Body.Close()
kty, _ := tok["kty"].(string)
if "" == kty {
if 0 == mathrand.Intn(2) {
kty = "RSA"
} else {
kty = "EC"
}
}
return kty, nil
}
http.HandleFunc("/private.jwk.json", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/private.jwk.json", func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
if "POST" != r.Method {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
kty, err := getKty(r)
if nil != err {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var privkey keypairs.PrivateKey
if "RSA" == kty {
keylen := 2048
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
} else {
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
}
jwk := MarshalJWKPrivateKey(privkey)
w.Write(jwk)
})
http.HandleFunc("/priv.der", func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s\n", r.Method, r.URL.Path) log.Printf("%s %s\n", r.Method, r.URL.Path)
if "POST" != r.Method { if "POST" != r.Method {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return return
} }
keylen := 2048 kty, err := getKty(r)
privkey, _ := rsa.GenerateKey(rand.Reader, keylen) if nil != err {
jwk := string(MarshalJWKPrivateKey(privkey)) http.Error(w, err.Error(), http.StatusBadRequest)
fmt.Fprintf(w, jwk) return
}
var privkey keypairs.PrivateKey
if "RSA" == kty {
keylen := 2048
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
} else {
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
}
der, _ := MarshalDERPrivateKey(privkey)
w.Write(der)
})
http.HandleFunc("/priv.pem", func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s\n", r.Method, r.URL.Path)
if "POST" != r.Method {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
kty, err := getKty(r)
if nil != err {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var privkey keypairs.PrivateKey
if "RSA" == kty {
keylen := 2048
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
} else {
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
}
privpem, _ := MarshalPEMPrivateKey(privkey)
w.Write(privpem)
}) })
http.HandleFunc("/inspect_token", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/inspect_token", func(w http.ResponseWriter, r *http.Request) {