Last active
December 3, 2020 20:13
-
-
Save guillaq/c2bc54a04ffcbf15292bb78b0ee64465 to your computer and use it in GitHub Desktop.
Golang JWT Sign
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"crypto/ecdsa" | |
"crypto/elliptic" | |
"crypto/rand" | |
"crypto/sha256" | |
"encoding/base64" | |
"encoding/json" | |
"fmt" | |
"math/big" | |
"strings" | |
) | |
// b64Encode is an equivalent to EncodeToString that returns bytes | |
// and is appropriate for JWTs | |
func b64Encode(raw []byte) []byte { | |
encoded := make([]byte, base64.RawURLEncoding.EncodedLen(len(raw))) | |
base64.RawURLEncoding.Encode(encoded, raw) | |
return encoded | |
} | |
// b64Decode is an equivalent to DecodeToString that returns bytes | |
// and is appropriate for JWTs | |
func b64Decode(raw []byte) ([]byte, error) { | |
decoded := make([]byte, base64.RawURLEncoding.DecodedLen(len(raw))) | |
_, err := base64.RawURLEncoding.Decode(decoded, raw) | |
return decoded, err | |
} | |
// encode converts a raw interface that is convertible to JSON into b64 bytes | |
func encode(m interface{}) ([]byte, error) { | |
raw, err := json.Marshal(m) | |
if err != nil { | |
return nil, err | |
} | |
return b64Encode(raw), nil | |
} | |
// prepareSignatureData concatenates the header and claims | |
func prepareSignatureData(header, claims []byte) (data []byte) { | |
data = append(data, header...) | |
data = append(data, byte('.')) | |
data = append(data, claims...) | |
return | |
} | |
const KEY_SIZE = 256 / 8 | |
// sign generates a signature using the Elliptic curve algorithm | |
// Adapted from https://github.com/dgrijalva/jwt-go/blob/master/ecdsa.go | |
func sign(key *ecdsa.PrivateKey, raw []byte) ([]byte, error) { | |
var err error | |
hash := sha256.Sum256(raw) | |
if r, s, err := ecdsa.Sign(rand.Reader, key, hash[:]); err == nil { | |
// We serialize the outputs (r and s) into big-endian byte arrays and pad | |
// them with zeros on the left to make sure the sizes work out. Both arrays | |
// must be keyBytes long, and the output must be 2*keyBytes long. | |
rBytes := r.Bytes() | |
rBytesPadded := make([]byte, KEY_SIZE) | |
copy(rBytesPadded[KEY_SIZE-len(rBytes):], rBytes) | |
sBytes := s.Bytes() | |
sBytesPadded := make([]byte, KEY_SIZE) | |
copy(sBytesPadded[KEY_SIZE-len(sBytes):], sBytes) | |
out := append(rBytesPadded, sBytesPadded...) | |
return out, nil | |
} | |
return nil, err | |
} | |
// Splits using the last . and returns the first part in UTF-8 encoding and second with b64 | |
func splitToken(token string) (content, signature []byte, err error) { | |
lastSep := strings.LastIndex(token, ".") | |
content = []byte(token[0:lastSep]) | |
signature, err = b64Decode([]byte(token[lastSep+1:])) | |
return | |
} | |
// verifies a signature using the Elliptic curve algorithm | |
// Adapted from https://github.com/dgrijalva/jwt-go/blob/master/ecdsa.go | |
func verify(key *ecdsa.PublicKey, data []byte, signature []byte) bool { | |
r := big.NewInt(0).SetBytes(signature[:KEY_SIZE]) | |
s := big.NewInt(0).SetBytes(signature[KEY_SIZE:]) | |
hash := sha256.Sum256(data) | |
return ecdsa.Verify(key, hash[:], r, s) | |
} | |
// makeJWT generates a JWT given a header and claims | |
func makeJWT(header, claims interface{}, key *ecdsa.PrivateKey) string { | |
encodedHeader, err := encode(header) | |
if err != nil { | |
panic(fmt.Sprintf("Could not encode header with %s", err)) | |
} | |
encodedClaims, err := encode(claims) | |
if err != nil { | |
panic(fmt.Sprintf("Could not encode claims with %s", err)) | |
} | |
signature, err := sign(key, prepareSignatureData(encodedHeader, encodedClaims)) | |
if err != nil { | |
panic(fmt.Sprintf("Could not sign with %s", err)) | |
} | |
return string(encodedHeader) + "." + string(encodedClaims) + "." + string(b64Encode(signature)) | |
} | |
// checkJWT validates the JWT signature | |
func checkJWT(jwt string, key *ecdsa.PublicKey) (bool, error) { | |
data, signature, err := splitToken(jwt) | |
if err != nil { | |
return false, err | |
} | |
return verify(key, data, signature), nil | |
} | |
func main() { | |
header := map[string]string{ | |
"alg": "ES256", "typ": "JWT", | |
} | |
claims := map[string]string{ | |
"iss": "getiota.fr", | |
} | |
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | |
if err != nil { | |
panic(fmt.Sprintf("Could not generate key with %s", err)) | |
} | |
jwt := makeJWT(header, claims, key) | |
fmt.Printf("the JWT is %s\n", jwt) | |
isValid, err := checkJWT(jwt, &key.PublicKey) | |
if err != nil { | |
panic(fmt.Sprintf("Could not verify token with %s", err)) | |
} | |
fmt.Printf("the JWT is valid ? %t\n", isValid) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment