Skip to content

Commit

Permalink
implemented all 3 elliptic curve algs
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelFraser99 committed Sep 8, 2023
1 parent 124a723 commit 9c73e64
Show file tree
Hide file tree
Showing 10 changed files with 950 additions and 30 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ For more information on SD-JWTs, see the [Selective Disclosure JWTs RFC](https:/
go get github.com/MichaelFraser99/go-sd-jwt
```


## Algorithms Supported
Currently, the module will support the following jwt signing algorithms:
- ES256
- ES384
- ES512

## Functions
### Pointer
Expand Down Expand Up @@ -77,14 +81,14 @@ type SdJwt struct {
// Has unexported fields.
}
```
SdJwt this object represents a valid SD-JWT. Created using the New function
SdJwt this object represents a valid SD-JWT. Created using the FromToken function
which performs the required validation. Helper methods are provided for
retrieving the contents

```go
func New(token string) (*SdJwt, error)
func FromToken(token string) (*SdJwt, error)
```
New Creates a new SD-JWT from a JWS or JWT format token. The token is
FromToken Creates a new SD-JWT from a JWS or JWT format token. The token is
validated inline with the SD-JWT specification. If the token is valid,
a new SdJwt object is returned.

Expand Down
141 changes: 141 additions & 0 deletions internal/jwt/algorithms/es256/ES256.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package es256

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/big"
)

type PublicKey struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}

func (pubKey *PublicKey) Equal(x PublicKey) bool {
if pubKey.X == x.X && pubKey.Y == x.Y && pubKey.Kty == x.Kty && pubKey.Crv == x.Crv {
return true
}
return false
}

type ES256 struct{}

func (signer *ES256) ValidateSignature(token, signature string, publicKeyJson string) (bool, error) {
curve := elliptic.P256()

var publicKey PublicKey
err := json.Unmarshal([]byte(publicKeyJson), &publicKey)
if err != nil {
return false, errors.New("provided public key json isn't valid es256 public key")
}

xBytes, err := base64.RawURLEncoding.DecodeString(publicKey.X)
if err != nil {
return false, err
}

yBytes, err := base64.RawURLEncoding.DecodeString(publicKey.Y)
if err != nil {
return false, err
}

pk := &ecdsa.PublicKey{
Curve: curve,
X: big.NewInt(0).SetBytes(xBytes),
Y: big.NewInt(0).SetBytes(yBytes),
}

bodyHash := sha256.Sum256([]byte(token))

r, s, err := signer.extractRSFromSignature(signature)
if err != nil {
return false, err
}

return ecdsa.Verify(pk, bodyHash[:], r, s), nil
}

func (signer *ES256) extractRSFromSignature(signature string) (*big.Int, *big.Int, error) {
decodedSignature, err := base64.RawURLEncoding.DecodeString(signature)
if err != nil {
return nil, nil, err
}

if len(decodedSignature) != 64 {
return nil, nil, errors.New("signature should be 64 bytes for ES256")
}
rb := decodedSignature[:32]
sb := decodedSignature[32:]

r := big.NewInt(0).SetBytes(rb)
s := big.NewInt(0).SetBytes(sb)

return r, s, nil
}

func (signer *ES256) Sign(body map[string]interface{}, headerKeys map[string]string) (*string, crypto.PrivateKey, crypto.PublicKey, error) {
curve := elliptic.P256()
pk, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, nil, err
}

header := headerKeys
header["alg"] = "ES256"

headerBytes, err := json.Marshal(header)
if err != nil {
return nil, nil, nil, errors.New("failed to marshal header to bytes")
}
base64Header := base64.RawURLEncoding.EncodeToString(headerBytes)

bodyBytes, err := json.Marshal(body)
if err != nil {
return nil, nil, nil, errors.New("failed to marshal body to bytes")
}
base64Body := base64.RawURLEncoding.EncodeToString(bodyBytes)

token := fmt.Sprintf("%s.%s", base64Header, base64Body)

digest := sha256.Sum256([]byte(token))

r, s, err := ecdsa.Sign(rand.Reader, pk, digest[:])
if err != nil {
return nil, nil, nil, err
}

rb := r.Bytes()
sb := s.Bytes()

sigBytes := append(rb, sb...)

base64Sig := base64.RawURLEncoding.EncodeToString(sigBytes)
signedToken := fmt.Sprintf("%s.%s", token, base64Sig)

cryptoPubKey := pk.PublicKey

x := base64.RawURLEncoding.EncodeToString(cryptoPubKey.X.Bytes())
y := base64.RawURLEncoding.EncodeToString(cryptoPubKey.Y.Bytes())

pubKey := PublicKey{
Kty: "EC",
Crv: "P-256",
X: x,
Y: y,
}

return &signedToken, pk, pubKey, nil

}
func (signer *ES256) SignWithKey(body map[string]interface{}, headerKeys map[string]string, privateKey string) (*string, error) {
return nil, nil //todo
}
72 changes: 72 additions & 0 deletions internal/jwt/algorithms/es256/ES256_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package es256

import (
"encoding/json"
"strings"
"testing"
)

