Skip to content

Commit 9efade3

Browse files
committed
Implement Dapr crypto scheme v1
Signed-off-by: ItalyPaleAle <[email protected]>
1 parent efcc1af commit 9efade3

18 files changed

+2328
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
Copyright 2023 The Dapr Authors
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package v1
15+
16+
import (
17+
"errors"
18+
"strconv"
19+
)
20+
21+
// Algorithm used to wrap the file key.
22+
type KeyAlgorithm string
23+
24+
const (
25+
KeyAlgorithmAESKW KeyAlgorithm = "AES-KW"
26+
KeyAlgorithmRSAOAEP256 KeyAlgorithm = "RSA-OAEP-256"
27+
28+
KeyAlgorithmAES KeyAlgorithm = "AES" // Alias for AES-KW
29+
KeyAlgorithmRSA KeyAlgorithm = "RSA" // Alias for RSA-OAEP-256
30+
)
31+
32+
// Validate the passed algorithm and resolves aliases.
33+
func (a KeyAlgorithm) Validate() (KeyAlgorithm, error) {
34+
switch a {
35+
// Valid algorithms, not aliased
36+
case KeyAlgorithmAESKW, KeyAlgorithmRSAOAEP256:
37+
return a, nil
38+
39+
// Alias for AES-KW
40+
case KeyAlgorithmAES:
41+
return KeyAlgorithmAESKW, nil
42+
43+
// Alias for RSA-OAEP-256
44+
case KeyAlgorithmRSA:
45+
return KeyAlgorithmRSAOAEP256, nil
46+
47+
default:
48+
return a, errors.New("algorithm " + string(a) + " is not supported")
49+
}
50+
}
51+
52+
// ID returns the numeric ID for the algorithm.
53+
func (a KeyAlgorithm) ID() int {
54+
switch a {
55+
case KeyAlgorithmAESKW, KeyAlgorithmAES:
56+
return 1
57+
case KeyAlgorithmRSAOAEP256, KeyAlgorithmRSA:
58+
return 2
59+
default:
60+
return 0
61+
}
62+
}
63+
64+
// NewKeyAlgorithmFromID returns a KeyAlgorithm from its ID.
65+
func NewKeyAlgorithmFromID(id int) (KeyAlgorithm, error) {
66+
switch id {
67+
case 1:
68+
return KeyAlgorithmAESKW, nil
69+
case 2:
70+
return KeyAlgorithmRSAOAEP256, nil
71+
default:
72+
return "", errors.New("algorithm ID " + strconv.Itoa(id) + " is not supported")
73+
}
74+
}
75+
76+
// MarhsalJSON implements json.Marshaler.
77+
func (a KeyAlgorithm) MarshalJSON() ([]byte, error) {
78+
return []byte(strconv.Itoa(a.ID())), nil
79+
}
80+
81+
// UnmarshalJSON implements json.Unmarshaler.
82+
func (a *KeyAlgorithm) UnmarshalJSON(dataB []byte) error {
83+
data := string(dataB)
84+
if data == "" || data == "null" {
85+
return errors.New("value is empty")
86+
}
87+
88+
id, err := strconv.Atoi(data)
89+
if err != nil {
90+
return errors.New("failed to parse value as number")
91+
}
92+
93+
newA, err := NewKeyAlgorithmFromID(id)
94+
if err != nil {
95+
return err
96+
}
97+
*a = newA
98+
return nil
99+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
Copyright 2023 The Dapr Authors
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package v1
15+
16+
import (
17+
"encoding/json"
18+
"testing"
19+
)
20+
21+
func TestKeyAlgorithmValidate(t *testing.T) {
22+
tests := []struct {
23+
name string
24+
a KeyAlgorithm
25+
want KeyAlgorithm
26+
wantErr bool
27+
}{
28+
{name: string(KeyAlgorithmAESKW), a: KeyAlgorithmAESKW, want: KeyAlgorithmAESKW},
29+
{name: string(KeyAlgorithmAES) + " alias", a: KeyAlgorithmAES, want: KeyAlgorithmAESKW},
30+
{name: string(KeyAlgorithmRSAOAEP256), a: KeyAlgorithmRSAOAEP256, want: KeyAlgorithmRSAOAEP256},
31+
{name: string(KeyAlgorithmRSA) + " alias", a: KeyAlgorithmRSA, want: KeyAlgorithmRSAOAEP256},
32+
{name: "invalid algorithm", a: "foo", wantErr: true},
33+
{name: "empty algorithm", a: "", wantErr: true},
34+
}
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
got, err := tt.a.Validate()
38+
if tt.wantErr {
39+
if err == nil {
40+
t.Errorf("KeyAlgorithm.Validate() error = %v, wantErr %v", err, tt.wantErr)
41+
}
42+
return
43+
} else if err != nil {
44+
t.Errorf("KeyAlgorithm.Validate() error = %v, wantErr %v", err, tt.wantErr)
45+
}
46+
if got != tt.want {
47+
t.Errorf("KeyAlgorithm.Validate() = %v, want %v", got, tt.want)
48+
}
49+
})
50+
}
51+
}
52+
53+
func TestKeyAlgorithmMarshalJSON(t *testing.T) {
54+
tests := []struct {
55+
name string
56+
a KeyAlgorithm
57+
want string
58+
wantErr bool
59+
}{
60+
{name: string(KeyAlgorithmAESKW), a: KeyAlgorithmAESKW, want: "1"},
61+
{name: string(KeyAlgorithmAES) + " alias", a: KeyAlgorithmAES, want: "1"},
62+
{name: string(KeyAlgorithmRSAOAEP256), a: KeyAlgorithmRSAOAEP256, want: "2"},
63+
{name: string(KeyAlgorithmRSA) + " alias", a: KeyAlgorithmRSA, want: "2"},
64+
{name: "invalid algorithm", a: "foo", want: "0"},
65+
{name: "empty algorithm", a: "", want: "0"},
66+
}
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
got, err := json.Marshal(tt.a)
70+
if (err != nil) != tt.wantErr {
71+
t.Errorf("KeyAlgorithm.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
72+
return
73+
}
74+
if string(got) != tt.want {
75+
t.Errorf("KeyAlgorithm.MarshalJSON() = %v, want %v", got, tt.want)
76+
}
77+
})
78+
}
79+
}
80+
81+
func TestKeyAlgorithmUnmarshalJSON(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
message string
85+
want KeyAlgorithm
86+
wantErr bool
87+
}{
88+
{name: string(KeyAlgorithmAESKW), message: "1", want: KeyAlgorithmAESKW},
89+
{name: string(KeyAlgorithmRSAOAEP256), message: "2", want: KeyAlgorithmRSAOAEP256},
90+
{name: "invalid ID", message: "99", wantErr: true},
91+
{name: "empty", message: "", wantErr: true},
92+
{name: "JSON null", message: "null", wantErr: true},
93+
{name: "JSON string", message: `"AES"`, wantErr: true},
94+
{name: "JSON object", message: `{"foo":1}`, wantErr: true},
95+
}
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
var a KeyAlgorithm
99+
err := json.Unmarshal([]byte(tt.message), &a)
100+
if (err != nil) != tt.wantErr {
101+
t.Errorf("KeyAlgorithm.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
102+
}
103+
if a != tt.want {
104+
t.Errorf("KeyAlgorithm.UnmarshalJSON() = %v, want %v", a, tt.want)
105+
}
106+
})
107+
}
108+
}

