Skip to content

Commit

Permalink
Support for secp256k1
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Dec 12, 2023
1 parent 91104d5 commit 0ee4a22
Show file tree
Hide file tree
Showing 36 changed files with 410 additions and 157 deletions.
9 changes: 8 additions & 1 deletion crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,14 @@ func (client *Crypto) Configure(config core.ServerConfig) error {
// New generates a new key pair.
// Stores the private key, returns the public basicKey.
// It returns an error when a key with the resulting ID already exists.
func (client *Crypto) New(ctx context.Context, namingFunc KIDNamingFunc) (Key, error) {
func (client *Crypto) New(ctx context.Context, keyType KeyType, namingFunc KIDNamingFunc) (Key, error) {
switch keyType {
case ECP256Key, ECP256k1Key, Ed25519Key, RSA2048Key, RSA3072Key, RSA4096Key:
// ok
default:
return nil, fmt.Errorf("invalid key type: %s", keyType)
}

keyPair, kid, err := generateKeyPairAndKID(namingFunc)
if err != nil {
return nil, err
Expand Down
21 changes: 14 additions & 7 deletions crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestCrypto_Exists(t *testing.T) {
client := createCrypto(t)

kid := "kid"
client.New(audit.TestContext(), StringNamingFunc(kid))
client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))

t.Run("returns true for existing key", func(t *testing.T) {
assert.True(t, client.Exists(ctx, kid))
Expand All @@ -69,7 +69,7 @@ func TestCrypto_New(t *testing.T) {
kid := "kid"
auditLogs := audit.CaptureLogs(t)

key, err := client.New(ctx, StringNamingFunc(kid))
key, err := client.New(ctx, ECP256Key, StringNamingFunc(kid))

assert.NoError(t, err)
assert.NotNil(t, key.Public())
Expand All @@ -80,17 +80,24 @@ func TestCrypto_New(t *testing.T) {
t.Run("error - invalid KID", func(t *testing.T) {
kid := "../certificate"

key, err := client.New(ctx, StringNamingFunc(kid))
key, err := client.New(ctx, ECP256Key, StringNamingFunc(kid))

assert.ErrorContains(t, err, "invalid key ID")
assert.Nil(t, key)
})

t.Run("error - invalid key type", func(t *testing.T) {
key, err := client.New(ctx, "caesar", StringNamingFunc("foo"))

assert.EqualError(t, err, "invalid key type: caesar")
assert.Nil(t, key)
})

t.Run("error - NamingFunction returns err", func(t *testing.T) {
errorNamingFunc := func(key crypto.PublicKey) (string, error) {
return "", errors.New("b00m!")
}
_, err := client.New(ctx, errorNamingFunc)
_, err := client.New(ctx, ECP256Key, errorNamingFunc)
assert.Error(t, err)
})

Expand All @@ -101,7 +108,7 @@ func TestCrypto_New(t *testing.T) {
storageMock.EXPECT().SavePrivateKey(ctx, gomock.Any(), gomock.Any()).Return(errors.New("foo"))

client := &Crypto{storage: storageMock}
key, err := client.New(ctx, StringNamingFunc("123"))
key, err := client.New(ctx, ECP256Key, StringNamingFunc("123"))
assert.Nil(t, key)
assert.Error(t, err)
assert.Equal(t, "could not create new keypair: could not save private key: foo", err.Error())
Expand All @@ -113,7 +120,7 @@ func TestCrypto_New(t *testing.T) {
storageMock.EXPECT().PrivateKeyExists(ctx, "123").Return(true)

client := &Crypto{storage: storageMock}
key, err := client.New(ctx, StringNamingFunc("123"))
key, err := client.New(ctx, ECP256Key, StringNamingFunc("123"))
assert.Nil(t, key)
assert.EqualError(t, err, "key with the given ID already exists", err)
})
Expand All @@ -123,7 +130,7 @@ func TestCrypto_Resolve(t *testing.T) {
ctx := context.Background()
client := createCrypto(t)
kid := "kid"
key, _ := client.New(audit.TestContext(), StringNamingFunc(kid))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))

t.Run("ok", func(t *testing.T) {
resolvedKey, err := client.Resolve(ctx, "kid")
Expand Down
2 changes: 1 addition & 1 deletion crypto/decrypter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestCrypto_Decrypt(t *testing.T) {
t.Run("ok", func(t *testing.T) {
client := createCrypto(t)
kid := "kid"
key, _ := client.New(audit.TestContext(), StringNamingFunc(kid))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))
pubKey := key.Public().(*ecdsa.PublicKey)

cipherText, err := EciesEncrypt(pubKey, []byte("hello!"))
Expand Down
19 changes: 18 additions & 1 deletion crypto/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,28 @@ var ErrPrivateKeyNotFound = errors.New("private key not found")
// KIDNamingFunc is a function passed to New() which generates the kid for the pub/priv key
type KIDNamingFunc func(key crypto.PublicKey) (string, error)

type KeyType string

const (
// ECP256Key is the key type for EC P-256
ECP256Key KeyType = "secp256r1"
// ECP256k1Key is the key type for EC P-256K (Koblitz curve)
ECP256k1Key = "secp256k1"
// Ed25519Key is the key type for ed25519
Ed25519Key = "ed25519"
// RSA2048Key is the key type for rsa2048
RSA2048Key = "rsa2048"
// RSA3072Key is the key type for rsa3072
RSA3072Key = "rsa3072"
// RSA4096Key is the key type for rsa4096
RSA4096Key = "rsa4096"
)

// KeyCreator is the interface for creating key pairs.
type KeyCreator interface {
// New generates a keypair and returns a Key. The context is used to pass audit information.
// The KIDNamingFunc will provide the kid.
New(ctx context.Context, namingFunc KIDNamingFunc) (Key, error)
New(ctx context.Context, keyType KeyType, namingFunc KIDNamingFunc) (Key, error)
}

// KeyResolver is the interface for resolving keys.
Expand Down
30 changes: 21 additions & 9 deletions crypto/jwx.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
"github.com/lestrrat-go/jwx/v2/jwk"
Expand All @@ -41,7 +42,7 @@ import (
// ErrUnsupportedSigningKey is returned when an unsupported private key is used to sign. Currently only ecdsa and rsa keys are supported
var ErrUnsupportedSigningKey = errors.New("signing key algorithm not supported")

var supportedAlgorithms = []jwa.SignatureAlgorithm{jwa.PS256, jwa.PS384, jwa.PS512, jwa.ES256, jwa.EdDSA, jwa.ES384, jwa.ES512}
var supportedAlgorithms = []jwa.SignatureAlgorithm{jwa.PS256, jwa.PS384, jwa.PS512, jwa.ES256, jwa.EdDSA, jwa.ES256K, jwa.ES384, jwa.ES512}

const defaultRsaEncryptionAlgorithm = jwa.RSA_OAEP_256
const defaultEcEncryptionAlgorithm = jwa.ECDH_ES_A256KW
Expand Down Expand Up @@ -135,8 +136,10 @@ func jwkKey(signer crypto.Signer) (key jwk.Key, err error) {
case *rsa.PrivateKey:
key.Set(jwk.AlgorithmKey, jwa.PS256)
case *ecdsa.PrivateKey:
var alg jwa.SignatureAlgorithm
alg, err = ecAlg(k)
alg, err := ecAlgUsingPublicKey(k.PublicKey)
if err != nil {
return nil, err
}
key.Set(jwk.AlgorithmKey, alg)
default:
err = errors.New("unsupported signing private key")
Expand All @@ -159,7 +162,17 @@ func signJWT(key jwk.Key, claims map[string]interface{}, headers map[string]inte
return "", fmt.Errorf("invalid JWT headers: %w", err)
}

sig, err = jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(key.Algorithm().String()), key, jws.WithProtectedHeaders(hdr)))
var publicKey crypto.PublicKey
if err = key.Raw(&publicKey); err != nil {
return "", err
}
alg, err := SignatureAlgorithm(publicKey)
if err != nil {
return "", err
}

sig, err = jwt.Sign(t, jwt.WithKey(alg, key, jws.WithProtectedHeaders(hdr)))

token = string(sig)

return
Expand Down Expand Up @@ -367,12 +380,11 @@ func convertHeaders(headers map[string]interface{}) (jws.Headers, error) {
return hdr, nil
}

func ecAlg(key *ecdsa.PrivateKey) (alg jwa.SignatureAlgorithm, err error) {
alg, err = ecAlgUsingPublicKey(key.PublicKey)
return
}

func ecAlgUsingPublicKey(key ecdsa.PublicKey) (alg jwa.SignatureAlgorithm, err error) {
if key.Curve == secp256k1.S256() {
return jwa.ES256K, nil
}
// Otherwise, assume it's a NIST curve
switch key.Params().BitSize {
case 256:
alg = jwa.ES256
Expand Down
8 changes: 4 additions & 4 deletions crypto/jwx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func TestCrypto_SignJWT(t *testing.T) {
client := createCrypto(t)

kid := "kid"
key, _ := client.New(audit.TestContext(), StringNamingFunc(kid))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))

t.Run("creates valid JWT", func(t *testing.T) {
tokenString, err := client.SignJWT(audit.TestContext(), map[string]interface{}{"iss": "nuts", "sub": "subject"}, nil, key)
Expand Down Expand Up @@ -197,7 +197,7 @@ func TestCrypto_SignJWS(t *testing.T) {
client := createCrypto(t)

kid := "kid"
key, _ := client.New(audit.TestContext(), StringNamingFunc(kid))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))

t.Run("creates valid JWS", func(t *testing.T) {
payload, _ := json.Marshal(map[string]interface{}{"iss": "nuts"})
Expand Down Expand Up @@ -244,7 +244,7 @@ func TestCrypto_SignJWS(t *testing.T) {
func TestCrypto_EncryptJWE(t *testing.T) {
client := createCrypto(t)

key, _ := client.New(audit.TestContext(), StringNamingFunc("did:nuts:1234#key-1"))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc("did:nuts:1234#key-1"))
public := key.Public()

headers := map[string]interface{}{"typ": "JWT", "kid": key.KID()}
Expand Down Expand Up @@ -327,7 +327,7 @@ func TestCrypto_DecryptJWE(t *testing.T) {
client := createCrypto(t)

kid := "did:nuts:1234#key-1"
key, _ := client.New(audit.TestContext(), StringNamingFunc(kid))
key, _ := client.New(audit.TestContext(), ECP256Key, StringNamingFunc(kid))

t.Run("decrypts valid JWE", func(t *testing.T) {
payload, _ := json.Marshal(map[string]interface{}{"iss": "nuts"})
Expand Down
16 changes: 8 additions & 8 deletions crypto/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
5 changes: 5 additions & 0 deletions crypto/test/private_invalid_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
1LGfzY1juuygBwYFK4EEACKhZANiAATAncgtph3ACHSPXWyvyYop/71skjBK6Q1T
UB6WFs6pusiUD1pYDMZ01IjBx/cMJaJP/VoyYl24Wbf2/mBnKt1lfDzYYVf0kFxT
dtTkGJrJAzbtHuysgU+GrEdjYSfhDKc=
-----END PRIVATE KEY-----
16 changes: 16 additions & 0 deletions crypto/test/private_rsa1024_pkcs8.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM2I2YtS2GfUakR0
HEoTSpV+kYDXf/uDbtUkfrD0v5PYziMdunUHKaXD+AEUncy9kV7Gt5p7ZmR/5DHo
NbgrBBjY+u7naHU7BUiT5SQieOF3oXGfuTmdnHX1ymKWY062x+IqMyy7vmhpFTIc
9EaJ1oKMUL9ewEmIscuGvmsy9aCPAgMBAAECgYA011ceo6jxYMIFYViYjschEg40
crL7pbnL4HsV4YaTayzsCEuUpMfHT0+mb3d2WNJT7IDtnYYglmTDk/CjraN6je0z
eDlKH+LsbvwoUN58obMkvjrX7195+xgEraFA+3hhoLPQjb2ux6cz13GR3RC1D/QH
VsN6BRJYkcoxe/G2IQJBAOCbCssPtMd7BAGpR+M4Y1zUkNLQyltmDUfaPuoyQpyi
3KlJ2yofiefGBBgpvS9BoJFBw0VOqLuxeCDnnGY09n8CQQDqQ2WOJtmf74OsIVe2
GcVvyFQVzH2g8IMboxw/iroTOdXijhtCAGfCuAJjNtksHD3/19t3X+Z4nAWahQAW
Gu3xAkEAhUn9Abx0X90U55d53dHcxX4v46ucKtlJEFbn9zuUZDgSEzSNJ1ZIFI9i
ZqR+bMjZbNpF859WauxKidxo6A6OKQJAB4u6NrT7p5IwfJfqWlxEJtCeHMGkfk2g
+3/qhgVy7vGa+Rw4toyKyxPgR8/ZePlD6fzK/fJh2xqzd4G3Of8OEQJAGhfkO+MI
TZvIL9/qm4UHyxjnJ+0zhD2H6IdbxpHKOByK5fmtIYE193tw+DdO7Es45Ns2c9y/
i7UkrL+HBMftgw==
-----END PRIVATE KEY-----
File renamed without changes.
5 changes: 5 additions & 0 deletions crypto/test/private_secp256k1_asn1.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MHQCAQEEIMgBmsXo5xfj6GMDUpXY9PWZpLV+NKIDfos1MYqYDZb9oAcGBSuBBAAK
oUQDQgAEDQGDPxSLLfrcnFM8HZLhyexQrr1BZkLx0awd1URadysAdqxeMY6irYn8
Hj2Qb7tlhDCFBfOeGKxZjNyf2fuSvQ==
-----END PRIVATE KEY-----
5 changes: 5 additions & 0 deletions crypto/test/private_secp256k1_asn1_echeader.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIMgBmsXo5xfj6GMDUpXY9PWZpLV+NKIDfos1MYqYDZb9oAcGBSuBBAAK
oUQDQgAEDQGDPxSLLfrcnFM8HZLhyexQrr1BZkLx0awd1URadysAdqxeMY6irYn8
Hj2Qb7tlhDCFBfOeGKxZjNyf2fuSvQ==
-----END EC PRIVATE KEY-----
6 changes: 6 additions & 0 deletions crypto/test/private_secp256r1_asn1.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIGkAgEBBDDkxTzFUFbIvBZWH6UlibvdXrhqYOzDb1lKTVSWX8P8OLbuu7Dzp9Ru
1LGfzY1juuygBwYFK4EEACKhZANiAATAncgtph3ACHSPXWyvyYop/71skjBK6Q1T
UB6WFs6pusiUD1pYDMZ01IjBx/cMJaJP/VoyYl24Wbf2/mBnKt1lfDzYYVf0kFxT
dtTkGJrJAzbtHuysgU+GrEdjYSfhDKc=
-----END PRIVATE KEY-----
File renamed without changes.
5 changes: 5 additions & 0 deletions crypto/test/private_secp256r1_pkcs8.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgam8mCp1lEcqIa7hy
iBy861mhscSeDP2W+KLm1tn2ZI2hRANCAAS+xlCLMK1ymtevTsDs44XghmOcp1Rf
9qmwJDj5YX5zNGDqvYVb0NVyTJ01tCOBBHNQY+UJYjoi+sTYP0Q3wnqB
-----END PRIVATE KEY-----
28 changes: 0 additions & 28 deletions crypto/test/sk.pem

This file was deleted.

Loading

0 comments on commit 0ee4a22

Please sign in to comment.