Skip to content

Instantly share code, notes, and snippets.

@guillaq
Last active December 3, 2020 20:13
Show Gist options
  • Save guillaq/c2bc54a04ffcbf15292bb78b0ee64465 to your computer and use it in GitHub Desktop.
Save guillaq/c2bc54a04ffcbf15292bb78b0ee64465 to your computer and use it in GitHub Desktop.
Golang JWT Sign
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