func TestValidateSignatureES256(t *testing.T) {
publicKey := "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ\",\"y\":\"Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8\"}"

token := "eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOiBbIkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tBTmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFrb2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vZXhhbXBsZS5jb20vaXNzdWVyIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjogInVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0"

signature := "kmx687kUBiIDvKWgo2Dub-TpdCCRLZwtD7TOj4RoLsUbtFBI8sMrtH2BejXtm_P6fOAjKAVc_7LRNJFgm3PJhg"

es256 := &ES256{}

valid, err := es256.ValidateSignature(token, signature, publicKey)
if err != nil {
t.Error("no error should be thrown", err)
}
if !valid {
t.Error("signature is not valid")
}
}

func TestES256_Sign(t *testing.T) {
body := map[string]interface{}{
"firstname": "john",
"surname": "smith",
"address": map[string]string{
"street": "Long Lane",
"number": "15",
"city": "Edinburgh",
},
}

headerKeys := map[string]string{
"typ": "jwt",
}

es256 := &ES256{}

token, privateKey, publicKey, err := es256.Sign(body, headerKeys)
if err != nil {
t.Error("no error should be thrown", err)
}
if token == nil {
t.Error("token should not be nil")
}
if privateKey == nil {
t.Error("private key should not be nil")
}
if publicKey == nil {
t.Error("public key should not be nil")
}
jsonPk, err := json.Marshal(publicKey)
if err != nil {
t.Error("no error should be thrown", err)
}
t.Log(*token)
t.Log(string(jsonPk))

components := strings.Split(*token, ".")
valid, err := es256.ValidateSignature(strings.Join(components[0:2], "."), components[2], string(jsonPk))
if err != nil {
t.Error("no error should be thrown", err)
}
if !valid {
t.Error("signature is not valid")
}
}
141 changes: 141 additions & 0 deletions internal/jwt/algorithms/es384/ES384.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package es384

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/big"
)

type PublicKey struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}

func (pubKey *PublicKey) Equal(x PublicKey) bool {
if pubKey.X == x.X && pubKey.Y == x.Y && pubKey.Kty == x.Kty && pubKey.Crv == x.Crv {
return true
}
return false
}

type ES384 struct{}

func (signer *ES384) ValidateSignature(token, signature string, publicKeyJson string) (bool, error) {
curve := elliptic.P384()

var publicKey PublicKey
err := json.Unmarshal([]byte(publicKeyJson), &publicKey)
if err != nil {
return false, errors.New("provided public key json isn't valid es384 public key")
}

xBytes, err := base64.RawURLEncoding.DecodeString(publicKey.X)
if err != nil {
return false, err
}

yBytes, err := base64.RawURLEncoding.DecodeString(publicKey.Y)
if err != nil {
return false, err
}

pk := &ecdsa.PublicKey{
Curve: curve,
X: big.NewInt(0).SetBytes(xBytes),
Y: big.NewInt(0).SetBytes(yBytes),
}

bodyHash := sha512.Sum384([]byte(token))

r, s, err := signer.extractRSFromSignature(signature)
if err != nil {
return false, err
}

return ecdsa.Verify(pk, bodyHash[:], r, s), nil
}

func (signer *ES384) extractRSFromSignature(signature string) (*big.Int, *big.Int, error) {
decodedSignature, err := base64.RawURLEncoding.DecodeString(signature)
if err != nil {
return nil, nil, err
}

if len(decodedSignature) != 96 {
return nil, nil, errors.New("signature should be 64 bytes for ES384")
}
rb := decodedSignature[:48]
sb := decodedSignature[48:]

r := big.NewInt(0).SetBytes(rb)
s := big.NewInt(0).SetBytes(sb)

return r, s, nil
}

func (signer *ES384) Sign(body map[string]interface{}, headerKeys map[string]string) (*string, crypto.PrivateKey, crypto.PublicKey, error) {
curve := elliptic.P384()
pk, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, nil, err
}

header := headerKeys
header["alg"] = "ES384"

headerBytes, err := json.Marshal(header)
if err != nil {
return nil, nil, nil, errors.New("failed to marshal header to bytes")
}
base64Header := base64.RawURLEncoding.EncodeToString(headerBytes)

bodyBytes, err := json.Marshal(body)
if err != nil {
return nil, nil, nil, errors.New("failed to marshal body to bytes")
}
base64Body := base64.RawURLEncoding.EncodeToString(bodyBytes)

token := fmt.Sprintf("%s.%s", base64Header, base64Body)

digest := sha512.Sum384([]byte(token))

r, s, err := ecdsa.Sign(rand.Reader, pk, digest[:])
if err != nil {
return nil, nil, nil, err
}

rb := r.Bytes()
sb := s.Bytes()

sigBytes := append(rb, sb...)

base64Sig := base64.RawURLEncoding.EncodeToString(sigBytes)
signedToken := fmt.Sprintf("%s.%s", token, base64Sig)

cryptoPubKey := pk.PublicKey

x := base64.RawURLEncoding.EncodeToString(cryptoPubKey.X.Bytes())
y := base64.RawURLEncoding.EncodeToString(cryptoPubKey.Y.Bytes())

pubKey := PublicKey{
Kty: "EC",
Crv: "P-384",
X: x,
Y: y,
}

return &signedToken, pk, pubKey, nil

}
func (signer *ES384) SignWithKey(body map[string]interface{}, headerKeys map[string]string, privateKey string) (*string, error) {
return nil, nil //todo
}
Loading

0 comments on commit 9c73e64

Please sign in to comment.