Skip to content

Commit 67a1a66

Browse files
committed
feat(#2): implemented ecdsa generate key. licence updates.
1 parent 035b878 commit 67a1a66

File tree

4 files changed

+378
-3
lines changed

4 files changed

+378
-3
lines changed

algorithms/ecdsa/ecdsa.go

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD
2+
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.package rsa
14+
15+
// Package ecdsa implements ECDSA operations as specified in the algorithms at
16+
// §23 https://www.w3.org/TR/WebCryptoAPI/#ecdsa
17+
package ecdsa
18+
19+
import (
20+
"crypto/ecdsa"
21+
"crypto/elliptic"
22+
"crypto/rand"
23+
"fmt"
24+
25+
"github.com/armortal/webcrypto-go"
26+
"github.com/armortal/webcrypto-go/util"
27+
)
28+
29+
const (
30+
name string = "ECDSA"
31+
32+
P256 string = "P-256"
33+
P384 string = "P-384"
34+
P521 string = "P-521"
35+
)
36+
37+
// CryptoKey represents an ECDSA cryptography key.
38+
type CryptoKey struct {
39+
isPrivate bool
40+
pub *ecdsa.PublicKey
41+
priv *ecdsa.PrivateKey
42+
alg *KeyAlgorithm
43+
ext bool
44+
usages []webcrypto.KeyUsage
45+
}
46+
47+
func (c *CryptoKey) Algorithm() webcrypto.KeyAlgorithm {
48+
return c.alg
49+
}
50+
51+
func (c *CryptoKey) Extractable() bool {
52+
return c.ext
53+
}
54+
55+
func (c *CryptoKey) Type() webcrypto.KeyType {
56+
if c.isPrivate {
57+
return webcrypto.Private
58+
}
59+
return webcrypto.Public
60+
}
61+
62+
func (c *CryptoKey) Usages() []webcrypto.KeyUsage {
63+
return c.usages
64+
}
65+
66+
type Algorithm struct {
67+
KeyGenParams *KeyGenParams
68+
}
69+
70+
func (a *Algorithm) GetName() string {
71+
return name
72+
}
73+
74+
// KeyGenParams represents the parameters available for key generation as specified at
75+
// §23.4 https://www.w3.org/TR/WebCryptoAPI/#dfn-EcKeyGenParams
76+
type KeyGenParams struct {
77+
NamedCurve string
78+
}
79+
80+
// KeyAlgorithm is the implementation of the dictionary specificationn at
81+
// §23.5 (https://www.w3.org/TR/WebCryptoAPI/#dfn-EcKeyAlgorithm)
82+
type KeyAlgorithm struct {
83+
namedCurve string
84+
}
85+
86+
func (k *KeyAlgorithm) NamedCurve() string {
87+
return k.namedCurve
88+
}
89+
90+
func (k *KeyAlgorithm) GetName() string {
91+
return name
92+
}
93+
94+
type SubtleCrypto struct{}
95+
96+
// Decrypt is not supported.
97+
func (s *SubtleCrypto) Decrypt(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
98+
return nil, webcrypto.ErrMethodNotSupported()
99+
}
100+
101+
// DeriveBits is not supported.
102+
func (s *SubtleCrypto) DeriveBits(algorithm webcrypto.Algorithm, baseKey webcrypto.CryptoKey, length uint64) ([]byte, error) {
103+
return nil, webcrypto.ErrMethodNotSupported()
104+
}
105+
106+
// DeriveKey is not supported.
107+
func (s *SubtleCrypto) DeriveKey(algorithm webcrypto.Algorithm, baseKey webcrypto.CryptoKey, derivedKeyType webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
108+
return nil, webcrypto.ErrMethodNotSupported()
109+
}
110+
111+
// Digest is not supported.
112+
func (s *SubtleCrypto) Digest(algorithm webcrypto.Algorithm, data []byte) ([]byte, error) {
113+
return nil, webcrypto.ErrMethodNotSupported()
114+
}
115+
116+
// Encrypt is not supported.
117+
func (s *SubtleCrypto) Encrypt(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
118+
return nil, webcrypto.ErrMethodNotSupported()
119+
}
120+
121+
// ExportKey is not supported.
122+
func (s *SubtleCrypto) ExportKey(format webcrypto.KeyFormat, key webcrypto.CryptoKey) (any, error) {
123+
return nil, webcrypto.ErrMethodNotSupported()
124+
}
125+
126+
// GenerateKey generates a new CryptoKeyPair as per 'Generate Key' operation at
127+
// §23.7 (https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations).
128+
func (s *SubtleCrypto) GenerateKey(algorithm webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (any, error) {
129+
// ensure its the correct algorithm
130+
alg, ok := algorithm.(*Algorithm)
131+
if !ok {
132+
return nil, webcrypto.NewError(webcrypto.ErrDataError, "algorithm must be *ecdsa.Algorithm")
133+
}
134+
135+
// If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
136+
if err := util.AreUsagesValid([]webcrypto.KeyUsage{
137+
webcrypto.Sign,
138+
webcrypto.Verify,
139+
}, keyUsages); err != nil {
140+
return nil, err
141+
}
142+
143+
// validate the KeyGenParams and get the curve
144+
if alg.KeyGenParams == nil {
145+
return nil, webcrypto.NewError(webcrypto.ErrDataError, "KeyGenParams cannot be nil")
146+
}
147+
148+
var crv elliptic.Curve
149+
switch alg.KeyGenParams.NamedCurve {
150+
case "P-256":
151+
crv = elliptic.P256()
152+
case "P-384":
153+
crv = elliptic.P384()
154+
case "P-521":
155+
crv = elliptic.P521()
156+
default:
157+
return nil, webcrypto.NewError(webcrypto.ErrNotSupportedError, "named curve not supported")
158+
}
159+
160+
// generate the key
161+
key, err := ecdsa.GenerateKey(crv, rand.Reader)
162+
if err != nil {
163+
return nil, webcrypto.NewError(webcrypto.ErrOperationError, fmt.Sprintf("failed to generate ecdsa key - %s", err.Error()))
164+
}
165+
166+
// create the key algorithm
167+
kalg := &KeyAlgorithm{
168+
namedCurve: alg.KeyGenParams.NamedCurve,
169+
}
170+
171+
// create the crypto key for the public key
172+
pub := &CryptoKey{
173+
isPrivate: false,
174+
pub: &key.PublicKey,
175+
alg: kalg,
176+
ext: true,
177+
usages: []webcrypto.KeyUsage{
178+
webcrypto.Verify,
179+
},
180+
}
181+
182+
// create the crypto key for the private key
183+
priv := &CryptoKey{
184+
isPrivate: true,
185+
priv: key,
186+
alg: kalg,
187+
ext: extractable,
188+
usages: []webcrypto.KeyUsage{
189+
webcrypto.Sign,
190+
},
191+
}
192+
193+
return webcrypto.NewCryptoKeyPair(pub, priv), nil
194+
}
195+
196+
// ImportKey is not supported.
197+
func (s *SubtleCrypto) ImportKey(format webcrypto.KeyFormat, keyData any, algorithm webcrypto.Algorithm, extractable bool, keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
198+
return nil, webcrypto.ErrMethodNotSupported()
199+
}
200+
201+
// ImportKey is not supported.
202+
func (s *SubtleCrypto) Sign(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, data []byte) ([]byte, error) {
203+
return nil, webcrypto.ErrMethodNotSupported()
204+
}
205+
206+
// ImportKey is not supported.
207+
func (s *SubtleCrypto) UnwrapKey(format webcrypto.KeyFormat,
208+
wrappedKey []byte,
209+
unwrappingKey webcrypto.CryptoKey,
210+
unwrapAlgorithm webcrypto.Algorithm,
211+
unwrappedKeyAlgorithm webcrypto.Algorithm,
212+
extractable bool,
213+
keyUsages ...webcrypto.KeyUsage) (webcrypto.CryptoKey, error) {
214+
return nil, webcrypto.ErrMethodNotSupported()
215+
}
216+
217+
// ImportKey is not supported.
218+
func (s *SubtleCrypto) Verify(algorithm webcrypto.Algorithm, key webcrypto.CryptoKey, signature []byte, data []byte) (bool, error) {
219+
return false, webcrypto.ErrMethodNotSupported()
220+
}
221+
222+
// ImportKey is not supported.
223+
func (s *SubtleCrypto) WrapKey(format webcrypto.KeyFormat, key webcrypto.CryptoKey, wrappingKey webcrypto.CryptoKey, wrapAlgorithm webcrypto.Algorithm) (any, error) {
224+
return nil, webcrypto.ErrMethodNotSupported()
225+
}

algorithms/ecdsa/ecdsa_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD
2+
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.package rsa
14+
15+
// Package ecdsa implements ECDSA operations as specified in the algorithms at
16+
// §23 https://www.w3.org/TR/WebCryptoAPI/#ecdsa
17+
package ecdsa
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"testing"
23+
24+
"github.com/armortal/webcrypto-go"
25+
)
26+
27+
func Test_GenerateKey(t *testing.T) {
28+
validate := func(key webcrypto.CryptoKey, keyType webcrypto.KeyType, curve string, extractable bool, usages []webcrypto.KeyUsage) error {
29+
if key.Type() != keyType {
30+
return errors.New("invalid key type")
31+
}
32+
33+
alg, ok := key.Algorithm().(*KeyAlgorithm)
34+
if !ok {
35+
return errors.New("Algorithm() must be *KeyAlgorithm")
36+
}
37+
if alg.NamedCurve() != curve {
38+
return errors.New("invalid named curve")
39+
}
40+
if key.Extractable() != extractable {
41+
return errors.New("extractable mismatch")
42+
}
43+
44+
if len(key.Usages()) != len(usages) {
45+
return errors.New("invalid usages")
46+
}
47+
48+
usages:
49+
for _, exp := range usages {
50+
for _, act := range key.Usages() {
51+
if exp == act {
52+
continue usages
53+
}
54+
}
55+
return fmt.Errorf("usage '%s' not in crypto key", exp)
56+
}
57+
return nil
58+
}
59+
60+
generateAndValidate := func(curve string, extractable bool, usages []webcrypto.KeyUsage) error {
61+
k, err := new(SubtleCrypto).GenerateKey(&Algorithm{
62+
KeyGenParams: &KeyGenParams{
63+
NamedCurve: curve,
64+
},
65+
}, extractable, usages...)
66+
if err != nil {
67+
t.Error(err)
68+
}
69+
ckp := k.(webcrypto.CryptoKeyPair)
70+
71+
if err := validate(ckp.PublicKey(), webcrypto.Public, curve, extractable, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
72+
return err
73+
}
74+
75+
if err := validate(ckp.PrivateKey(), webcrypto.Private, curve, extractable, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
76+
return err
77+
}
78+
79+
return nil
80+
}
81+
82+
t.Run("generate a valid P-256 key", func(t *testing.T) {
83+
if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
84+
t.Error(err)
85+
}
86+
87+
if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
88+
t.Error(err)
89+
}
90+
91+
if err := generateAndValidate(P256, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
92+
t.Error(err)
93+
}
94+
})
95+
96+
t.Run("generate a valid P-384 key", func(t *testing.T) {
97+
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
98+
t.Error(err)
99+
}
100+
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
101+
t.Error(err)
102+
}
103+
if err := generateAndValidate(P384, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
104+
t.Error(err)
105+
}
106+
})
107+
108+
t.Run("generate a valid P-521 key", func(t *testing.T) {
109+
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Sign}); err != nil {
110+
t.Error(err)
111+
}
112+
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Sign, webcrypto.Verify}); err != nil {
113+
t.Error(err)
114+
}
115+
if err := generateAndValidate(P521, true, []webcrypto.KeyUsage{webcrypto.Verify}); err != nil {
116+
t.Error(err)
117+
}
118+
})
119+
}

