Commit 97104d02 authored by tavit ohanian's avatar tavit ohanian

reference basis

parents b05d3772 0d1ffa76
Pipeline #639 failed with stages
in 0 seconds
os:
- linux
language: go
go:
- 1.13.x
- 1.13.x
env:
global:
- GOTFLAGS="-race -count 2"
matrix:
- BUILD_DEPTYPE=gomod
# disable travis install
install:
- true
script:
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
cache:
directories:
- $GOPATH/pkg/mod
- $HOME/.cache/go-build
notifications:
email: false
The MIT License (MIT)
Copyright (c) 2016 Jeromy Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# go-p2p-secio
# DEPRECATION NOTICE
dms3 p2p go-libp2p-secio
\ No newline at end of file
secio is not maintained any more, and we advise against using it.
Use [go-libp2p-tls](https://github.com/libp2p/go-libp2p-tls/) and [go-libp2p-noise](https://github.com/libp2p/go-libp2p-noise/) instead.
# go-libp2p-secio
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
[![GoDoc](https://godoc.org/github.com/libp2p/go-libp2p-secio?status.svg)](https://godoc.org/github.com/libp2p/go-libp2p-secio)
[![Build Status](https://travis-ci.org/libp2p/go-libp2p-secio.svg?branch=master)](https://travis-ci.org/libp2p/go-libp2p-secio)
> A secure transport module for go-libp2p
`go-libp2p-secio` is a component of the [libp2p project](https://libp2p.io), a
modular networking stack for developing peer-to-peer applications. It provides a
secure transport channel for [`go-libp2p`][go-libp2p]. Following an initial
plaintext handshake, all data exchanged between peers using `go-libp2p-secio` is
encrypted and protected from eavesdropping.
libp2p supports multiple [transport protocols][docs-transport], many of which
lack native channel security. `go-libp2p-secio` is designed to work with
go-libp2p's ["transport upgrader"][transport-upgrader], which applies security
modules (like `go-libp2p-secio`) to an insecure channel. `go-libp2p-secio`
implements the [`SecureTransport` interface][godoc-securetransport], which
allows the upgrader to secure any underlying connection.
More detail on the handshake protocol and wire format used is available in the
[SECIO spec][secio-spec].
## Install
Most people building applications with libp2p will have no need to install
`go-libp2p-secio` directly. It is included as a dependency of the main
[`go-libp2p`][go-libp2p] "entry point" module and is enabled by default.
For users who do not depend on `go-libp2p` and are managing their libp2p module
dependencies in a more manual fashion, `go-libp2p-secio` is a standard Go module
which can be installed with:
```sh
go get github.com/libp2p/go-libp2p-secio
```
This repo is [gomod](https://github.com/golang/go/wiki/Modules)-compatible, and users of
go 1.11 and later with modules enabled will automatically pull the latest tagged release
by referencing this package. Upgrades to future releases can be managed using `go get`,
or by editing your `go.mod` file as [described by the gomod documentation](https://github.com/golang/go/wiki/Modules#how-to-upgrade-and-downgrade-dependencies).
## Usage
`go-libp2p-secio` is enabled by default when constructing a new libp2p
[Host][godoc-host], and it will be used to secure connections if both peers
support it and [agree to use it][conn-spec] when establishing the connection.
You can disable SECIO by using the [`Security` option][godoc-security-option]
when constructing a libp2p `Host` and passing in a different `SecureTransport`
implementation, for example,
[`go-libp2p-tls`](https://github.com/libp2p/go-libp2p-tls).
Transport security can be disabled for development and testing by passing the
`NoSecurity` global [`Option`][godoc-option].
## Contribute
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/go-libp2p-secio/issues)!
This repository falls under the libp2p [Code of Conduct](https://github.com/libp2p/community/blob/master/code-of-conduct.md).
### Want to hack on libp2p?
[![](https://cdn.rawgit.com/libp2p/community/master/img/contribute.gif)](https://github.com/libp2p/community/blob/master/CONTRIBUTE.md)
## License
MIT
---
The last gx published version of this module was: 2.0.30: QmSVaJe1aRjc78cZARTtf4pqvXERYwihyYhZWoVWceHnsK
[go-libp2p]: https://github.com/libp2p/go-libp2p
[secio-spec]: https://github.com/libp2p/specs/blob/master/secio/README.md
[conn-spec]: https://github.com/libp2p/specs/blob/master/connections/README.md
[docs-transport]: https://docs.libp2p.io/concepts/transport
[transport-upgrader]: https://github.com/libp2p/go-libp2p-transport-upgrader
[godoc-host]: https://godoc.org/github.com/libp2p/go-libp2p-core/host#Host
[godoc-option]: https://godoc.org/github.com/libp2p/go-libp2p#Option
[godoc-security-option]: https://godoc.org/github.com/libp2p/go-libp2p#Security
[godoc-securetransport]: https://godoc.org/github.com/libp2p/go-libp2p-core/sec#SecureTransport
package secio
import (
"errors"
"fmt"
"strings"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha512"
"hash"
ci "github.com/libp2p/go-libp2p-core/crypto"
sha256 "github.com/minio/sha256-simd"
)
// SupportedExchanges is the list of supported ECDH curves
var SupportedExchanges = DefaultSupportedExchanges
const DefaultSupportedExchanges = "P-256,P-384,P-521"
// SupportedCiphers is the list of supported Ciphers
var SupportedCiphers = DefaultSupportedCiphers
const DefaultSupportedCiphers = "AES-256,AES-128"
// SupportedHashes is the list of supported Hashes
var SupportedHashes = DefaultSupportedHashes
const DefaultSupportedHashes = "SHA256,SHA512"
// HMAC carries a hash and its size
type HMAC struct {
hash.Hash
size int
}
// encParams represent encryption parameters
type encParams struct {
// keys
permanentPubKey ci.PubKey
ephemeralPubKey []byte
keys ci.StretchedKeys
// selections
curveT string
cipherT string
hashT string
// cipher + mac
cipher cipher.Stream
mac HMAC
}
func (e *encParams) makeMacAndCipher() error {
m, err := newMac(e.hashT, e.keys.MacKey)
if err != nil {
return err
}
bc, err := newBlockCipher(e.cipherT, e.keys.CipherKey)
if err != nil {
return err
}
e.cipher = cipher.NewCTR(bc, e.keys.IV)
e.mac = m
return nil
}
func newMac(hashType string, key []byte) (HMAC, error) {
switch hashType {
case "SHA512":
return HMAC{hmac.New(sha512.New, key), sha512.Size}, nil
case "SHA256":
return HMAC{hmac.New(sha256.New, key), sha256.Size}, nil
default:
return HMAC{}, fmt.Errorf("Unrecognized hash type: %s", hashType)
}
}
func newBlockCipher(cipherT string, key []byte) (cipher.Block, error) {
switch cipherT {
case "AES-128", "AES-256":
return aes.NewCipher(key)
default:
return nil, fmt.Errorf("Unrecognized cipher type: %s", cipherT)
}
}
// Determines which algorithm to use. Note: f(a, b) = f(b, a)
func selectBest(order int, p1, p2 string) (string, error) {
var f, s []string
switch {
case order < 0:
f = strings.Split(p2, ",")
s = strings.Split(p1, ",")
case order > 0:
f = strings.Split(p1, ",")
s = strings.Split(p2, ",")
default: // Exact same preferences.
p := strings.Split(p1, ",")
return p[0], nil
}
for _, fc := range f {
for _, sc := range s {
if fc == sc {
return fc, nil
}
}
}
return "", errors.New("No algorithms in common!")
}
coverage:
range: "50...100"
comment: off
This diff is collapsed.
PB = $(wildcard *.proto)
GO = $(PB:.proto=.pb.go)
all: $(GO)
%.pb.go: %.proto
protoc --proto_path=$(GOPATH)/src:. --gogofaster_out=. $<
clean:
rm -f *.pb.go
rm -f *.go
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: spipe.proto
package spipe_pb
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type Propose struct {
Rand []byte `protobuf:"bytes,1,opt,name=rand" json:"rand"`
Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey" json:"pubkey"`
Exchanges string `protobuf:"bytes,3,opt,name=exchanges" json:"exchanges"`
Ciphers string `protobuf:"bytes,4,opt,name=ciphers" json:"ciphers"`
Hashes string `protobuf:"bytes,5,opt,name=hashes" json:"hashes"`
}
func (m *Propose) Reset() { *m = Propose{} }
func (m *Propose) String() string { return proto.CompactTextString(m) }
func (*Propose) ProtoMessage() {}
func (*Propose) Descriptor() ([]byte, []int) {
return fileDescriptor_c474ec75f0379e64, []int{0}
}
func (m *Propose) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Propose) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Propose.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Propose) XXX_Merge(src proto.Message) {
xxx_messageInfo_Propose.Merge(m, src)
}
func (m *Propose) XXX_Size() int {
return m.Size()
}
func (m *Propose) XXX_DiscardUnknown() {
xxx_messageInfo_Propose.DiscardUnknown(m)
}
var xxx_messageInfo_Propose proto.InternalMessageInfo
func (m *Propose) GetRand() []byte {
if m != nil {
return m.Rand
}
return nil
}
func (m *Propose) GetPubkey() []byte {
if m != nil {
return m.Pubkey
}
return nil
}
func (m *Propose) GetExchanges() string {
if m != nil {
return m.Exchanges
}
return ""
}
func (m *Propose) GetCiphers() string {
if m != nil {
return m.Ciphers
}
return ""
}
func (m *Propose) GetHashes() string {
if m != nil {
return m.Hashes
}
return ""
}
type Exchange struct {
Epubkey []byte `protobuf:"bytes,1,opt,name=epubkey" json:"epubkey"`
Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature"`
}
func (m *Exchange) Reset() { *m = Exchange{} }
func (m *Exchange) String() string { return proto.CompactTextString(m) }
func (*Exchange) ProtoMessage() {}
func (*Exchange) Descriptor() ([]byte, []int) {
return fileDescriptor_c474ec75f0379e64, []int{1}
}
func (m *Exchange) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Exchange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Exchange.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Exchange) XXX_Merge(src proto.Message) {
xxx_messageInfo_Exchange.Merge(m, src)
}
func (m *Exchange) XXX_Size() int {
return m.Size()
}
func (m *Exchange) XXX_DiscardUnknown() {
xxx_messageInfo_Exchange.DiscardUnknown(m)
}
var xxx_messageInfo_Exchange proto.InternalMessageInfo
func (m *Exchange) GetEpubkey() []byte {
if m != nil {
return m.Epubkey
}
return nil
}
func (m *Exchange) GetSignature() []byte {
if m != nil {
return m.Signature
}
return nil
}
func init() {
proto.RegisterType((*Propose)(nil), "spipe.pb.Propose")
proto.RegisterType((*Exchange)(nil), "spipe.pb.Exchange")
}
func init() { proto.RegisterFile("spipe.proto", fileDescriptor_c474ec75f0379e64) }
var fileDescriptor_c474ec75f0379e64 = []byte{
// 207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2e, 0xc8, 0x2c,
0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0x72, 0x92, 0x94, 0x16, 0x33, 0x72,
0xb1, 0x07, 0x14, 0xe5, 0x17, 0xe4, 0x17, 0xa7, 0x0a, 0x49, 0x70, 0xb1, 0x14, 0x25, 0xe6, 0xa5,
0x48, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x38, 0xb1, 0x9c, 0xb8, 0x27, 0xcf, 0x10, 0x04, 0x16, 0x11,
0x92, 0xe1, 0x62, 0x2b, 0x28, 0x4d, 0xca, 0x4e, 0xad, 0x94, 0x60, 0x42, 0x92, 0x83, 0x8a, 0x09,
0x29, 0x71, 0x71, 0xa6, 0x56, 0x24, 0x67, 0x24, 0xe6, 0xa5, 0xa7, 0x16, 0x4b, 0x30, 0x2b, 0x30,
0x6a, 0x70, 0x42, 0x15, 0x20, 0x84, 0x85, 0xe4, 0xb8, 0xd8, 0x93, 0x33, 0x0b, 0x32, 0x52, 0x8b,
0x8a, 0x25, 0x58, 0x90, 0x54, 0xc0, 0x04, 0x41, 0x36, 0x64, 0x24, 0x16, 0x67, 0xa4, 0x16, 0x4b,
0xb0, 0x22, 0x49, 0x43, 0xc5, 0x94, 0xfc, 0xb8, 0x38, 0x5c, 0xa1, 0x46, 0x81, 0x4c, 0x4a, 0x85,
0x3a, 0x06, 0xd9, 0xa1, 0x30, 0x41, 0x90, 0x6b, 0x8a, 0x33, 0xd3, 0xf3, 0x12, 0x4b, 0x4a, 0x8b,
0x52, 0x51, 0x9c, 0x8b, 0x10, 0x76, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6,
0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39,
0x06, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7c, 0x0d, 0xc4, 0xcb, 0x27, 0x01, 0x00, 0x00,
}
func (m *Propose) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Propose) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Propose) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
i -= len(m.Hashes)
copy(dAtA[i:], m.Hashes)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Hashes)))
i--
dAtA[i] = 0x2a
i -= len(m.Ciphers)
copy(dAtA[i:], m.Ciphers)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Ciphers)))
i--
dAtA[i] = 0x22
i -= len(m.Exchanges)
copy(dAtA[i:], m.Exchanges)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Exchanges)))
i--
dAtA[i] = 0x1a
if m.Pubkey != nil {
i -= len(m.Pubkey)
copy(dAtA[i:], m.Pubkey)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Pubkey)))
i--
dAtA[i] = 0x12
}
if m.Rand != nil {
i -= len(m.Rand)
copy(dAtA[i:], m.Rand)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Rand)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *Exchange) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Exchange) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Exchange) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.Signature != nil {
i -= len(m.Signature)
copy(dAtA[i:], m.Signature)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Signature)))
i--
dAtA[i] = 0x12
}
if m.Epubkey != nil {
i -= len(m.Epubkey)
copy(dAtA[i:], m.Epubkey)
i = encodeVarintSpipe(dAtA, i, uint64(len(m.Epubkey)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintSpipe(dAtA []byte, offset int, v uint64) int {
offset -= sovSpipe(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Propose) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Rand != nil {
l = len(m.Rand)
n += 1 + l + sovSpipe(uint64(l))
}
if m.Pubkey != nil {
l = len(m.Pubkey)
n += 1 + l + sovSpipe(uint64(l))
}
l = len(m.Exchanges)
n += 1 + l + sovSpipe(uint64(l))
l = len(m.Ciphers)
n += 1 + l + sovSpipe(uint64(l))
l = len(m.Hashes)
n += 1 + l + sovSpipe(uint64(l))
return n
}
func (m *Exchange) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Epubkey != nil {
l = len(m.Epubkey)
n += 1 + l + sovSpipe(uint64(l))
}
if m.Signature != nil {
l = len(m.Signature)
n += 1 + l + sovSpipe(uint64(l))
}
return n
}
func sovSpipe(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozSpipe(x uint64) (n int) {
return sovSpipe(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Propose) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Propose: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Propose: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Rand", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Rand = append(m.Rand[:0], dAtA[iNdEx:postIndex]...)
if m.Rand == nil {
m.Rand = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Pubkey", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Pubkey = append(m.Pubkey[:0], dAtA[iNdEx:postIndex]...)
if m.Pubkey == nil {
m.Pubkey = []byte{}
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Exchanges", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Exchanges = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Ciphers", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Ciphers = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Hashes", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Hashes = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSpipe(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthSpipe
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthSpipe
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Exchange) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Exchange: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Exchange: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Epubkey", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Epubkey = append(m.Epubkey[:0], dAtA[iNdEx:postIndex]...)
if m.Epubkey == nil {
m.Epubkey = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSpipe
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSpipe
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSpipe
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...)
if m.Signature == nil {
m.Signature = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSpipe(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthSpipe
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthSpipe
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipSpipe(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSpipe
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSpipe
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSpipe
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthSpipe
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupSpipe
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthSpipe
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthSpipe = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowSpipe = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupSpipe = fmt.Errorf("proto: unexpected end of group")
)
syntax = "proto2";
package spipe.pb;
message Propose {
optional bytes rand = 1;
optional bytes pubkey = 2;
optional string exchanges = 3;
optional string ciphers = 4;
optional string hashes = 5;
}
message Exchange {
optional bytes epubkey = 1;
optional bytes signature = 2;
}
package secio
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"net"
"time"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/sec"
proto "github.com/gogo/protobuf/proto"
logging "github.com/ipfs/go-log"
ci "github.com/libp2p/go-libp2p-core/crypto"
pb "github.com/libp2p/go-libp2p-secio/pb"
msgio "github.com/libp2p/go-msgio"
mh "github.com/multiformats/go-multihash"
)
var log = logging.Logger("secio")
// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
var ErrUnsupportedKeyType = errors.New("unsupported key type")
// ErrClosed signals the closing of a connection.
var ErrClosed = errors.New("connection closed")
// ErrWrongPeer is returned when we attempt to handshake with the wrong peer.
var ErrWrongPeer = errors.New("connected to wrong peer")
// ErrBadSig signals that the peer sent us a handshake packet with a bad signature.
var ErrBadSig = errors.New("bad signature")
// ErrEcho is returned when we're attempting to handshake with the same keys and nonces.
var ErrEcho = errors.New("same keys and nonces. one side talking to self")
// HandshakeTimeout governs how long the handshake will be allowed to take place for.
// Making this number large means there could be many bogus connections waiting to
// timeout in flight. Typical handshakes take ~3RTTs, so it should be completed within
// seconds across a typical planet in the solar system.
var HandshakeTimeout = time.Second * 30
// nonceSize is the size of our nonces (in bytes)
const nonceSize = 16
// secureSession encapsulates all the parameters needed for encrypting
// and decrypting traffic from an insecure channel.
type secureSession struct {
msgio.ReadWriteCloser
insecure net.Conn
localKey ci.PrivKey
localPeer peer.ID
remotePeer peer.ID
local encParams
remote encParams
sharedSecret []byte
}
var _ sec.SecureConn = &secureSession{}
func (s *secureSession) Loggable() map[string]interface{} {
m := make(map[string]interface{})
m["localPeer"] = s.localPeer.Pretty()
m["remotePeer"] = s.remotePeer.Pretty()
m["established"] = (s.ReadWriteCloser != nil)
return m
}
func newSecureSession(ctx context.Context, local peer.ID, key ci.PrivKey, insecure net.Conn, remotePeer peer.ID) (*secureSession, error) {
s := &secureSession{localPeer: local, localKey: key}
switch {
case s.localPeer == "":
return nil, errors.New("no local id provided")
case s.localKey == nil:
return nil, errors.New("no local private key provided")
case !s.localPeer.MatchesPrivateKey(s.localKey):
return nil, fmt.Errorf("peer.ID does not match PrivateKey")
case insecure == nil:
return nil, fmt.Errorf("insecure ReadWriter is nil")
}
s.insecure = insecure
s.remotePeer = remotePeer
handshakeCtx, cancel := context.WithTimeout(ctx, HandshakeTimeout) // remove
defer cancel()
if err := s.runHandshake(handshakeCtx); err != nil {
return nil, err
}
return s, nil
}
func hashSha256(data []byte) mh.Multihash {
h, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
// this error can be safely ignored (panic) because multihash only fails
// from the selection of hash function. If the fn + length are valid, it
// won't error.
panic("multihash failed to hash using SHA2_256.")
}
return h
}
// runHandshake performs initial communication over insecure channel to share
// keys, IDs, and initiate communication, assigning all necessary params.
// requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
func (s *secureSession) runHandshake(ctx context.Context) error {
defer log.EventBegin(ctx, "secureHandshake", s).Done()
result := make(chan error, 1)
go func() {
// do *not* close the channel (will look like a success).
result <- s.runHandshakeSync()
}()
var err error
select {
case <-ctx.Done():
err = ctx.Err()
// State unknown. We *have* to close this.
s.insecure.Close()
// Wait for the handshake to return.
<-result
case err = <-result:
}
return err
}
func (s *secureSession) runHandshakeSync() error {
insecureM := msgio.NewReadWriter(s.insecure)
// =============================================================================
// step 1. Propose -- propose cipher suite + send pubkeys + nonce
// Generate and send Hello packet.
// Hello = (rand, PublicKey, Supported)
nonceOut := make([]byte, nonceSize)
_, err := rand.Read(nonceOut)
if err != nil {
return err
}
s.local.permanentPubKey = s.localKey.GetPublic()
myPubKeyBytes, err := s.local.permanentPubKey.Bytes()
if err != nil {
return err
}
proposeOut := new(pb.Propose)
proposeOut.Rand = nonceOut
proposeOut.Pubkey = myPubKeyBytes
proposeOut.Exchanges = SupportedExchanges
proposeOut.Ciphers = SupportedCiphers
proposeOut.Hashes = SupportedHashes
// log.Debugf("1.0 Propose: nonce:%s exchanges:%s ciphers:%s hashes:%s",
// nonceOut, SupportedExchanges, SupportedCiphers, SupportedHashes)
// Marshal our propose packet
proposeOutBytes, err := proto.Marshal(proposeOut)
if err != nil {
return err
}
// Send Propose packet and Receive their Propose packet
proposeInBytes, err := readWriteMsg(insecureM, proposeOutBytes)
if err != nil {
return err
}
defer insecureM.ReleaseMsg(proposeInBytes)
// Parse their propose packet
proposeIn := new(pb.Propose)
if err = proto.Unmarshal(proposeInBytes, proposeIn); err != nil {
return err
}
// log.Debugf("1.0.1 Propose recv: nonce:%s exchanges:%s ciphers:%s hashes:%s",
// proposeIn.GetRand(), proposeIn.GetExchanges(), proposeIn.GetCiphers(), proposeIn.GetHashes())
// =============================================================================
// step 1.1 Identify -- get identity from their key
// get remote identity
s.remote.permanentPubKey, err = ci.UnmarshalPublicKey(proposeIn.GetPubkey())
if err != nil {
return err
}
// get peer id
actualRemotePeer, err := peer.IDFromPublicKey(s.remote.permanentPubKey)
if err != nil {
return err
}
switch s.remotePeer {
case actualRemotePeer:
// All good.
case "":
// No peer set. We're accepting a remote connection.
s.remotePeer = actualRemotePeer
default:
// Peer mismatch. Bail.
s.insecure.Close()
log.Debugf("expected peer %s, got peer %s", s.remotePeer, actualRemotePeer)
return ErrWrongPeer
}
log.Debugf("1.1 Identify: %s Remote Peer Identified as %s", s.localPeer, s.remotePeer)
// =============================================================================
// step 1.2 Selection -- select/agree on best encryption parameters
// to determine order, use cmp(H(remote_pubkey||local_rand), H(local_pubkey||remote_rand)).
oh1 := hashSha256(append(proposeIn.GetPubkey(), nonceOut...))
oh2 := hashSha256(append(myPubKeyBytes, proposeIn.GetRand()...))
order := bytes.Compare(oh1, oh2)
if order == 0 {
return ErrEcho // talking to self (same socket. must be reuseport + dialing self)
}
s.local.curveT, err = selectBest(order, SupportedExchanges, proposeIn.GetExchanges())
if err != nil {
return err
}
s.local.cipherT, err = selectBest(order, SupportedCiphers, proposeIn.GetCiphers())
if err != nil {
return err
}
s.local.hashT, err = selectBest(order, SupportedHashes, proposeIn.GetHashes())
if err != nil {
return err
}
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
s.remote.curveT = s.local.curveT
s.remote.cipherT = s.local.cipherT
s.remote.hashT = s.local.hashT
// log.Debugf("1.2 selection: exchange:%s cipher:%s hash:%s",
// s.local.curveT, s.local.cipherT, s.local.hashT)
// =============================================================================
// step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures.
// Generate EphemeralPubKey
var genSharedKey ci.GenSharedKey
s.local.ephemeralPubKey, genSharedKey, err = ci.GenerateEKeyPair(s.local.curveT)
if err != nil {
return err
}
// Gather corpus to sign.
selectionOut := new(bytes.Buffer)
selectionOut.Write(proposeOutBytes)
selectionOut.Write(proposeInBytes)
selectionOut.Write(s.local.ephemeralPubKey)
selectionOutBytes := selectionOut.Bytes()
// log.Debugf("2.0 exchange: %v", selectionOutBytes)
exchangeOut := new(pb.Exchange)
exchangeOut.Epubkey = s.local.ephemeralPubKey
exchangeOut.Signature, err = s.localKey.Sign(selectionOutBytes)
if err != nil {
return err
}
// Marshal our exchange packet
exchangeOutBytes, err := proto.Marshal(exchangeOut)
if err != nil {
return err
}
// Send Exchange packet and receive their Exchange packet
exchangeInBytes, err := readWriteMsg(insecureM, exchangeOutBytes)
if err != nil {
return err
}
defer insecureM.ReleaseMsg(exchangeInBytes)
// Parse their Exchange packet.
exchangeIn := new(pb.Exchange)
if err = proto.Unmarshal(exchangeInBytes, exchangeIn); err != nil {
return err
}
// =============================================================================
// step 2.1. Verify -- verify their exchange packet is good.
// get their ephemeral pub key
s.remote.ephemeralPubKey = exchangeIn.GetEpubkey()
selectionIn := new(bytes.Buffer)
selectionIn.Write(proposeInBytes)
selectionIn.Write(proposeOutBytes)
selectionIn.Write(s.remote.ephemeralPubKey)
selectionInBytes := selectionIn.Bytes()
// log.Debugf("2.0.1 exchange recv: %v", selectionInBytes)
// u.POut("Remote Peer Identified as %s\n", s.remote)
sigOK, err := s.remote.permanentPubKey.Verify(selectionInBytes, exchangeIn.GetSignature())
if err != nil {
// log.Error("2.1 Verify: failed: %s", err)
return err
}
if !sigOK {
// log.Error("2.1 Verify: failed: %s", ErrBadSig)
return ErrBadSig
}
// log.Debugf("2.1 Verify: signature verified.")
// =============================================================================
// step 2.2. Keys -- generate keys for mac + encryption
// OK! seems like we're good to go.
s.sharedSecret, err = genSharedKey(exchangeIn.GetEpubkey())
if err != nil {
return err
}
// generate two sets of keys (stretching)
k1, k2 := ci.KeyStretcher(s.local.cipherT, s.local.hashT, s.sharedSecret)
// use random nonces to decide order.
switch {
case order > 0:
// just break
case order < 0:
k1, k2 = k2, k1 // swap
default:
// we should've bailed before this. but if not, bail here.
return ErrEcho
}
s.local.keys = k1
s.remote.keys = k2
// log.Debug("2.2 keys:\n\tshared: %v\n\tk1: %v\n\tk2: %v",
// s.sharedSecret, s.local.keys, s.remote.keys)
// =============================================================================
// step 2.3. MAC + Cipher -- prepare MAC + cipher
if err := s.local.makeMacAndCipher(); err != nil {
return err
}
if err := s.remote.makeMacAndCipher(); err != nil {
return err
}
// log.Debug("2.3 mac + cipher.")
// =============================================================================
// step 3. Finish -- send expected message to verify encryption works (send local nonce)
// setup ETM ReadWriter
w := NewETMWriter(s.insecure, s.local.cipher, s.local.mac)
r := NewETMReader(s.insecure, s.remote.cipher, s.remote.mac)
s.ReadWriteCloser = msgio.Combine(w, r).(msgio.ReadWriteCloser)
// log.Debug("3.0 finish. sending: %v", proposeIn.GetRand())
// send their Nonce and receive ours
nonceOut2, err := readWriteMsg(s.ReadWriteCloser, proposeIn.GetRand())
if err != nil {
return err
}
defer s.ReleaseMsg(nonceOut2)
// log.Debug("3.0 finish.\n\texpect: %v\n\tactual: %v", nonceOut, nonceOut2)
if !bytes.Equal(nonceOut, nonceOut2) {
return fmt.Errorf("Failed to read our encrypted nonce: %s != %s", nonceOut2, nonceOut)
}
// Whew! ok, that's all folks.
return nil
}
package secio
import (
"crypto/cipher"
"crypto/hmac"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
pool "github.com/libp2p/go-buffer-pool"
msgio "github.com/libp2p/go-msgio"
)
// ErrMACInvalid signals that a MAC verification failed
var ErrMACInvalid = errors.New("MAC verification failed")
type etmWriter struct {
str cipher.Stream // the stream cipher to encrypt with
mac HMAC // the mac to authenticate data with
w io.Writer
sync.Mutex
}
// NewETMWriter Encrypt-Then-MAC
func NewETMWriter(w io.Writer, s cipher.Stream, mac HMAC) msgio.WriteCloser {
return &etmWriter{w: w, str: s, mac: mac}
}
// Write writes passed in buffer as a single message.
func (w *etmWriter) Write(b []byte) (int, error) {
if err := w.WriteMsg(b); err != nil {
return 0, err
}
return len(b), nil
}
// WriteMsg writes the msg in the passed in buffer.
func (w *etmWriter) WriteMsg(b []byte) error {
w.Lock()
defer w.Unlock()
// encrypt.
buf := pool.Get(4 + len(b) + w.mac.Size())
defer pool.Put(buf)
data := buf[4 : 4+len(b)]
w.str.XORKeyStream(data, b)
// log.Debugf("ENC plaintext (%d): %s %v", len(b), b, b)
// log.Debugf("ENC ciphertext (%d): %s %v", len(data), data, data)
// then, mac.
if _, err := w.mac.Write(data); err != nil {
return err
}
// Sum appends.
data = w.mac.Sum(data)
w.mac.Reset()
binary.BigEndian.PutUint32(buf[:4], uint32(len(data)))
_, err := w.w.Write(buf)
return err
}
func (w *etmWriter) Close() error {
if c, ok := w.w.(io.Closer); ok {
return c.Close()
}
return nil
}
type etmReader struct {
msgio.Reader
io.Closer
// internal buffer returned from the msgio
buf []byte
// low and high watermark for the buffered data
lowat int
hiwat int
// params
msg msgio.ReadCloser // msgio for knowing where boundaries lie
str cipher.Stream // the stream cipher to encrypt with
mac HMAC // the mac to authenticate data with
// internal buffer used for checking MACs, this saves us quite a few
// allocations and should be quite small.
macBuf []byte
sync.Mutex
}
// NewETMReader Encrypt-Then-MAC
func NewETMReader(r io.Reader, s cipher.Stream, mac HMAC) msgio.ReadCloser {
return &etmReader{msg: msgio.NewReader(r), str: s, mac: mac}
}
func (r *etmReader) NextMsgLen() (int, error) {
return r.msg.NextMsgLen()
}
func (r *etmReader) drain(buf []byte) int {
// Return zero if there is no data remaining in the internal buffer.
if r.lowat == r.hiwat {
return 0
}
// Copy data to the output buffer.
n := copy(buf, r.buf[r.lowat:r.hiwat])
// Update the low watermark.
r.lowat += n
// Release the buffer and reset the watermarks if it has been fully read.
if r.lowat == r.hiwat {
r.msg.ReleaseMsg(r.buf)
r.buf = nil
r.lowat = 0
r.hiwat = 0
}
return n
}
func (r *etmReader) fill() error {
// Read a message from the underlying msgio.
msg, err := r.msg.ReadMsg()
if err != nil {
return err
}
// Check the MAC.
n, err := r.macCheckThenDecrypt(msg)
if err != nil {
r.msg.ReleaseMsg(msg)
return err
}
// Retain the buffer so it can be drained from and later released.
r.buf = msg
r.lowat = 0
r.hiwat = n
return nil
}
func (r *etmReader) Read(buf []byte) (int, error) {
r.Lock()
defer r.Unlock()
// Return buffered data without reading more, if possible.
copied := r.drain(buf)
if copied > 0 {
return copied, nil
}
// Check the length of the next message.
fullLen, err := r.msg.NextMsgLen()
if err != nil {
return 0, err
}
// If the destination buffer is too short, fill an internal buffer and then
// drain as much of that into the output buffer as will fit.
if len(buf) < fullLen {
err := r.fill()
if err != nil {
return 0, err
}
copied := r.drain(buf)
return copied, nil
}
// Otherwise, read directly into the destination buffer.
n, err := io.ReadFull(r.msg, buf[:fullLen])
if err != nil {
return 0, err
}
m, err := r.macCheckThenDecrypt(buf[:n])
if err != nil {
return 0, err
}
return m, nil
}
func (r *etmReader) ReadMsg() ([]byte, error) {
r.Lock()
defer r.Unlock()
msg, err := r.msg.ReadMsg()
if err != nil {
return nil, err
}
n, err := r.macCheckThenDecrypt(msg)
if err != nil {
r.msg.ReleaseMsg(msg)
return nil, err
}
return msg[:n], nil
}
func (r *etmReader) macCheckThenDecrypt(m []byte) (int, error) {
l := len(m)
if l < r.mac.size {
return 0, fmt.Errorf("buffer (%d) shorter than MAC size (%d)", l, r.mac.size)
}
mark := l - r.mac.size
data := m[:mark]
macd := m[mark:]
r.mac.Write(data)
r.macBuf = r.mac.Sum(r.macBuf[:0])
r.mac.Reset()
// check mac. if failed, return error.
if !hmac.Equal(macd, r.macBuf) {
log.Debug("MAC Invalid:", r.macBuf, "!=", macd)
return 0, ErrMACInvalid
}
// ok seems good. decrypt. (can decrypt in place, yay!)
// log.Debugf("DEC ciphertext (%d): %s %v", len(data), data, data)
r.str.XORKeyStream(data, data)
// log.Debugf("DEC plaintext (%d): %s %v", len(data), data, data)
return mark, nil
}
func (r *etmReader) Close() error {
return r.msg.Close()
}
// ReleaseMsg signals a buffer can be reused.
func (r *etmReader) ReleaseMsg(b []byte) {
r.msg.ReleaseMsg(b)
}
// read and write a message at the same time.
func readWriteMsg(c msgio.ReadWriter, out []byte) ([]byte, error) {
wresult := make(chan error)
go func() {
wresult <- c.WriteMsg(out)
}()
msg, err1 := c.ReadMsg()
// Always wait for the read to finish.
err2 := <-wresult
if err1 != nil {
return nil, err1
}
if err2 != nil {
c.ReleaseMsg(msg)
return nil, err2
}
return msg, nil
}
package secio
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"io"
"testing"
)
type keyInfo struct {
cipherKey []byte
iv []byte
macKey []byte
}
func getTestKeyInfo() *keyInfo {
return &keyInfo{
cipherKey: []byte("this is a test keyaaaaaaaaaaaaaa"),
iv: make([]byte, 16),
macKey: []byte("key for the mac"),
}
}
func getTestingWriter(w io.Writer, ki *keyInfo) (*etmWriter, error) {
c, err := aes.NewCipher(ki.cipherKey)
if err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(c, ki.iv)
mac, err := newMac("SHA256", ki.macKey)
if err != nil {
return nil, err
}
return NewETMWriter(w, stream, mac).(*etmWriter), nil
}
func getTestingReader(r io.Reader, ki *keyInfo) (*etmReader, error) {
c, err := aes.NewCipher(ki.cipherKey)
if err != nil {
return nil, err
}
stream := cipher.NewCFBDecrypter(c, ki.iv)
mac, err := newMac("SHA256", ki.macKey)
if err != nil {
return nil, err
}
return NewETMReader(r, stream, mac).(*etmReader), nil
}
func TestBasicETMStream(t *testing.T) {
buf := new(bytes.Buffer)
ki := getTestKeyInfo()
w, err := getTestingWriter(buf, ki)
if err != nil {
t.Fatal(err)
}
before := []byte("hello world")
err = w.WriteMsg(before)
if err != nil {
t.Fatal(err)
}
r, err := getTestingReader(buf, ki)
if err != nil {
t.Fatal(err)
}
msg, err := r.ReadMsg()
if err != nil {
t.Fatal(err)
}
if string(before) != string(msg) {
t.Fatal("got wrong message")
}
w.Close()
r.Close()
}
func TestBasicETMStreamRaw(t *testing.T) {
buf := new(bytes.Buffer)
ki := getTestKeyInfo()
w, err := getTestingWriter(buf, ki)
if err != nil {
t.Fatal(err)
}
before := []byte("hello world")
wlen, err := w.Write(before)
if err != nil {
t.Fatal(err)
}
if wlen != len(before) {
t.Errorf("Write length mismatch. %d != %d", wlen, len(before))
}
r, err := getTestingReader(buf, ki)
if err != nil {
t.Fatal(err)
}
after := make([]byte, wlen)
rlen, err := r.Read(after)
if err != nil {
t.Fatal(err)
}
if rlen != wlen {
t.Errorf("Read length mismatch. %d != %d", rlen, wlen)
}
if string(before) != string(after) {
t.Fatal("got wrong message")
}
w.Close()
r.Close()
}
// Package secio is used to encrypt `go-libp2p-conn` connections. Connections wrapped by secio use secure sessions provided by this package to encrypt all traffic. A TLS-like handshake is used to setup the communication channel.
package secio
import (
"context"
"net"
"time"
ci "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/sec"
)
// ID is secio's protocol ID (used when negotiating with multistream)
const ID = "/secio/1.0.0"
// Deprecated: secio is now deprecated.
// Use TLS 1.3 (https://github.com/libp2p/go-libp2p-tls/) and Noise (https://github.com/libp2p/go-libp2p-noise/) instead.
type Transport struct {
LocalID peer.ID
PrivateKey ci.PrivKey
}
// Deprecated: secio is now deprecated.
// Use TLS 1.3 (https://github.com/libp2p/go-libp2p-tls/) and Noise (https://github.com/libp2p/go-libp2p-noise/) instead.
func New(sk ci.PrivKey) (*Transport, error) {
id, err := peer.IDFromPrivateKey(sk)
if err != nil {
return nil, err
}
return &Transport{
LocalID: id,
PrivateKey: sk,
}, nil
}
var _ sec.SecureTransport = (*Transport)(nil)
func (sg *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (sec.SecureConn, error) {
return newSecureSession(ctx, sg.LocalID, sg.PrivateKey, insecure, "")
}
func (sg *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
return newSecureSession(ctx, sg.LocalID, sg.PrivateKey, insecure, p)
}
func (s *secureSession) SetReadDeadline(t time.Time) error {
return s.insecure.SetReadDeadline(t)
}
func (s *secureSession) SetWriteDeadline(t time.Time) error {
return s.insecure.SetWriteDeadline(t)
}
func (s *secureSession) SetDeadline(t time.Time) error {
return s.insecure.SetDeadline(t)
}
func (s *secureSession) RemoteAddr() net.Addr {
return s.insecure.RemoteAddr()
}
func (s *secureSession) LocalAddr() net.Addr {
return s.insecure.LocalAddr()
}
// LocalPeer retrieves the local peer.
func (s *secureSession) LocalPeer() peer.ID {
return s.localPeer
}
// LocalPrivateKey retrieves the local peer's PrivateKey
func (s *secureSession) LocalPrivateKey() ci.PrivKey {
return s.localKey
}
// RemotePeer retrieves the remote peer.
func (s *secureSession) RemotePeer() peer.ID {
return s.remotePeer
}
// RemotePublicKey retrieves the remote public key.
func (s *secureSession) RemotePublicKey() ci.PubKey {
return s.remote.permanentPubKey
}
package secio
import (
"bytes"
"context"
"io"
"math/rand"
"net"
"strings"
"testing"
"time"
ci "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/sec"
cst "github.com/libp2p/go-libp2p-testing/suites/sec"
)
func newTestTransport(t *testing.T, typ, bits int) *Transport {
priv, pub, err := ci.GenerateKeyPair(typ, bits)
if err != nil {
t.Fatal(err)
}
id, err := peer.IDFromPublicKey(pub)
if err != nil {
t.Fatal(err)
}
return &Transport{
PrivateKey: priv,
LocalID: id,
}
}
func TestTransport(t *testing.T) {
at := newTestTransport(t, ci.RSA, 2048)
bt := newTestTransport(t, ci.RSA, 2048)
cst.SubtestAll(t, at, bt, at.LocalID, bt.LocalID)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// Create a new pair of connected TCP sockets.
func newConnPair(t *testing.T) (net.Conn, net.Conn) {
lstnr, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("Failed to listen: %v", err)
return nil, nil
}
var clientErr error
var client net.Conn
addr := lstnr.Addr()
done := make(chan struct{})
go func() {
defer close(done)
client, clientErr = net.Dial(addr.Network(), addr.String())
}()
server, err := lstnr.Accept()
<-done
lstnr.Close()
if err != nil {
t.Fatalf("Failed to accept: %v", err)
}
if clientErr != nil {
t.Fatalf("Failed to connect: %v", clientErr)
}
return client, server
}
// Create a new pair of connected sessions based off of the provided
// session generators.
func connect(t *testing.T, clientTpt, serverTpt *Transport) (sec.SecureConn, sec.SecureConn) {
client, server := newConnPair(t)
// Connect the client and server sessions
done := make(chan struct{})
var clientConn sec.SecureConn
var clientErr error
go func() {
defer close(done)
clientConn, clientErr = clientTpt.SecureOutbound(context.TODO(), client, serverTpt.LocalID)
}()
serverConn, serverErr := serverTpt.SecureInbound(context.TODO(), server)
<-done
if serverErr != nil {
t.Fatal(serverErr)
}
if clientErr != nil {
t.Fatal(clientErr)
}
return clientConn, serverConn
}
// Shuffle a slice of strings
func shuffle(strs []string) []string {
for i := len(strs) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
strs[i], strs[j] = strs[j], strs[i]
}
return strs
}
type sessionParam struct {
Exchange string
Cipher string
Hash string
}
// Reset the global session parameters to the defaults
func resetSessionParams() {
SupportedExchanges = DefaultSupportedExchanges
SupportedCiphers = DefaultSupportedCiphers
SupportedHashes = DefaultSupportedHashes
}
// Get the minimal set of session parameters we should test.
//
// We'll try each exchange, cipher, and hash at least once. The combination
// with other parameters is randomized.
func getMinimalSessionParams() []sessionParam {
params := []sessionParam{}
rand.Seed(time.Now().UnixNano())
exchanges := shuffle(strings.Split(DefaultSupportedExchanges, ","))
ciphers := shuffle(strings.Split(DefaultSupportedCiphers, ","))
hashes := shuffle(strings.Split(DefaultSupportedHashes, ","))
m := max(len(exchanges), max(len(ciphers), len(hashes)))
for i := 0; i < m; i++ {
param := sessionParam{
Exchange: exchanges[i%len(exchanges)],
Cipher: ciphers[i%len(ciphers)],
Hash: hashes[i%len(hashes)],
}
params = append(params, param)
}
return params
}
// Get all of the combinations of session parameters possible
func getFullSessionParams() []sessionParam {
params := []sessionParam{}
exchanges := strings.Split(DefaultSupportedExchanges, ",")
ciphers := strings.Split(DefaultSupportedCiphers, ",")
hashes := strings.Split(DefaultSupportedHashes, ",")
for _, exchange := range exchanges {
for _, cipher := range ciphers {
for _, hash := range hashes {
param := sessionParam{
Exchange: exchange,
Cipher: cipher,
Hash: hash,
}
params = append(params, param)
}
}
}
return params
}
// Check the peer IDs
func testIDs(t *testing.T, clientTpt, serverTpt *Transport, clientConn, serverConn sec.SecureConn) {
if clientConn.LocalPeer() != clientTpt.LocalID {
t.Fatal("Client Local Peer ID mismatch.")
}
if clientConn.RemotePeer() != serverTpt.LocalID {
t.Fatal("Client Remote Peer ID mismatch.")
}
if clientConn.LocalPeer() != serverConn.RemotePeer() {
t.Fatal("Server Local Peer ID mismatch.")
}
}
// Check the keys
func testKeys(t *testing.T, clientTpt, serverTpt *Transport, clientConn, serverConn sec.SecureConn) {
sk := serverConn.LocalPrivateKey()
pk := sk.GetPublic()
if !sk.Equals(serverTpt.PrivateKey) {
t.Error("Private key Mismatch.")
}
if !pk.Equals(clientConn.RemotePublicKey()) {
t.Error("Public key mismatch.")
}
}
// Check sending and receiving messages
func testReadWrite(t *testing.T, clientConn, serverConn sec.SecureConn) {
before := []byte("hello world")
_, err := clientConn.Write(before)
if err != nil {
t.Fatal(err)
}
after := make([]byte, len(before))
_, err = io.ReadFull(serverConn, after)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(before, after) {
t.Errorf("Message mismatch. %v != %v", before, after)
}
}
// Setup a new session with a pair of locally connected sockets
func testConnection(t *testing.T, clientTpt, serverTpt *Transport) {
clientConn, serverConn := connect(t, clientTpt, serverTpt)
testIDs(t, clientTpt, serverTpt, clientConn, serverConn)
testKeys(t, clientTpt, serverTpt, clientConn, serverConn)
testReadWrite(t, clientConn, serverConn)
clientConn.Close()
serverConn.Close()
}
// Run a set of sessions through the session setup and verification.
func TestConnections(t *testing.T) {
clientTpt := newTestTransport(t, ci.RSA, 2048)
serverTpt := newTestTransport(t, ci.Ed25519, 2048)
t.Logf("Using default session parameters.")
testConnection(t, clientTpt, serverTpt)
defer resetSessionParams()
testParams := getMinimalSessionParams()
for _, params := range testParams {
SupportedExchanges = params.Exchange
SupportedCiphers = params.Cipher
SupportedHashes = params.Hash
t.Logf("Using Exchange: %s Cipher: %s Hash: %s\n",
params.Exchange, params.Cipher, params.Hash)
testConnection(t, clientTpt, serverTpt)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment