implement signed pipe and add blowfish cipher to supported suites
Jeromy committed
Oct 26, 2014 at 21:44 UTC
01c0c6e169aba8f5f8d52db27a32987bfd446100
7 files changed
+350
-11
crypto/key.go
+13
-1
@@ -50,6 +50,8 @@ type PrivKey interface {
50
51
// Generate a secret string of bytes
52
GenSecret() []byte
53
+
54
+ Unencrypt(b []byte) ([]byte, error)
55
}
56
57
type PubKey interface {
@@ -57,6 +59,8 @@ type PubKey interface {
59
60
// Verify that 'sig' is the signed hash of 'data'
61
Verify(data []byte, sig []byte) (bool, error)
62
+
63
+ Encrypt(data []byte) ([]byte, error)
64
}
65
66
// Given a public key, generates the shared key.
@@ -126,14 +130,20 @@ func GenerateEKeyPair(curveName string) ([]byte, GenSharedKey, error) {
130
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
131
func KeyStretcher(cmp int, cipherType string, hashType string, secret []byte) ([]byte, []byte, []byte, []byte, []byte, []byte) {
132
var cipherKeySize int
133
+ var ivSize int
134
switch cipherType {
135
case "AES-128":
136
+ ivSize = 16
137
cipherKeySize = 16
138
case "AES-256":
139
+ ivSize = 16
140
+ cipherKeySize = 32
141
+ case "Blowfish":
142
+ ivSize = 8
143
+ // Note: 24 arbitrarily selected, needs more thought
144
cipherKeySize = 32
145
}
146
136
- ivSize := 16
147
hmacKeySize := 20
148
149
seed := []byte("key expansion")
@@ -149,6 +159,8 @@ func KeyStretcher(cmp int, cipherType string, hashType string, secret []byte) ([
159
h = sha256.New
160
case "SHA512":
161
h = sha512.New
162
+ default:
163
+ panic("Unrecognized hash function, programmer error?")
164
}
165
166
m := hmac.New(h, secret)
crypto/rsa.go
+8
@@ -43,6 +43,10 @@ func (pk *RsaPublicKey) Bytes() ([]byte, error) {
43
return proto.Marshal(pbmes)
44
}
45
46
+func (pk *RsaPublicKey) Encrypt(b []byte) ([]byte, error) {
47
+ return rsa.EncryptPKCS1v15(rand.Reader, pk.k, b)
48
+}
49
+
50
// Equals checks whether this key is equal to another
51
func (pk *RsaPublicKey) Equals(k Key) bool {
52
return KeyEqual(pk, k)
@@ -67,6 +71,10 @@ func (sk *RsaPrivateKey) GetPublic() PubKey {
71
return &RsaPublicKey{&sk.k.PublicKey}
72
}
73
74
+func (sk *RsaPrivateKey) Unencrypt(b []byte) ([]byte, error) {
75
+ return rsa.DecryptPKCS1v15(rand.Reader, sk.k, b)
76
+}
77
+
78
func (sk *RsaPrivateKey) Bytes() ([]byte, error) {
79
b := x509.MarshalPKCS1PrivateKey(sk.k)
80
pbmes := new(pb.PrivateKey)
crypto/spipe/handshake.go
+29
-7
@@ -8,6 +8,7 @@ import (
8
"fmt"
9
"strings"
10
11
+ bfish "code.google.com/p/go.crypto/blowfish"
12
"crypto/aes"
13
"crypto/cipher"
14
"crypto/hmac"
@@ -31,7 +32,7 @@ var log = u.Logger("handshake")
32
var SupportedExchanges = "P-256,P-224,P-384,P-521"
33
34
// List of supported Ciphers
34
-var SupportedCiphers = "AES-256,AES-128"
35
+var SupportedCiphers = "AES-256,AES-128,Blowfish"
36
37
// List of supported Hashes
38
var SupportedHashes = "SHA256,SHA512,SHA1"
@@ -185,8 +186,8 @@ func (s *SecurePipe) handshake() error {
186
cmp := bytes.Compare(myPubKey, proposeResp.GetPubkey())
187
mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret)
188
188
- go s.handleSecureIn(hashType, tIV, tCKey, tMKey)
189
- go s.handleSecureOut(hashType, mIV, mCKey, mMKey)
189
+ go s.handleSecureIn(hashType, cipherType, tIV, tCKey, tMKey)
190
+ go s.handleSecureOut(hashType, cipherType, mIV, mCKey, mMKey)
191
192
finished := []byte("Finished")
193
@@ -224,8 +225,24 @@ func makeMac(hashType string, key []byte) (hash.Hash, int) {
225
}
226
}
227
227
-func (s *SecurePipe) handleSecureIn(hashType string, tIV, tCKey, tMKey []byte) {
228
- theirBlock, _ := aes.NewCipher(tCKey)
228
+func makeCipher(cipherType string, CKey []byte) (cipher.Block, error) {
229
+ switch cipherType {
230
+ case "AES-128", "AES-256":
231
+ return aes.NewCipher(CKey)
232
+ case "Blowfish":
233
+ return bfish.NewCipher(CKey)
234
+ default:
235
+ return nil, fmt.Errorf("Unrecognized cipher string: %s", cipherType)
236
+ }
237
+}
238
+
239
+func (s *SecurePipe) handleSecureIn(hashType, cipherType string, tIV, tCKey, tMKey []byte) {
240
+ theirBlock, err := makeCipher(cipherType, tCKey)
241
+ if err != nil {
242
+ log.Criticalf("Invalid Cipher: %s", err)
243
+ s.cancel()
244
+ return
245
+ }
246
theirCipher := cipher.NewCTR(theirBlock, tIV)
247
248
theirMac, macSize := makeMac(hashType, tMKey)
@@ -269,8 +286,13 @@ func (s *SecurePipe) handleSecureIn(hashType string, tIV, tCKey, tMKey []byte) {
286
}
287
}
288
272
-func (s *SecurePipe) handleSecureOut(hashType string, mIV, mCKey, mMKey []byte) {
273
- myBlock, _ := aes.NewCipher(mCKey)
289
+func (s *SecurePipe) handleSecureOut(hashType, cipherType string, mIV, mCKey, mMKey []byte) {
290
+ myBlock, err := makeCipher(cipherType, mCKey)
291
+ if err != nil {
292
+ log.Criticalf("Invalid Cipher: %s", err)
293
+ s.cancel()
294
+ return
295
+ }
296
myCipher := cipher.NewCTR(myBlock, mIV)
297
298
myMac, macSize := makeMac(hashType, mMKey)
crypto/spipe/internal/pb/spipe.pb.go
+27
-2
@@ -1,4 +1,4 @@
1
-// Code generated by protoc-gen-gogo.
1
+// Code generated by protoc-gen-go.
2
// source: spipe.proto
3
// DO NOT EDIT!
4
@@ -11,10 +11,11 @@ It is generated from these files:
11
It has these top-level messages:
12
Propose
13
Exchange
14
+ DataSig
15
*/
16
package spipe_pb
17
17
-import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/proto"
18
+import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
19
import math "math"
20
21
// Reference imports to suppress errors if they are not otherwise used.
@@ -93,5 +94,29 @@ func (m *Exchange) GetSignature() []byte {
94
return nil
95
}
96
97
+type DataSig struct {
98
+ Data []byte `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"`
99
+ Sig []byte `protobuf:"bytes,2,opt,name=sig" json:"sig,omitempty"`
100
+ XXX_unrecognized []byte `json:"-"`
101
+}
102
+
103
+func (m *DataSig) Reset() { *m = DataSig{} }
104
+func (m *DataSig) String() string { return proto.CompactTextString(m) }
105
+func (*DataSig) ProtoMessage() {}
106
+
107
+func (m *DataSig) GetData() []byte {
108
+ if m != nil {
109
+ return m.Data
110
+ }
111
+ return nil
112
+}
113
+
114
+func (m *DataSig) GetSig() []byte {
115
+ if m != nil {
116
+ return m.Sig
117
+ }
118
+ return nil
119
+}
120
+
121
func init() {
122
}
crypto/spipe/internal/pb/spipe.proto
+5
@@ -12,3 +12,8 @@ message Exchange {
12
optional bytes epubkey = 1;
13
optional bytes signature = 2;
14
}
15
+
16
+message DataSig {
17
+ optional bytes data = 1;
18
+ optional bytes sig = 2;
19
+}
crypto/spipe/signedpipe.go
new
+201
@@ -0,0 +1,201 @@
1
+package spipe
2
+
3
+import (
4
+ "bytes"
5
+ "crypto/rand"
6
+ "errors"
7
+
8
+ "code.google.com/p/goprotobuf/proto"
9
+
10
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
11
+ ci "github.com/jbenet/go-ipfs/crypto"
12
+ pb "github.com/jbenet/go-ipfs/crypto/spipe/internal/pb"
13
+ "github.com/jbenet/go-ipfs/peer"
14
+)
15
+
16
+type SignedPipe struct {
17
+ Duplex
18
+ insecure Duplex
19
+
20
+ local peer.Peer
21
+ remote peer.Peer
22
+ peers peer.Peerstore
23
+
24
+ ctx context.Context
25
+ cancel context.CancelFunc
26
+}
27
+
28
+func NewSignedPipe(parctx context.Context, bufsize int, local peer.Peer,
29
+ peers peer.Peerstore, insecure Duplex) (*SignedPipe, error) {
30
+
31
+ ctx, cancel := context.WithCancel(parctx)
32
+
33
+ sp := &SignedPipe{
34
+ Duplex: Duplex{
35
+ In: make(chan []byte, bufsize),
36
+ Out: make(chan []byte, bufsize),
37
+ },
38
+ local: local,
39
+ peers: peers,
40
+ insecure: insecure,
41
+
42
+ ctx: ctx,
43
+ cancel: cancel,
44
+ }
45
+
46
+ if err := sp.handshake(); err != nil {
47
+ sp.Close()
48
+ return nil, err
49
+ }
50
+ return sp, nil
51
+}
52
+
53
+func (sp *SignedPipe) handshake() error {
54
+ // Send them our public key
55
+ pubk := sp.local.PubKey()
56
+ pkb, err := pubk.Bytes()
57
+ if err != nil {
58
+ return err
59
+ }
60
+
61
+ sp.insecure.Out <- pkb
62
+ theirPkb := <-sp.insecure.In
63
+
64
+ theirPubKey, err := ci.UnmarshalPublicKey(theirPkb)
65
+ if err != nil {
66
+ return err
67
+ }
68
+
69
+ challenge := make([]byte, 32)
70
+ rand.Read(challenge)
71
+
72
+ enc, err := theirPubKey.Encrypt(challenge)
73
+ if err != nil {
74
+ return err
75
+ }
76
+
77
+ sp.insecure.Out <- enc
78
+ theirEnc := <-sp.insecure.In
79
+
80
+ unenc, err := sp.local.PrivKey().Unencrypt(theirEnc)
81
+ if err != nil {
82
+ return err
83
+ }
84
+
85
+ sig, err := sp.local.PrivKey().Sign(unenc)
86
+ if err != nil {
87
+ return err
88
+ }
89
+
90
+ sp.insecure.Out <- unenc
91
+ theirUnenc := <-sp.insecure.In
92
+ sp.insecure.Out <- sig
93
+ theirSig := <-sp.insecure.In
94
+
95
+ if !bytes.Equal(theirUnenc, challenge) {
96
+ return errors.New("received bad challenge response")
97
+ }
98
+
99
+ correct, err := theirPubKey.Verify(theirUnenc, theirSig)
100
+ if err != nil {
101
+ return err
102
+ }
103
+
104
+ if !correct {
105
+ return errors.New("Incorrect signature on challenge")
106
+ }
107
+
108
+ go sp.handleIn(theirPubKey)
109
+ go sp.handleOut()
110
+
111
+ finished := []byte("finished")
112
+ sp.Out <- finished
113
+ resp := <-sp.In
114
+ if !bytes.Equal(resp, finished) {
115
+ return errors.New("Handshake failed!")
116
+ }
117
+
118
+ return nil
119
+}
120
+
121
+func (sp *SignedPipe) handleOut() {
122
+ for {
123
+ var data []byte
124
+ var ok bool
125
+ select {
126
+ case <-sp.ctx.Done():
127
+ return
128
+ case data, ok = <-sp.Out:
129
+ if !ok {
130
+ log.Warning("pipe closed!")
131
+ return
132
+ }
133
+ }
134
+
135
+ sdata := new(pb.DataSig)
136
+
137
+ sig, err := sp.local.PrivKey().Sign(data)
138
+ if err != nil {
139
+ log.Error("Error signing outgoing data: %s", err)
140
+ continue
141
+ }
142
+
143
+ sdata.Data = data
144
+ sdata.Sig = sig
145
+ b, err := proto.Marshal(sdata)
146
+ if err != nil {
147
+ log.Error("Error marshaling signed data object: %s", err)
148
+ continue
149
+ }
150
+
151
+ select {
152
+ case sp.insecure.Out <- b:
153
+ case <-sp.ctx.Done():
154
+ log.Debug("Context finished before send could occur")
155
+ return
156
+ }
157
+ }
158
+}
159
+
160
+func (sp *SignedPipe) handleIn(theirPubkey ci.PubKey) {
161
+ for {
162
+ var data []byte
163
+ var ok bool
164
+ select {
165
+ case <-sp.ctx.Done():
166
+ return
167
+ case data, ok = <-sp.insecure.In:
168
+ if !ok {
169
+ log.Debug("Signed pipe closed")
170
+ return
171
+ }
172
+ }
173
+
174
+ sdata := new(pb.DataSig)
175
+ err := proto.Unmarshal(data, sdata)
176
+ if err != nil {
177
+ log.Error("Failed to unmarshal sigdata object")
178
+ continue
179
+ }
180
+ correct, err := theirPubkey.Verify(sdata.GetData(), sdata.GetSig())
181
+ if err != nil {
182
+ log.Error(err)
183
+ continue
184
+ }
185
+ if !correct {
186
+ log.Error("Received data with invalid signature!")
187
+ continue
188
+ }
189
+
190
+ select {
191
+ case <-sp.ctx.Done():
192
+ return
193
+ case sp.In <- sdata.GetData():
194
+ }
195
+ }
196
+}
197
+
198
+func (sp *SignedPipe) Close() error {
199
+ sp.cancel()
200
+ return nil
201
+}
crypto/spipe/spipe_test.go
+67
-1
@@ -3,7 +3,7 @@ package spipe
3
import (
4
"testing"
5
6
- "code.google.com/p/go.net/context"
6
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
7
8
ci "github.com/jbenet/go-ipfs/crypto"
9
"github.com/jbenet/go-ipfs/peer"
@@ -67,6 +67,14 @@ func BenchmarkDataEncryptLite(b *testing.B) {
67
runEncryptBenchmark(b)
68
}
69
70
+func BenchmarkDataEncryptBlowfish(b *testing.B) {
71
+ SupportedExchanges = "P-256"
72
+ SupportedCiphers = "Blowfish"
73
+ SupportedHashes = "SHA1"
74
+
75
+ runEncryptBenchmark(b)
76
+}
77
+
78
func runEncryptBenchmark(b *testing.B) {
79
pstore := peer.NewPeerstore()
80
ctx := context.TODO()
@@ -125,6 +133,64 @@ func runEncryptBenchmark(b *testing.B) {
133
134
}
135
136
+func BenchmarkSignedChannel(b *testing.B) {
137
+ pstore := peer.NewPeerstore()
138
+ ctx := context.TODO()
139
+ bufsize := 1024 * 1024
140
+
141
+ pa := getPeer(b)
142
+ pb := getPeer(b)
143
+ duplexa := Duplex{
144
+ In: make(chan []byte),
145
+ Out: make(chan []byte),
146
+ }
147
+ duplexb := Duplex{
148
+ In: make(chan []byte),
149
+ Out: make(chan []byte),
150
+ }
151
+
152
+ go bindDuplexNoCopy(duplexa, duplexb)
153
+
154
+ var spb *SignedPipe
155
+ done := make(chan struct{})
156
+ go func() {
157
+ var err error
158
+ spb, err = NewSignedPipe(ctx, bufsize, pb, pstore, duplexb)
159
+ if err != nil {
160
+ b.Fatal(err)
161
+ }
162
+ done <- struct{}{}
163
+ }()
164
+
165
+ spa, err := NewSignedPipe(ctx, bufsize, pa, pstore, duplexa)
166
+ if err != nil {
167
+ b.Fatal(err)
168
+ }
169
+
170
+ <-done
171
+
172
+ go func() {
173
+ for _ = range spa.In {
174
+ // Throw it all away,
175
+ // all of your hopes and dreams
176
+ // piped out to /dev/null...
177
+ done <- struct{}{}
178
+ }
179
+ }()
180
+
181
+ data := make([]byte, 1024*512)
182
+ util.NewFastRand().Read(data)
183
+ // Begin actual benchmarking
184
+ b.ResetTimer()
185
+
186
+ for i := 0; i < b.N; i++ {
187
+ b.SetBytes(int64(len(data)))
188
+ spb.Out <- data
189
+ <-done
190
+ }
191
+
192
+}
193
+
194
func BenchmarkDataTransfer(b *testing.B) {
195
duplexa := Duplex{
196
In: make(chan []byte),