crypto_key.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 ARMORTAL TECHNOLOGIES PTY LTD
1+
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD
22

33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -57,6 +57,37 @@ type CryptoKey interface {
5757
// and private (PrivateKey) keys.
5858
// See §17. (https://w3c.github.io/webcrypto/#keypair)
5959
type CryptoKeyPair interface {
60-
PrivateKey() CryptoKey
6160
PublicKey() CryptoKey
61+
PrivateKey() CryptoKey
62+
}
63+
64+
// NewCryptoKeyPair creates a new key pair from the public and private keys.
65+
// This function shouldn't be called from your application. It is called from the
66+
// implementing algorithms when returning key pairs from the GenerateKey function.
67+
// Use Subtle().GenerateKey() to get your key pairs.
68+
func NewCryptoKeyPair(public CryptoKey, private CryptoKey) CryptoKeyPair {
69+
if public == nil || private == nil {
70+
panic("webcrypto: both public and private keys are required")
71+
}
72+
return &cryptoKeyPair{
73+
pub: public,
74+
priv: private,
75+
}
76+
}
77+
78+
// cryptoKeyPair implements CryptoKeyPair. It can be created with NewCryptoKeyPair()
79+
// for algorithms that don't need custom implementations.
80+
type cryptoKeyPair struct {
81+
pub CryptoKey
82+
priv CryptoKey
83+
}
84+
85+
// PrivateKey returns the key pair's private key.
86+
func (p *cryptoKeyPair) PrivateKey() CryptoKey {
87+
return p.priv
88+
}
89+
90+
// PublicKey returns the key pair's public key.
91+
func (p *cryptoKeyPair) PublicKey() CryptoKey {
92+
return p.pub
6293
}

subtle.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 ARMORTAL TECHNOLOGIES PTY LTD
1+
// Copyright 2023-2024 ARMORTAL TECHNOLOGIES PTY LTD
22

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

0 commit comments

Comments
 (0)