crypto/schemes/enc/v1/ciphers.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright 2023 The Dapr Authors
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package v1
15+
16+
import (
17+
"errors"
18+
"strconv"
19+
)
20+
21+
// Cipher used to encrypt the file.
22+
type Cipher string
23+
24+
const (
25+
CipherAESGCM Cipher = "AES-GCM"
26+
CipherChaCha20Poly1305 Cipher = "ChaCha20-Poly1305"
27+
)
28+
29+
// Validate the passed cipher and resolves aliases.
30+
func (a Cipher) Validate() (Cipher, error) {
31+
switch a {
32+
// Valid ciphers, not aliased
33+
case CipherAESGCM, CipherChaCha20Poly1305:
34+
return a, nil
35+
36+
default:
37+
return a, errors.New("cipher " + string(a) + " is not supported")
38+
}
39+
}
40+
41+
// ID returns the numeric ID for the cipher.
42+
func (a Cipher) ID() int {
43+
switch a {
44+
case CipherAESGCM:
45+
return 1
46+
case CipherChaCha20Poly1305:
47+
return 2
48+
default:
49+
return 0
50+
}
51+
}
52+
53+
// NewCipherFromID returns a Cipher from its ID.
54+
func NewCipherFromID(id int) (Cipher, error) {
55+
switch id {
56+
case 1:
57+
return CipherAESGCM, nil
58+
case 2:
59+
return CipherChaCha20Poly1305, nil
60+
default:
61+
return "", errors.New("cipher ID " + strconv.Itoa(id) + " is not supported")
62+
}
63+
}
64+
65+
// MarhsalJSON implements json.Marshaler.
66+
func (a Cipher) MarshalJSON() ([]byte, error) {
67+
return []byte(strconv.Itoa(a.ID())), nil
68+
}
69+
70+
// UnmarshalJSON implements json.Unmarshaler.
71+
func (a *Cipher) UnmarshalJSON(dataB []byte) error {
72+
data := string(dataB)
73+
if data == "" || data == "null" {
74+
return errors.New("value is empty")
75+
}
76+
77+
id, err := strconv.Atoi(data)
78+
if err != nil {
79+
return errors.New("failed to parse value as number")
80+
}
81+
82+
newA, err := NewCipherFromID(id)
83+
if err != nil {
84+
return err
85+
}
86+
*a = newA
87+
return nil
88+
}

0 commit comments

Comments
 (0)