@cryptotaxi247 / kubo / commits / 01c0c6e16

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),