Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement jwk for private keys #39

Merged
merged 2 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions provider/github-app-token/github/jwk/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,83 @@ import (
"math/big"
)

// RFC7518 6.2.2. Parameters for Elliptic Curve Private Keys
type ecdsaPrivateKey struct {
commonKey

// RFC7518 6.2.1.1. "crv" (Curve) Parameter
Crv string `json:"crv"`

// RFC7518 6.2.1.2. "x" (X Coordinate) Parameter
X string `json:"x"`

// RFC7518 6.2.1.3. "y" (Y Coordinate) Parameter
Y string `json:"y"`

// RFC7518 6.2.2.1. "d" (ECC Private Key) Parameter
D string `json:"d"`

privateKey ecdsa.PrivateKey
}

func (key *ecdsaPrivateKey) PrivateKey() interface{} {
return &key.privateKey
}

func (key *ecdsaPrivateKey) PublicKey() interface{} {
return &key.privateKey.PublicKey
}

func parseEcdsaPrivateKey(data []byte) (Key, error) {
var key ecdsaPrivateKey
if err := json.Unmarshal(data, &key); err != nil {
return nil, err
}
if err := key.commonKey.decode(); err != nil {
return nil, err
}
if err := key.decode(); err != nil {
return nil, err
}
return &key, nil
}

// decode decodes the encoded values into publicKey.
func (key *ecdsaPrivateKey) decode() error {
switch key.Crv {
case "P-224":
key.privateKey.Curve = elliptic.P224()
case "P-256":
key.privateKey.Curve = elliptic.P256()
case "P-384":
key.privateKey.Curve = elliptic.P384()
case "P-521":
key.privateKey.Curve = elliptic.P521()
default:
return fmt.Errorf("jwk: unknown elliptic curve: %q", key.Crv)
}

dataX, err := base64.RawURLEncoding.DecodeString(key.X)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter x: %w", err)
}
key.privateKey.X = new(big.Int).SetBytes(dataX)

dataY, err := base64.RawURLEncoding.DecodeString(key.Y)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter y: %w", err)
}
key.privateKey.Y = new(big.Int).SetBytes(dataY)

dataD, err := base64.RawURLEncoding.DecodeString(key.D)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter d: %w", err)
}
key.privateKey.D = new(big.Int).SetBytes(dataD)

return nil
}

