You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
2.6 KiB
135 lines
2.6 KiB
package jose |
|
|
|
import ( |
|
"bytes" |
|
"encoding/base64" |
|
"encoding/binary" |
|
"encoding/json" |
|
"math/big" |
|
"strings" |
|
) |
|
|
|
// JSON Web Key |
|
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5 |
|
type JWK struct { |
|
ID string |
|
Type string |
|
Alg string |
|
Use string |
|
Exponent int |
|
Modulus *big.Int |
|
Secret []byte |
|
} |
|
|
|
type jwkJSON struct { |
|
ID string `json:"kid"` |
|
Type string `json:"kty"` |
|
Alg string `json:"alg"` |
|
Use string `json:"use"` |
|
Exponent string `json:"e"` |
|
Modulus string `json:"n"` |
|
} |
|
|
|
func (j *JWK) MarshalJSON() ([]byte, error) { |
|
t := jwkJSON{ |
|
ID: j.ID, |
|
Type: j.Type, |
|
Alg: j.Alg, |
|
Use: j.Use, |
|
Exponent: encodeExponent(j.Exponent), |
|
Modulus: encodeModulus(j.Modulus), |
|
} |
|
|
|
return json.Marshal(&t) |
|
} |
|
|
|
func (j *JWK) UnmarshalJSON(data []byte) error { |
|
var t jwkJSON |
|
err := json.Unmarshal(data, &t) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
e, err := decodeExponent(t.Exponent) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
n, err := decodeModulus(t.Modulus) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
j.ID = t.ID |
|
j.Type = t.Type |
|
j.Alg = t.Alg |
|
j.Use = t.Use |
|
j.Exponent = e |
|
j.Modulus = n |
|
|
|
return nil |
|
} |
|
|
|
type JWKSet struct { |
|
Keys []JWK `json:"keys"` |
|
} |
|
|
|
func decodeExponent(e string) (int, error) { |
|
decE, err := decodeBase64URLPaddingOptional(e) |
|
if err != nil { |
|
return 0, err |
|
} |
|
var eBytes []byte |
|
if len(decE) < 8 { |
|
eBytes = make([]byte, 8-len(decE), 8) |
|
eBytes = append(eBytes, decE...) |
|
} else { |
|
eBytes = decE |
|
} |
|
eReader := bytes.NewReader(eBytes) |
|
var E uint64 |
|
err = binary.Read(eReader, binary.BigEndian, &E) |
|
if err != nil { |
|
return 0, err |
|
} |
|
return int(E), nil |
|
} |
|
|
|
func encodeExponent(e int) string { |
|
b := make([]byte, 8) |
|
binary.BigEndian.PutUint64(b, uint64(e)) |
|
var idx int |
|
for ; idx < 8; idx++ { |
|
if b[idx] != 0x0 { |
|
break |
|
} |
|
} |
|
return base64.RawURLEncoding.EncodeToString(b[idx:]) |
|
} |
|
|
|
// Turns a URL encoded modulus of a key into a big int. |
|
func decodeModulus(n string) (*big.Int, error) { |
|
decN, err := decodeBase64URLPaddingOptional(n) |
|
if err != nil { |
|
return nil, err |
|
} |
|
N := big.NewInt(0) |
|
N.SetBytes(decN) |
|
return N, nil |
|
} |
|
|
|
func encodeModulus(n *big.Int) string { |
|
return base64.RawURLEncoding.EncodeToString(n.Bytes()) |
|
} |
|
|
|
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not. |
|
// The stdlib version currently doesn't handle this. |
|
// We can get rid of this is if this bug: |
|
// https://github.com/golang/go/issues/4237 |
|
// ever closes. |
|
func decodeBase64URLPaddingOptional(e string) ([]byte, error) { |
|
if m := len(e) % 4; m != 0 { |
|
e += strings.Repeat("=", 4-m) |
|
} |
|
return base64.URLEncoding.DecodeString(e) |
|
}
|
|
|