| 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) |
| } |