// RFC7518 6.2.1. Parameters for Elliptic Curve Public Keys
type ecdsaPublicKey struct {
commonKey
Expand Down
7 changes: 5 additions & 2 deletions provider/github-app-token/github/jwk/jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ func ParseKey(data []byte) (Key, error) {
}
switch hint.Kty {
case "EC":
// TODO: implement private key.
return parseEcdsaPublicKey(data)
if len(hint.D) > 0 {
return parseEcdsaPrivateKey(data)
} else {
return parseEcdsaPublicKey(data)
}
case "RSA":
if len(hint.D) > 0 {
return parseRSAPrivateKey(data)
Expand Down
86 changes: 86 additions & 0 deletions provider/github-app-token/github/jwk/jwk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,92 @@ func TestKeyAppendixA(t *testing.T) {
}
})

t.Run("RFC 7517 A.2. Example Private Keys (EC)", func(t *testing.T) {
rawKey := `{"kty":"EC",` +
`"crv":"P-256",` +
`"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",` +
`"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",` +
`"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",` +
`"use":"enc",` +
`"kid":"1"}`
key, err := ParseKey([]byte(rawKey))
if err != nil {
t.Fatal(err)
}
if key.KeyType() != "EC" {
t.Errorf("unexpected key type: want %s, got %s", "EC", key.KeyType())
}
privateKey, ok := key.PrivateKey().(*ecdsa.PrivateKey)
if !ok {
t.Errorf("unexpected key type: want *ecdsa.PrivateKey, got %T", key.PrivateKey())
}
if privateKey.Curve.Params().Name != "P-256" {
t.Errorf("unexpected curve: want P-256, got %s", privateKey.Curve.Params().Name)
}
publicKey, ok := key.PublicKey().(*ecdsa.PublicKey)
if !ok {
t.Errorf("unexpected key type: want *ecdsa.PublicKey, got %T", key.PublicKey())
}
if !privateKey.PublicKey.Equal(publicKey) {
t.Error("public keys are mismatch")
}
})

t.Run("RFC 7517 A.2. Example Private Keys (RSA)", func(t *testing.T) {
rawKey := `{"kty":"RSA",` +
`"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4` +
`cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst` +
`n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q` +
`vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS` +
`D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw` +
`0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",` +
`"e":"AQAB",` +
`"d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9` +
`M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij` +
`wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d` +
`_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz` +
`nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz` +
`me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",` +
`"p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV` +
`nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV` +
`WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",` +
`"q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum` +
`qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx` +
`kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",` +
`"dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim` +
`YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu` +
`YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",` +
`"dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU` +
`vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9` +
`GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",` +
`"qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg` +
`UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx` +
`yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",` +
`"alg":"RS256",` +
`"kid":"2011-04-29"}`
key, err := ParseKey([]byte(rawKey))
if err != nil {
t.Fatal(err)
}
if key.KeyType() != "RSA" {
t.Errorf("unexpected key type: want %s, got %s", "RSA", key.KeyType())
}
if key.Algorithm() != "RS256" {
t.Errorf("unexpected algorithm: want %s, got %s", "RS256", key.Algorithm())
}
privateKey, ok := key.PrivateKey().(*rsa.PrivateKey)
if !ok {
t.Errorf("unexpected key type: want *rsa.PrivateKey, got %T", key.PrivateKey())
}
publicKey, ok := key.PublicKey().(*rsa.PublicKey)
if !ok {
t.Errorf("unexpected key type: want *rsa.PublicKey, got %T", key.PublicKey())
}
if !privateKey.PublicKey.Equal(publicKey) {
t.Error("public keys are mismatch")
}
})

t.Run("RFC 7517 A.3. Example Symmetric Keys (A128KW)", func(t *testing.T) {
rawKey := `{"kty":"oct","alg":"A128KW","k":"GawgguFyGrWKav7AX4VKUg"}`
key, err := ParseKey([]byte(rawKey))
Expand Down
118 changes: 114 additions & 4 deletions provider/github-app-token/github/jwk/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import (
type rsaPrivateKey struct {
commonKey

// RFC7518 6.3.1.1. "n" (Modulus) Parameter
N string `json:"n"`

// RFC7518 6.3.1.2. "e" (Exponent) Parameter
E string `json:"e"`

// RFC7518 6.3.2.1. "d" (Private Exponent) Parameter
D string `json:"d"`

Expand All @@ -22,13 +28,13 @@ type rsaPrivateKey struct {
Q string `json:"q"`

// RFC7518 6.3.2.4. "dp" (First Factor CRT Exponent) Parameter
DP string `json:"dp"`
Dp string `json:"dp,omitempty"`

// RFC7518 6.3.2.5. "dq" (Second Factor CRT Exponent) Parameter
DQ string `json:"dq"`
Dq string `json:"dq,omitempty"`

// RFC7518 6.3.2.6. "qi" (First CRT Coefficient) Parameter
QI string `json:"qi"`
Qi string `json:"qi,omitempty"`

// RFC7518 6.3.2.7. "oth" (Other Primes Info) Parameter
Oth []struct {
Expand All @@ -41,6 +47,8 @@ type rsaPrivateKey struct {
// RFC7518 6.3.2.7.3. "t" (Factor CRT Coefficient)
T string `json:"t"`
} `json:"oth,omitempty"`

privateKey rsa.PrivateKey
}

func parseRSAPrivateKey(data []byte) (Key, error) {
Expand All @@ -57,8 +65,110 @@ func parseRSAPrivateKey(data []byte) (Key, error) {
return &key, nil
}

func (key *rsaPrivateKey) PrivateKey() interface{} {
return &key.privateKey
}

func (key *rsaPrivateKey) PublicKey() interface{} {
return &key.privateKey.PublicKey
}

func (key *rsaPrivateKey) decode() error {
return nil
// parameters for public key
dataE, err := base64.RawURLEncoding.DecodeString(key.E)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter e: %w", err)
}
var e int
for _, v := range dataE {
e = (e << 8) | int(v)
}
key.privateKey.E = e

dataN, err := base64.RawURLEncoding.DecodeString(key.N)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter n: %w", err)
}
key.privateKey.N = new(big.Int).SetBytes(dataN)

// parameters for private key
dataD, err := base64.RawURLEncoding.DecodeString(key.D)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter d: %w", err)
}
key.privateKey.D = new(big.Int).SetBytes(dataD)

dataP, err := base64.RawURLEncoding.DecodeString(key.P)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter p: %w", err)
}
p := new(big.Int).SetBytes(dataP)

dataQ, err := base64.RawURLEncoding.DecodeString(key.Q)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter q: %w", err)
}
q := new(big.Int).SetBytes(dataQ)

key.privateKey.Primes = []*big.Int{p, q}

crtValues := make([]rsa.CRTValue, 0, len(key.Oth))
for i, v := range key.Oth {
dataR, err := base64.RawURLEncoding.DecodeString(v.R)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter oth[%d].r: %w", i, err)
}
r := new(big.Int).SetBytes(dataR)
key.privateKey.Primes = append(key.privateKey.Primes, r)

dataD, err := base64.RawURLEncoding.DecodeString(v.D)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter oth[%d].d: %w", i, err)
}
d := new(big.Int).SetBytes(dataD)

dataT, err := base64.RawURLEncoding.DecodeString(v.T)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter oth[%d].d: %w", i, err)
}
t := new(big.Int).SetBytes(dataT)

crtValues = append(crtValues, rsa.CRTValue{
Exp: d,
Coeff: t,
R: r,
})
}

// precomputed values
if key.Dp != "" && key.Dq != "" && key.Qi != "" {
dataDp, err := base64.RawURLEncoding.DecodeString(key.Q)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter dp: %w", err)
}
dp := new(big.Int).SetBytes(dataDp)

dataDq, err := base64.RawURLEncoding.DecodeString(key.Q)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter dq: %w", err)
}
dq := new(big.Int).SetBytes(dataDq)

dataQi, err := base64.RawURLEncoding.DecodeString(key.Qi)
if err != nil {
return fmt.Errorf("jwk: failed to parse parameter qi: %w", err)
}
qi := new(big.Int).SetBytes(dataQi)

key.privateKey.Precomputed = rsa.PrecomputedValues{
Dp: dp,
Dq: dq,
Qinv: qi,
CRTValues: crtValues,
}
}

return key.privateKey.Validate()
}

// RFC7518 6.3.1. Parameters for RSA Public Keys
Expand Down