Skip to content

Commit

Permalink
feat(#2): implemented ecdsa generate key. licence updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
stevencedro committed Apr 8, 2024
1 parent 035b878 commit 67a1a66
Show file tree
Hide file tree
Showing 4 changed files with 378 additions and 3 deletions.
225 changes: 225 additions & 0 deletions algorithms/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package rsa

// Package ecdsa implements ECDSA operations as specified in the algorithms at
// §23 https://www.w3.org/TR/WebCryptoAPI/#ecdsa
package ecdsa

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"

"github.com/armortal/webcrypto-go"
"github.com/armortal/webcrypto-go/util"
)

const (
name string = "ECDSA"

P256 string = "P-256"
P384 string = "P-384"
P521 string = "P-521"
)

// CryptoKey represents an ECDSA cryptography key.
type CryptoKey struct {
isPrivate bool
pub *ecdsa.PublicKey
priv *ecdsa.PrivateKey
alg *KeyAlgorithm
ext bool
usages []webcrypto.KeyUsage
}

func (c *CryptoKey) Algorithm() webcrypto.KeyAlgorithm {
return c.alg
}

func (c *CryptoKey) Extractable() bool {
return c.ext
}

func (c *CryptoKey) Type() webcrypto.KeyType {
if c.isPrivate {
return webcrypto.Private
}
return webcrypto.Public
}

func (c *CryptoKey) Usages() []webcrypto.KeyUsage {
return c.usages
}

type Algorithm struct {
KeyGenParams *KeyGenParams
}

func (a *Algorithm) GetName() string {
return name
}

// KeyGenParams represents the parameters available for key generation as specified at
// §23.4 https://www.w3.org/TR/WebCryptoAPI/#dfn-EcKeyGenParams
type KeyGenParams struct {
NamedCurve string
}

// KeyAlgorithm is the implementation of the dictionary specificationn at
// §23.5 (https://www.w3.org/TR/WebCryptoAPI/#dfn-EcKeyAlgorithm)
type KeyAlgorithm struct {
namedCurve string
}

func (k *KeyAlgorithm) NamedCurve() string {
return k.namedCurve
}

func (k *KeyAlgorithm) GetName() string {
return name
}

type SubtleCrypto struct{}

