Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added v3 license with shuffled salsa cipher
- Loading branch information
Showing
6 changed files
with
425 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/********************************************************************************** | ||
* Copyright (c) 2009-2019 Misakai Ltd. | ||
* This program is free software: you can redistribute it and/or modify it under the | ||
* terms of the GNU Affero General Public License as published by the Free Software | ||
* Foundation, either version 3 of the License, or(at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License along | ||
* with this program. If not, see<http://www.gnu.org/licenses/>. | ||
************************************************************************************/ | ||
|
||
package cipher | ||
|
||
import ( | ||
"encoding/base64" | ||
"errors" | ||
|
||
"github.com/emitter-io/emitter/internal/security" | ||
"golang.org/x/crypto/salsa20/salsa" | ||
) | ||
|
||
// Shuffle represents a security cipher which can encrypt/decrypt security keys. | ||
type Shuffle struct { | ||
key [32]byte | ||
nonce [16]byte | ||
} | ||
|
||
// NewShuffle creates a new shuffled salsa cipher. | ||
func NewShuffle(key, nonce []byte) (*Shuffle, error) { | ||
if len(key) != 32 || len(nonce) != 16 { | ||
return nil, errors.New("shuffled: invalid cryptographic key") | ||
} | ||
|
||
cipher := new(Shuffle) | ||
copy(cipher.key[:], key) | ||
copy(cipher.nonce[:], nonce) | ||
return cipher, nil | ||
} | ||
|
||
// EncryptKey encrypts the key and return a base-64 encoded string. | ||
func (c *Shuffle) EncryptKey(k security.Key) (string, error) { | ||
buffer := make([]byte, 24) | ||
copy(buffer[:], k) | ||
|
||
err := c.crypt(buffer) | ||
return base64.RawURLEncoding.EncodeToString(buffer), err | ||
} | ||
|
||
// DecryptKey decrypts the security key from a base64 encoded string. | ||
func (c *Shuffle) DecryptKey(buffer []byte) (security.Key, error) { | ||
if len(buffer) != 32 { | ||
return nil, errors.New("cipher: the key provided is not valid") | ||
} | ||
|
||
// Warning: we do a base64 decode in the same underlying buffer, to save up | ||
// on memory allocations. Keep in mind that the previous data will be lost. | ||
n, err := decodeKey(buffer, buffer) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// We now need to resize the slice, since we changed it. | ||
buffer = buffer[:n] | ||
c.crypt(buffer) | ||
|
||
// Return the key on the decrypted buffer. | ||
return security.Key(buffer), nil | ||
} | ||
|
||
// crypt encrypts or decrypts the data and shuffles (recommended). | ||
func (c *Shuffle) crypt(data []byte) error { | ||
buffer := data[2:] | ||
salt := data[0:2] | ||
|
||
// Apply the salt to nonce | ||
var nonce [16]byte | ||
for i := 0; i < 16; i += 2 { | ||
nonce[i] = salt[0] ^ c.nonce[i] | ||
nonce[i+1] = salt[1] ^ c.nonce[i+1] | ||
} | ||
|
||
var subKey [32]byte | ||
salsa.HSalsa20(&subKey, &nonce, &c.key, &salsa.Sigma) | ||
salsa.XORKeyStream(buffer, buffer, &nonce, &subKey) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/********************************************************************************** | ||
* Copyright (c) 2009-2019 Misakai Ltd. | ||
* This program is free software: you can redistribute it and/or modify it under the | ||
* terms of the GNU Affero General Public License as published by the Free Software | ||
* Foundation, either version 3 of the License, or(at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License along | ||
* with this program. If not, see<http://www.gnu.org/licenses/>. | ||
************************************************************************************/ | ||
|
||
package cipher | ||
|
||
import ( | ||
"crypto/rand" | ||
"testing" | ||
"time" | ||
|
||
"github.com/emitter-io/emitter/internal/security" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type x struct { | ||
test string | ||
} | ||
|
||
// Benchmark_Shuffled_EncryptKey-8 4477147 263 ns/op 64 B/op 2 allocs/op | ||
func Benchmark_Shuffled_EncryptKey(b *testing.B) { | ||
cipher := new(Shuffle) | ||
key := "A-dOBQDuXhqoFz-GZZdbpSFCtzmFl7Ng" | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
_, _ = cipher.EncryptKey([]byte(key)) | ||
} | ||
} | ||
|
||
func Test_Shuffled(t *testing.T) { | ||
cipher := new(Shuffle) | ||
key := security.Key(make([]byte, 24)) | ||
key.SetSalt(999) | ||
key.SetMaster(2) | ||
key.SetContract(123) | ||
key.SetSignature(777) | ||
key.SetPermissions(security.AllowReadWrite) | ||
key.SetTarget("a/b/c/") | ||
key.SetExpires(time.Unix(1497683272, 0).UTC()) | ||
|
||
encoded, err := cipher.EncryptKey(key) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "A-dOBQDuXhqoFz-GZZdbpSFCtzmFl7Ng", encoded) | ||
|
||
decoded, err := cipher.DecryptKey([]byte(encoded)) | ||
assert.NoError(t, err) | ||
assert.Equal(t, key, decoded) | ||
} | ||
|
||
// Benchmark_Shuffled_DecryptKey-8 4857697 245 ns/op 0 B/op 0 allocs/op | ||
func Benchmark_Shuffled_DecryptKey(b *testing.B) { | ||
cipher := new(Shuffle) | ||
key := "A-dOBQDuXhqoFz-GZZdbpSFCtzmFl7Ng" | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
_, _ = cipher.DecryptKey([]byte(key)) | ||
} | ||
} | ||
|
||
func Test_Shuffled_Errors(t *testing.T) { | ||
cipher := new(Shuffle) | ||
tests := []struct { | ||
key string | ||
err bool | ||
}{ | ||
|
||
{ | ||
key: "", | ||
err: true, | ||
}, | ||
{ | ||
key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*", | ||
err: true, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
_, err := cipher.DecryptKey([]byte(tc.key)) | ||
assert.Equal(t, tc.err, err != nil, tc.key) | ||
|
||
} | ||
} | ||
|
||
func TestNewShuffled(t *testing.T) { | ||
|
||
// Happy path | ||
{ | ||
c, err := NewShuffle(make([]byte, 32), make([]byte, 16)) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, c) | ||
} | ||
|
||
// Error case | ||
{ | ||
c, err := NewShuffle(nil, nil) | ||
assert.Error(t, err) | ||
assert.Nil(t, c) | ||
} | ||
|
||
} | ||
|
||
func TestShuffled_Entropy(t *testing.T) { | ||
cryptoKey := make([]byte, 32) | ||
rand.Read(cryptoKey) | ||
|
||
nonce := make([]byte, 16) | ||
rand.Read(nonce) | ||
|
||
c, err := NewShuffle(cryptoKey, nonce) | ||
|
||
key1 := makeKey(111) | ||
key2 := makeKey(333) | ||
|
||
k1, err := c.EncryptKey(key1) | ||
assert.NoError(t, err) | ||
|
||
k2, err := c.EncryptKey(key2) | ||
assert.NoError(t, err) | ||
|
||
var diff int | ||
for i := range k1 { | ||
if k1[i] != k2[i] { | ||
diff++ | ||
} | ||
} | ||
|
||
assert.NotEqual(t, k1, k2) | ||
assert.Greater(t, diff, 20) | ||
} | ||
|
||
func printKey(key security.Key) { | ||
println(key.Salt(), key.Contract(), key.Signature(), key.Expires().String()) | ||
} | ||
|
||
func makeKey(salt int) security.Key { | ||
key := security.Key(make([]byte, 24)) | ||
key.SetSalt(uint16(salt)) | ||
key.SetMaster(2) | ||
key.SetContract(123) | ||
key.SetSignature(777) | ||
key.SetPermissions(security.AllowReadWrite) | ||
key.SetTarget("a/b/c/") | ||
key.SetExpires(time.Unix(1497683272, 0).UTC()) | ||
return key | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/********************************************************************************** | ||
* Copyright (c) 2009-2019 Misakai Ltd. | ||
* This program is free software: you can redistribute it and/or modify it under the | ||
* terms of the GNU Affero General Public License as published by the Free Software | ||
* Foundation, either version 3 of the License, or(at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License along | ||
* with this program. If not, see<http://www.gnu.org/licenses/>. | ||
************************************************************************************/ | ||
|
||
package license | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/base64" | ||
"math" | ||
"math/big" | ||
|
||
"github.com/emitter-io/emitter/internal/security" | ||
"github.com/emitter-io/emitter/internal/security/cipher" | ||
"github.com/golang/snappy" | ||
"github.com/kelindar/binary" | ||
) | ||
|
||
// V3 represents a v3 license. | ||
type V3 struct { | ||
EncryptionKey []byte // Gets or sets the encryption key. | ||
EncryptionSalt []byte // Gets or sets the encryption key. | ||
User uint32 // Gets or sets the contract id. | ||
Sign uint32 // Gets or sets the signature of the contract. | ||
Index uint32 // Gets or sets the current master. | ||
} | ||
|
||
// NewV3 generates a new v2 license. | ||
func NewV3() *V3 { | ||
return &V3{ | ||
EncryptionKey: randN(32), | ||
EncryptionSalt: randN(16), | ||
User: uint32(be.Uint32(randN(4))), | ||
Sign: uint32(be.Uint32(randN(4))), | ||
Index: 1, | ||
} | ||
} | ||
|
||
// parseV3 decodes the license and verifies it. | ||
func parseV3(data string) (*V3, error) { | ||
|
||
// Decode from base64 first | ||
raw, err := base64.RawURLEncoding.DecodeString(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Uncompress the bytes | ||
raw, err = snappy.Decode(nil, raw) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Unmarshal the license | ||
var license V3 | ||
err = binary.Unmarshal(raw, &license) | ||
return &license, err | ||
} | ||
|
||
// Cipher creates a new cipher for the licence | ||
func (l *V3) Cipher() (Cipher, error) { | ||
return cipher.NewShuffle(l.EncryptionKey, l.EncryptionSalt) | ||
} | ||
|
||
// String converts the license to string. | ||
func (l *V3) String() string { | ||
encoded, _ := binary.Marshal(l) | ||
encoded = snappy.Encode(nil, encoded) | ||
|
||
return base64.RawURLEncoding.EncodeToString(encoded) + ":3" | ||
} | ||
|
||
// Contract retuns the contract ID of the license. | ||
func (l *V3) Contract() uint32 { | ||
return l.User | ||
} | ||
|
||
// Signature returns the signature of the license. | ||
func (l *V3) Signature() uint32 { | ||
return l.Sign | ||
} | ||
|
||
// Master returns the secret key index. | ||
func (l *V3) Master() uint32 { | ||
return l.Index | ||
} | ||
|
||
// NewMasterKey generates a new master key. | ||
func (l *V3) NewMasterKey(id uint16) (key security.Key, err error) { | ||
var n *big.Int | ||
if n, err = rand.Int(rand.Reader, big.NewInt(math.MaxInt16)); err == nil { | ||
key = security.Key(make([]byte, 24)) | ||
key.SetSalt(uint16(n.Uint64())) | ||
key.SetMaster(id) | ||
key.SetContract(l.User) | ||
key.SetSignature(l.Sign) | ||
key.SetPermissions(security.AllowMaster) | ||
} | ||
return | ||
} |
Oops, something went wrong.