// Decrypt is not supported.
func (s *SubtleCrypto) Decrypt(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// DeriveBits is not supported.
func (s *SubtleCrypto) DeriveBits(algorithm webcrypto.Algorithm, baseKey webcrypto.CryptoKey, length uint64) ([]byte, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// DeriveKey is not supported.
func (s *SubtleCrypto) DeriveKey(algorithm webcrypto.Algorithm, baseKey webcrypto.CryptoKey, derivedKeyType webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// Digest is not supported.
func (s *SubtleCrypto) Digest(algorithm webcrypto.Algorithm, data []byte) ([]byte, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// Encrypt is not supported.
func (s *SubtleCrypto) Encrypt(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// ExportKey is not supported.
func (s *SubtleCrypto) ExportKey(format webcrypto.KeyFormat, key webcrypto.CryptoKey) (any, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// GenerateKey generates a new CryptoKeyPair as per 'Generate Key' operation at
// §23.7 (https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations).
func (s *SubtleCrypto) GenerateKey(algorithm webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (any, error) {
// ensure its the correct algorithm
alg, ok := algorithm.(*Algorithm)
if !ok {
return nil, webcrypto.NewError(webcrypto.ErrDataError, "algorithm must be *ecdsa.Algorithm")
}

// If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
if err := util.AreUsagesValid([]webcrypto.KeyUsage{
webcrypto.Sign,
webcrypto.Verify,
}, keyUsages); err != nil {
return nil, err
}

// validate the KeyGenParams and get the curve
if alg.KeyGenParams == nil {
return nil, webcrypto.NewError(webcrypto.ErrDataError, "KeyGenParams cannot be nil")
}

var crv elliptic.Curve
switch alg.KeyGenParams.NamedCurve {
case "P-256":
crv = elliptic.P256()
case "P-384":
crv = elliptic.P384()
case "P-521":
crv = elliptic.P521()
default:
return nil, webcrypto.NewError(webcrypto.ErrNotSupportedError, "named curve not supported")
}

// generate the key
key, err := ecdsa.GenerateKey(crv, rand.Reader)
if err != nil {
return nil, webcrypto.NewError(webcrypto.ErrOperationError, fmt.Sprintf("failed to generate ecdsa key - %s", err.Error()))
}

// create the key algorithm
kalg := &KeyAlgorithm{
namedCurve: alg.KeyGenParams.NamedCurve,
}

// create the crypto key for the public key
pub := &CryptoKey{
isPrivate: false,
pub: &key.PublicKey,
alg: kalg,
ext: true,
usages: []webcrypto.KeyUsage{
webcrypto.Verify,
},
}

// create the crypto key for the private key
priv := &CryptoKey{
isPrivate: true,
priv: key,
alg: kalg,
ext: extractable,
usages: []webcrypto.KeyUsage{
webcrypto.Sign,
},
}

return webcrypto.NewCryptoKeyPair(pub, priv), nil
}

// ImportKey is not supported.
func (s *SubtleCrypto) ImportKey(format webcrypto.KeyFormat, keyData any, algorithm webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// ImportKey is not supported.
func (s *SubtleCrypto) Sign(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// ImportKey is not supported.
func (s *SubtleCrypto) UnwrapKey(format webcrypto.KeyFormat,
wrappedKey []byte,
unwrappingKey webcrypto.CryptoKey,
unwrapAlgorithm webcrypto.Algorithm,
unwrappedKeyAlgorithm webcrypto.Algorithm,
extractable bool,
keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
return nil, webcrypto.ErrMethodNotSupported()
}

// ImportKey is not supported.
func (s *SubtleCrypto) Verify(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, signature []byte, data []byte) (bool, error) {
return false, webcrypto.ErrMethodNotSupported()
}

// ImportKey is not supported.
func (s *SubtleCrypto) WrapKey(format webcrypto.KeyFormat, key webcrypto.CryptoKey, wrappingKey webcrypto.CryptoKey, wrapAlgorithm webcrypto.Algorithm) (any, error) {
return nil, webcrypto.ErrMethodNotSupported()
}
119 changes: 119 additions & 0 deletions algorithms/ecdsa/ecdsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package rsa

// Package ecdsa implements ECDSA operations as specified in the algorithms at
// §23 https://www.w3.org/TR/WebCryptoAPI/#ecdsa
package ecdsa

import (
"errors"
"fmt"
"testing"

"github.com/armortal/webcrypto-go"
)

func Test_GenerateKey(t *testing.T) {
validate := func(key webcrypto.CryptoKey, keyType webcrypto.KeyType, curve string, extractable bool, usages []webcrypto.KeyUsage) error {
if key.Type() != keyType {
return errors.New("invalid key type")
}

alg, ok := key.Algorithm().(*KeyAlgorithm)
if !ok {
return errors.New("Algorithm() must be *KeyAlgorithm")
}
if alg.NamedCurve() != curve {
return errors.New("invalid named curve")
}
if key.Extractable() != extractable {
return errors.New("extractable mismatch")
}

if len(key.Usages()) != len(usages) {
return errors.New("invalid usages")
}

usages:
for _, exp := range usages {
for _, act := range key.Usages() {
if exp == act {
continue usages
}
}
return fmt.Errorf("usage '%s' not in crypto key", exp)
}
return nil
}

generateAndValidate := func(curve string, extractable bool, usages []webcrypto.KeyUsage) error {
k, err := new(SubtleCrypto).GenerateKey(&Algorithm{
KeyGenParams: &KeyGenParams{
NamedCurve: curve,
},
}, extractable, usages...)
if err != nil {
t.Error(err)
}
ckp := k.(webcrypto.CryptoKeyPair)

if err := validate(ckp.PublicKey(), webcrypto.Public, curve, extractable, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
return err
}

if err := validate(ckp.PrivateKey(), webcrypto.Private, curve, extractable, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
return err
}

return nil
}

t.Run("generate a valid P-256 key", func(t *testing.T) {
if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
t.Error(err)
}

if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
t.Error(err)
}

if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
t.Error(err)
}
})

t.Run("generate a valid P-384 key", func(t *testing.T) {
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
t.Error(err)
}
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
t.Error(err)
}
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
t.Error(err)
}
})

t.Run("generate a valid P-521 key", func(t *testing.T) {
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
t.Error(err)
}
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
t.Error(err)
}
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
t.Error(err)
}
})
}
35 changes: 33 additions & 2 deletions crypto_key.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 ARMORTAL TECHNOLOGIES PTY LTD
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,37 @@ type CryptoKey interface {
// and private (PrivateKey) keys.
// See §17. (https://w3c.github.io/webcrypto/#keypair)
type CryptoKeyPair interface {
PrivateKey() CryptoKey
PublicKey() CryptoKey
PrivateKey() CryptoKey
}

// NewCryptoKeyPair creates a new key pair from the public and private keys.
// This function shouldn't be called from your application. It is called from the
// implementing algorithms when returning key pairs from the GenerateKey function.
// Use Subtle().GenerateKey() to get your key pairs.
func NewCryptoKeyPair(public CryptoKey, private CryptoKey) CryptoKeyPair {
if public == nil || private == nil {
panic("webcrypto: both public and private keys are required")
}
return &cryptoKeyPair{
pub: public,
priv: private,
}
}

// cryptoKeyPair implements CryptoKeyPair. It can be created with NewCryptoKeyPair()
// for algorithms that don't need custom implementations.
type cryptoKeyPair struct {
pub CryptoKey
priv CryptoKey
}

// PrivateKey returns the key pair's private key.
func (p *cryptoKeyPair) PrivateKey() CryptoKey {
return p.priv
}

// PublicKey returns the key pair's public key.
func (p *cryptoKeyPair) PublicKey() CryptoKey {
return p.pub
}
2 changes: 1 addition & 1 deletion subtle.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 ARMORTAL TECHNOLOGIES PTY LTD
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down

0 comments on commit 67a1a66

Please sign in to comment.