From 00542dec0245871d92d1e872d96f2c01c5f4b824 Mon Sep 17 00:00:00 2001 From: Sudhi Herle Date: Fri, 20 Mar 2020 17:40:52 -0700 Subject: [PATCH] Major breaking changes: Reworked file encryption scheme * all encryption now uses ephmeral curve25519 keys * sender can identify themselves by providing a signing key * sign/verify now uses a string prefix for calculating checksum of the incoming message + known prefix [prevents us from verifying unknown blobs] * encrypt/decrypt key is now expanded with a known prefix _and_ the header checksum * protobuf definition changed to include an encrypted sender identification blob (sender public key) * moved protobuf files into an internal/pb directory * general code rearrangement to make it easy to find files * added extra validation for reading all keys * bumped version to 1.0.0 --- README.md | 50 +- build | 2 +- {sign => internal/pb}/hdr.pb.go | 837 ++++++++++++++++++-------------- {sign => internal/pb}/hdr.proto | 22 +- internal/pb/wrap.go | 97 ++++ sign/encrypt.go | 439 +++++++---------- sign/encrypt_test.go | 7 + sign/keys.go | 548 +++++++++++++++++++++ sign/sign.go | 464 +----------------- sign/sign_test.go | 12 +- version | 2 +- 11 files changed, 1369 insertions(+), 1111 deletions(-) rename {sign => internal/pb}/hdr.pb.go (58%) rename {sign => internal/pb}/hdr.proto (66%) create mode 100644 internal/pb/wrap.go create mode 100644 sign/keys.go diff --git a/README.md b/README.md index e60234e..12564dc 100644 --- a/README.md +++ b/README.md @@ -140,14 +140,15 @@ recipient can decrypt using their private key. ### How is the private key protected? The Ed25519 private key is encrypted in AES-GCM-256 mode using a key -derived from the user's pass phrase. +derived from the user's pass-phrase. ### How is the Encryption done? The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses -a random 32-byte AES-256 key. The input is broken into chunks and -each chunk is individually AEAD encrypted. The default chunk size -is 4MB (4 * 1048576 bytes). Each chunk generates its own nonce -from a global salt. The nonce is calculated as a SHA256 hash of +a random 32-byte AES-256 key. This key is mixed in with the header checksum +as a safeguard to protect the header against accidental or malicious corruption. +The input is broken into chunks and each chunk is individually AEAD encrypted. +The default chunk size is 4MB (4 * 1048576 bytes). Each chunk generates +its own nonce from a global salt. The nonce is calculated as a SHA256 hash of the salt, the chunk length and the block number. ### What is the public-key cryptography? @@ -179,16 +180,26 @@ described as a protobuf file (sign/hdr.proto): ```protobuf message header { - uint32 chunk_size = 1; - bytes salt = 2; - repeated wrapped_key keys = 3; + uint32 chunk_size = 1; + bytes salt = 2; + bytes pk = 3; // sender's ephemeral curve PK + sender sender_pk = 4; // sender's encrypted ed25519 PK + repeated wrapped_key keys = 5; } + /* + * Sender info is wrapped using the data encryption key + */ + message sender { + bytes pk = 1; + } + + /* + * A file encryption key is wrapped by a recipient specific public + * key. WrappedKey describes such a wrapped key. + */ message wrapped_key { - bytes pk_hash = 1; // hash of Ed25519 PK - bytes pk = 2; // curve25519 PK - bytes nonce = 3; // AEAD nonce - bytes key = 4; // AEAD encrypted key + bytes key = 2; } ``` @@ -203,18 +214,23 @@ chunk is encoded the same way: AEAD tag ``` -The chunk length does _not_ include the AEAD tag length; it is implicitly +The chunk length does _not_ include the AEAD tag length; it is implicitly computed. The chunk data and AEAD tag are treated as an atomic unit for AEAD decryption. ## Understanding the Code -`src/sign` is a library to generate, verify and store Ed25519 keys -and signatures. It uses the extended library (golang.org/x/crypto) -for the underlying operations. +The core logic is in `src/sign`: it is a library that exposes all the +functionality: key generation, key parsing, signing, encryption, decryption +etc. -`src/crypt.go` contains the encryption & decryption code. +* `src/encrypt.go` contains the core encryption, decryption code +* `src/sign.go` contains the Ed25519 signing, verification code +* `src/keys.go` contains key generation, serialization, de-serialization +* `src/ssh.go` contains code to parse SSH Ed25519 key files +* `src/stream.go` contains code that provides an `io.Reader` and `io.WriteCloser` interface + for encryption and decryption. The generated keys and signatures are proper YAML files and human readable. diff --git a/build b/build index 92f6807..564ce0b 100755 --- a/build +++ b/build @@ -17,7 +17,7 @@ Progs=".:sigtool" # Relative path to protobuf sources # e.g. src/foo/a.proto -Protobufs="sign/hdr.proto" +Protobufs="internal/pb/hdr.proto" # -- DO NOT CHANGE ANYTHING AFTER THIS -- diff --git a/sign/hdr.pb.go b/internal/pb/hdr.pb.go similarity index 58% rename from sign/hdr.pb.go rename to internal/pb/hdr.pb.go index b1c4cb7..a4817d4 100644 --- a/sign/hdr.pb.go +++ b/internal/pb/hdr.pb.go @@ -1,18 +1,30 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: sign/hdr.proto +// Code generated by protoc-gen-gogo. +// source: internal/pb/hdr.proto +// DO NOT EDIT! -package sign +/* + Package pb is a generated protocol buffer package. -import ( - bytes "bytes" - fmt "fmt" - proto "github.com/gogo/protobuf/proto" - io "io" - math "math" - math_bits "math/bits" - reflect "reflect" - strings "strings" -) + It is generated from these files: + internal/pb/hdr.proto + + It has these top-level messages: + Header + Sender + WrappedKey +*/ +package pb + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import bytes "bytes" + +import strings "strings" +import reflect "reflect" + +import io "io" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal @@ -23,7 +35,7 @@ var _ = math.Inf // 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 +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package // // Every encrypted file starts with a header describing the @@ -33,40 +45,14 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Header struct { ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` - Keys []*WrappedKey `protobuf:"bytes,3,rep,name=keys,proto3" json:"keys,omitempty"` + Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"` + SenderPk *Sender `protobuf:"bytes,4,opt,name=sender_pk,json=senderPk" json:"sender_pk,omitempty"` + Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys" json:"keys,omitempty"` } -func (m *Header) Reset() { *m = Header{} } -func (*Header) ProtoMessage() {} -func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_85aff542c746609f, []int{0} -} -func (m *Header) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Header.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 *Header) XXX_Merge(src proto.Message) { - xxx_messageInfo_Header.Merge(m, src) -} -func (m *Header) XXX_Size() int { - return m.Size() -} -func (m *Header) XXX_DiscardUnknown() { - xxx_messageInfo_Header.DiscardUnknown(m) -} - -var xxx_messageInfo_Header proto.InternalMessageInfo +func (m *Header) Reset() { *m = Header{} } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{0} } func (m *Header) GetChunkSize() uint32 { if m != nil { @@ -82,6 +68,20 @@ func (m *Header) GetSalt() []byte { return nil } +func (m *Header) GetPk() []byte { + if m != nil { + return m.Pk + } + return nil +} + +func (m *Header) GetSenderPk() *Sender { + if m != nil { + return m.SenderPk + } + return nil +} + func (m *Header) GetKeys() []*WrappedKey { if m != nil { return m.Keys @@ -90,68 +90,33 @@ func (m *Header) GetKeys() []*WrappedKey { } // -// A file encryption key is wrapped by a recipient specific public -// key. WrappedKey describes such a wrapped key. -type WrappedKey struct { - PkHash []byte `protobuf:"bytes,1,opt,name=pk_hash,json=pkHash,proto3" json:"pk_hash,omitempty"` - Pk []byte `protobuf:"bytes,2,opt,name=pk,proto3" json:"pk,omitempty"` - Nonce []byte `protobuf:"bytes,3,opt,name=nonce,proto3" json:"nonce,omitempty"` - Key []byte `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` +// Sender info is wrapped using the data encryption key +type Sender struct { + Pk []byte `protobuf:"bytes,1,opt,name=pk,proto3" json:"pk,omitempty"` } -func (m *WrappedKey) Reset() { *m = WrappedKey{} } -func (*WrappedKey) ProtoMessage() {} -func (*WrappedKey) Descriptor() ([]byte, []int) { - return fileDescriptor_85aff542c746609f, []int{1} -} -func (m *WrappedKey) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *WrappedKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_WrappedKey.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 *WrappedKey) XXX_Merge(src proto.Message) { - xxx_messageInfo_WrappedKey.Merge(m, src) -} -func (m *WrappedKey) XXX_Size() int { - return m.Size() -} -func (m *WrappedKey) XXX_DiscardUnknown() { - xxx_messageInfo_WrappedKey.DiscardUnknown(m) -} +func (m *Sender) Reset() { *m = Sender{} } +func (*Sender) ProtoMessage() {} +func (*Sender) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{1} } -var xxx_messageInfo_WrappedKey proto.InternalMessageInfo - -func (m *WrappedKey) GetPkHash() []byte { - if m != nil { - return m.PkHash - } - return nil -} - -func (m *WrappedKey) GetPk() []byte { +func (m *Sender) GetPk() []byte { if m != nil { return m.Pk } return nil } -func (m *WrappedKey) GetNonce() []byte { - if m != nil { - return m.Nonce - } - return nil +// +// A file encryption key is wrapped by a recipient specific public +// key. WrappedKey describes such a wrapped key. +type WrappedKey struct { + Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` } +func (m *WrappedKey) Reset() { *m = WrappedKey{} } +func (*WrappedKey) ProtoMessage() {} +func (*WrappedKey) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{2} } + func (m *WrappedKey) GetKey() []byte { if m != nil { return m.Key @@ -160,36 +125,16 @@ func (m *WrappedKey) GetKey() []byte { } func init() { - proto.RegisterType((*Header)(nil), "sign.header") - proto.RegisterType((*WrappedKey)(nil), "sign.wrapped_key") + proto.RegisterType((*Header)(nil), "pb.header") + proto.RegisterType((*Sender)(nil), "pb.sender") + proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key") } - -func init() { proto.RegisterFile("sign/hdr.proto", fileDescriptor_85aff542c746609f) } - -var fileDescriptor_85aff542c746609f = []byte{ - // 257 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x31, 0x4a, 0xc4, 0x40, - 0x14, 0x86, 0xe7, 0x25, 0x31, 0xe2, 0xdb, 0x75, 0xd1, 0x41, 0x30, 0x8d, 0x8f, 0xb0, 0x20, 0xa4, - 0x8a, 0xa0, 0x9e, 0xc0, 0xca, 0x3a, 0xf6, 0x86, 0xec, 0x66, 0x70, 0xc2, 0x48, 0x32, 0x64, 0x56, - 0x24, 0x5b, 0x79, 0x04, 0x8f, 0xe1, 0x51, 0x2c, 0x53, 0x6e, 0x69, 0x26, 0x8d, 0xe5, 0x1e, 0x41, - 0x32, 0x5a, 0xd8, 0xfd, 0xff, 0xf7, 0xe0, 0x7d, 0xf0, 0xe3, 0xc2, 0x54, 0x4f, 0xf5, 0x95, 0x2c, - 0xdb, 0x54, 0xb7, 0xcd, 0xa6, 0xe1, 0xc1, 0xd4, 0x97, 0x2b, 0x0c, 0xa5, 0x28, 0x4a, 0xd1, 0xf2, - 0x0b, 0xc4, 0xb5, 0x7c, 0xa9, 0x55, 0x6e, 0xaa, 0xad, 0x88, 0x20, 0x86, 0xe4, 0x38, 0x3b, 0x72, - 0xe4, 0xa1, 0xda, 0x0a, 0xce, 0x31, 0x30, 0xc5, 0xf3, 0x26, 0xf2, 0x62, 0x48, 0xe6, 0x99, 0xcb, - 0xfc, 0x12, 0x03, 0x25, 0x3a, 0x13, 0xf9, 0xb1, 0x9f, 0xcc, 0xae, 0x4f, 0xd3, 0xe9, 0x63, 0xfa, - 0xda, 0x16, 0x5a, 0x8b, 0x32, 0x57, 0xa2, 0xcb, 0xdc, 0x79, 0xf9, 0x88, 0xb3, 0x7f, 0x90, 0x9f, - 0xe3, 0xa1, 0x56, 0xb9, 0x2c, 0x8c, 0x74, 0x96, 0x79, 0x16, 0x6a, 0x75, 0x5f, 0x18, 0xc9, 0x17, - 0xe8, 0x69, 0xf5, 0x27, 0xf0, 0xb4, 0xe2, 0x67, 0x78, 0x50, 0x37, 0xf5, 0x5a, 0x44, 0xbe, 0x43, - 0xbf, 0x85, 0x9f, 0xa0, 0xaf, 0x44, 0x17, 0x05, 0x8e, 0x4d, 0xf1, 0xee, 0xb6, 0x1f, 0x88, 0xed, - 0x06, 0x62, 0xfb, 0x81, 0xe0, 0xcd, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0xd0, 0x5b, 0x82, 0x2f, - 0x4b, 0xf0, 0x6d, 0x89, 0xed, 0x2d, 0xc1, 0xfb, 0x48, 0xac, 0x1f, 0x89, 0xed, 0x46, 0x62, 0xab, - 0xd0, 0xcd, 0x70, 0xf3, 0x13, 0x00, 0x00, 0xff, 0xff, 0xde, 0xf2, 0x28, 0xc0, 0x18, 0x01, 0x00, - 0x00, -} - func (this *Header) Equal(that interface{}) bool { if that == nil { - return this == nil + if this == nil { + return true + } + return false } that1, ok := that.(*Header) @@ -202,7 +147,10 @@ func (this *Header) Equal(that interface{}) bool { } } if that1 == nil { - return this == nil + if this == nil { + return true + } + return false } else if this == nil { return false } @@ -212,6 +160,12 @@ func (this *Header) Equal(that interface{}) bool { if !bytes.Equal(this.Salt, that1.Salt) { return false } + if !bytes.Equal(this.Pk, that1.Pk) { + return false + } + if !this.SenderPk.Equal(that1.SenderPk) { + return false + } if len(this.Keys) != len(that1.Keys) { return false } @@ -222,9 +176,42 @@ func (this *Header) Equal(that interface{}) bool { } return true } +func (this *Sender) Equal(that interface{}) bool { + if that == nil { + if this == nil { + return true + } + return false + } + + that1, ok := that.(*Sender) + if !ok { + that2, ok := that.(Sender) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + if this == nil { + return true + } + return false + } else if this == nil { + return false + } + if !bytes.Equal(this.Pk, that1.Pk) { + return false + } + return true +} func (this *WrappedKey) Equal(that interface{}) bool { if that == nil { - return this == nil + if this == nil { + return true + } + return false } that1, ok := that.(*WrappedKey) @@ -237,19 +224,13 @@ func (this *WrappedKey) Equal(that interface{}) bool { } } if that1 == nil { - return this == nil + if this == nil { + return true + } + return false } else if this == nil { return false } - if !bytes.Equal(this.PkHash, that1.PkHash) { - return false - } - if !bytes.Equal(this.Pk, that1.Pk) { - return false - } - if !bytes.Equal(this.Nonce, that1.Nonce) { - return false - } if !bytes.Equal(this.Key, that1.Key) { return false } @@ -259,25 +240,36 @@ func (this *Header) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 7) - s = append(s, "&sign.Header{") + s := make([]string, 0, 9) + s = append(s, "&pb.Header{") s = append(s, "ChunkSize: "+fmt.Sprintf("%#v", this.ChunkSize)+",\n") s = append(s, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\n") + s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n") + if this.SenderPk != nil { + s = append(s, "SenderPk: "+fmt.Sprintf("%#v", this.SenderPk)+",\n") + } if this.Keys != nil { s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n") } s = append(s, "}") return strings.Join(s, "") } +func (this *Sender) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 5) + s = append(s, "&pb.Sender{") + s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n") + s = append(s, "}") + return strings.Join(s, "") +} func (this *WrappedKey) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 8) - s = append(s, "&sign.WrappedKey{") - s = append(s, "PkHash: "+fmt.Sprintf("%#v", this.PkHash)+",\n") - s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n") - s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n") + s := make([]string, 0, 5) + s = append(s, "&pb.WrappedKey{") s = append(s, "Key: "+fmt.Sprintf("%#v", this.Key)+",\n") s = append(s, "}") return strings.Join(s, "") @@ -293,7 +285,7 @@ func valueToGoStringHdr(v interface{}, typ string) string { func (m *Header) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } @@ -301,48 +293,80 @@ func (m *Header) Marshal() (dAtA []byte, err error) { } func (m *Header) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) + var i int _ = i var l int _ = l - if len(m.Keys) > 0 { - for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Keys[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintHdr(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } + if m.ChunkSize != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintHdr(dAtA, i, uint64(m.ChunkSize)) } if len(m.Salt) > 0 { - i -= len(m.Salt) - copy(dAtA[i:], m.Salt) - i = encodeVarintHdr(dAtA, i, uint64(len(m.Salt))) - i-- dAtA[i] = 0x12 + i++ + i = encodeVarintHdr(dAtA, i, uint64(len(m.Salt))) + i += copy(dAtA[i:], m.Salt) } - if m.ChunkSize != 0 { - i = encodeVarintHdr(dAtA, i, uint64(m.ChunkSize)) - i-- - dAtA[i] = 0x8 + if len(m.Pk) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk))) + i += copy(dAtA[i:], m.Pk) } - return len(dAtA) - i, nil + if m.SenderPk != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintHdr(dAtA, i, uint64(m.SenderPk.Size())) + n1, err := m.SenderPk.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.Keys) > 0 { + for _, msg := range m.Keys { + dAtA[i] = 0x2a + i++ + i = encodeVarintHdr(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *Sender) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Sender) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Pk) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk))) + i += copy(dAtA[i:], m.Pk) + } + return i, nil } func (m *WrappedKey) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } @@ -350,61 +374,47 @@ func (m *WrappedKey) Marshal() (dAtA []byte, err error) { } func (m *WrappedKey) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) + var i int _ = i var l int _ = l if len(m.Key) > 0 { - i -= len(m.Key) - copy(dAtA[i:], m.Key) - i = encodeVarintHdr(dAtA, i, uint64(len(m.Key))) - i-- - dAtA[i] = 0x22 - } - if len(m.Nonce) > 0 { - i -= len(m.Nonce) - copy(dAtA[i:], m.Nonce) - i = encodeVarintHdr(dAtA, i, uint64(len(m.Nonce))) - i-- - dAtA[i] = 0x1a - } - if len(m.Pk) > 0 { - i -= len(m.Pk) - copy(dAtA[i:], m.Pk) - i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk))) - i-- dAtA[i] = 0x12 + i++ + i = encodeVarintHdr(dAtA, i, uint64(len(m.Key))) + i += copy(dAtA[i:], m.Key) } - if len(m.PkHash) > 0 { - i -= len(m.PkHash) - copy(dAtA[i:], m.PkHash) - i = encodeVarintHdr(dAtA, i, uint64(len(m.PkHash))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil + return i, nil } +func encodeFixed64Hdr(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Hdr(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} func encodeVarintHdr(dAtA []byte, offset int, v uint64) int { - offset -= sovHdr(v) - base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return base + return offset + 1 } func (m *Header) Size() (n int) { - if m == nil { - return 0 - } var l int _ = l if m.ChunkSize != 0 { @@ -414,6 +424,14 @@ func (m *Header) Size() (n int) { if l > 0 { n += 1 + l + sovHdr(uint64(l)) } + l = len(m.Pk) + if l > 0 { + n += 1 + l + sovHdr(uint64(l)) + } + if m.SenderPk != nil { + l = m.SenderPk.Size() + n += 1 + l + sovHdr(uint64(l)) + } if len(m.Keys) > 0 { for _, e := range m.Keys { l = e.Size() @@ -423,24 +441,19 @@ func (m *Header) Size() (n int) { return n } -func (m *WrappedKey) Size() (n int) { - if m == nil { - return 0 - } +func (m *Sender) Size() (n int) { var l int _ = l - l = len(m.PkHash) - if l > 0 { - n += 1 + l + sovHdr(uint64(l)) - } l = len(m.Pk) if l > 0 { n += 1 + l + sovHdr(uint64(l)) } - l = len(m.Nonce) - if l > 0 { - n += 1 + l + sovHdr(uint64(l)) - } + return n +} + +func (m *WrappedKey) Size() (n int) { + var l int + _ = l l = len(m.Key) if l > 0 { n += 1 + l + sovHdr(uint64(l)) @@ -449,7 +462,14 @@ func (m *WrappedKey) Size() (n int) { } func sovHdr(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n } func sozHdr(x uint64) (n int) { return sovHdr(uint64((x << 1) ^ uint64((int64(x) >> 63)))) @@ -458,15 +478,22 @@ func (this *Header) String() string { if this == nil { return "nil" } - repeatedStringForKeys := "[]*WrappedKey{" - for _, f := range this.Keys { - repeatedStringForKeys += strings.Replace(fmt.Sprintf("%v", f), "WrappedKey", "WrappedKey", 1) + "," - } - repeatedStringForKeys += "}" s := strings.Join([]string{`&Header{`, `ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`, `Salt:` + fmt.Sprintf("%v", this.Salt) + `,`, - `Keys:` + repeatedStringForKeys + `,`, + `Pk:` + fmt.Sprintf("%v", this.Pk) + `,`, + `SenderPk:` + strings.Replace(fmt.Sprintf("%v", this.SenderPk), "Sender", "Sender", 1) + `,`, + `Keys:` + strings.Replace(fmt.Sprintf("%v", this.Keys), "WrappedKey", "WrappedKey", 1) + `,`, + `}`, + }, "") + return s +} +func (this *Sender) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Sender{`, + `Pk:` + fmt.Sprintf("%v", this.Pk) + `,`, `}`, }, "") return s @@ -476,9 +503,6 @@ func (this *WrappedKey) String() string { return "nil" } s := strings.Join([]string{`&WrappedKey{`, - `PkHash:` + fmt.Sprintf("%v", this.PkHash) + `,`, - `Pk:` + fmt.Sprintf("%v", this.Pk) + `,`, - `Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`, `Key:` + fmt.Sprintf("%v", this.Key) + `,`, `}`, }, "") @@ -507,7 +531,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - wire |= uint64(b&0x7F) << shift + wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } @@ -535,7 +559,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ChunkSize |= uint32(b&0x7F) << shift + m.ChunkSize |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } @@ -554,7 +578,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } @@ -563,9 +587,6 @@ func (m *Header) Unmarshal(dAtA []byte) error { return ErrInvalidLengthHdr } postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHdr - } if postIndex > l { return io.ErrUnexpectedEOF } @@ -575,6 +596,70 @@ func (m *Header) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHdr + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHdr + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...) + if m.Pk == nil { + m.Pk = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SenderPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHdr + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHdr + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SenderPk == nil { + m.SenderPk = &Sender{} + } + if err := m.SenderPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType) } @@ -588,7 +673,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } @@ -597,9 +682,6 @@ func (m *Header) Unmarshal(dAtA []byte) error { return ErrInvalidLengthHdr } postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthHdr - } if postIndex > l { return io.ErrUnexpectedEOF } @@ -617,7 +699,85 @@ func (m *Header) Unmarshal(dAtA []byte) error { if skippy < 0 { return ErrInvalidLengthHdr } - if (iNdEx + skippy) < 0 { + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Sender) 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 ErrIntOverflowHdr + } + 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: sender: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: sender: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHdr + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHdr + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...) + if m.Pk == nil { + m.Pk = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHdr(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { return ErrInvalidLengthHdr } if (iNdEx + skippy) > l { @@ -647,7 +807,7 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - wire |= uint64(b&0x7F) << shift + wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } @@ -661,109 +821,7 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: wrapped_key: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PkHash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHdr - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHdr - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHdr - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PkHash = append(m.PkHash[:0], dAtA[iNdEx:postIndex]...) - if m.PkHash == nil { - m.PkHash = []byte{} - } - iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHdr - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHdr - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHdr - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...) - if m.Pk == nil { - m.Pk = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHdr - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHdr - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHdr - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...) - if m.Nonce == nil { - m.Nonce = []byte{} - } - iNdEx = postIndex - case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) } @@ -777,7 +835,7 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } @@ -786,9 +844,6 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { return ErrInvalidLengthHdr } postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHdr - } if postIndex > l { return io.ErrUnexpectedEOF } @@ -806,9 +861,6 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { if skippy < 0 { return ErrInvalidLengthHdr } - if (iNdEx + skippy) < 0 { - return ErrInvalidLengthHdr - } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } @@ -824,7 +876,6 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { func skipHdr(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 { @@ -856,8 +907,10 @@ func skipHdr(dAtA []byte) (n int, err error) { break } } + return iNdEx, nil case 1: iNdEx += 8 + return iNdEx, nil case 2: var length int for shift := uint(0); ; shift += 7 { @@ -874,34 +927,76 @@ func skipHdr(dAtA []byte) (n int, err error) { break } } + iNdEx += length if length < 0 { return 0, ErrInvalidLengthHdr } - iNdEx += length + return iNdEx, nil case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupHdr + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHdr + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipHdr(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next } - depth-- + return iNdEx, nil + case 4: + return iNdEx, nil case 5: iNdEx += 4 + return iNdEx, nil default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } - if iNdEx < 0 { - return 0, ErrInvalidLengthHdr - } - if depth == 0 { - return iNdEx, nil - } } - return 0, io.ErrUnexpectedEOF + panic("unreachable") } var ( - ErrInvalidLengthHdr = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowHdr = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupHdr = fmt.Errorf("proto: unexpected end of group") + ErrInvalidLengthHdr = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHdr = fmt.Errorf("proto: integer overflow") ) + +func init() { proto.RegisterFile("internal/pb/hdr.proto", fileDescriptorHdr) } + +var fileDescriptorHdr = []byte{ + // 265 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0x4f, 0x4a, 0xc3, 0x50, + 0x10, 0xc6, 0x33, 0x69, 0x0d, 0x76, 0xe2, 0x3f, 0x1e, 0x08, 0x6f, 0xe3, 0x18, 0xe2, 0xc2, 0x2c, + 0x24, 0x85, 0x7a, 0x03, 0x4f, 0x20, 0xf1, 0x00, 0x21, 0x31, 0x03, 0x09, 0xaf, 0xa4, 0x8f, 0x97, + 0x88, 0xa4, 0x2b, 0x8f, 0xe0, 0xd6, 0x1b, 0x78, 0x14, 0x97, 0x5d, 0xba, 0x34, 0xcf, 0x8d, 0xcb, + 0x1e, 0x41, 0x9a, 0x54, 0x70, 0x35, 0x1f, 0xbf, 0xf9, 0x86, 0x6f, 0xf8, 0xf0, 0xbc, 0xaa, 0x5b, + 0x36, 0x75, 0xb6, 0x9c, 0xeb, 0x7c, 0x5e, 0x16, 0x26, 0xd6, 0x66, 0xd5, 0xae, 0x84, 0xab, 0xf3, + 0xf0, 0x0d, 0xd0, 0x2b, 0x39, 0x2b, 0xd8, 0x88, 0x0b, 0xc4, 0xc7, 0xf2, 0xa9, 0x56, 0x69, 0x53, + 0xad, 0x59, 0x42, 0x00, 0xd1, 0x71, 0x32, 0x1b, 0xc8, 0x43, 0xb5, 0x66, 0x21, 0x70, 0xda, 0x64, + 0xcb, 0x56, 0xba, 0x01, 0x44, 0x47, 0xc9, 0xa0, 0xc5, 0x09, 0xba, 0x5a, 0xc9, 0xc9, 0x40, 0x5c, + 0xad, 0xc4, 0x35, 0xce, 0x1a, 0xae, 0x0b, 0x36, 0xa9, 0x56, 0x72, 0x1a, 0x40, 0xe4, 0x2f, 0x30, + 0xd6, 0x79, 0x3c, 0xc2, 0xe4, 0x70, 0x9c, 0xf7, 0x4a, 0x5c, 0xe1, 0x54, 0x71, 0xd7, 0xc8, 0x83, + 0x60, 0x12, 0xf9, 0x8b, 0xd3, 0x9d, 0xe7, 0xd9, 0x64, 0x5a, 0x73, 0x91, 0x2a, 0xee, 0x92, 0x61, + 0x19, 0x4a, 0xf4, 0xc6, 0x83, 0x7d, 0x0e, 0xfc, 0xe5, 0x84, 0x97, 0xe8, 0xff, 0xb3, 0x8b, 0x33, + 0x9c, 0x28, 0xee, 0xf6, 0x9f, 0xed, 0xe4, 0xdd, 0xcd, 0xa6, 0x27, 0xe7, 0xb3, 0x27, 0x67, 0xdb, + 0x13, 0xbc, 0x58, 0x82, 0x77, 0x4b, 0xf0, 0x61, 0x09, 0x36, 0x96, 0xe0, 0xcb, 0x12, 0xfc, 0x58, + 0x72, 0xb6, 0x96, 0xe0, 0xf5, 0x9b, 0x9c, 0xdc, 0x1b, 0xfa, 0xb8, 0xfd, 0x0d, 0x00, 0x00, 0xff, + 0xff, 0x7f, 0xa8, 0x12, 0x55, 0x28, 0x01, 0x00, 0x00, +} diff --git a/sign/hdr.proto b/internal/pb/hdr.proto similarity index 66% rename from sign/hdr.proto rename to internal/pb/hdr.proto index 7b7f80b..d01cecb 100644 --- a/sign/hdr.proto +++ b/internal/pb/hdr.proto @@ -2,7 +2,7 @@ syntax="proto3"; //import "gogoproto/gogo.proto" -package sign; +package pb; //option (gogoproto.marshaler_all) = true; //option (gogoproto.sizer_all) = true; @@ -16,9 +16,18 @@ package sign; * protobuf format before writing to disk. */ message header { - uint32 chunk_size = 1; - bytes salt = 2; - repeated wrapped_key keys = 3; + uint32 chunk_size = 1; + bytes salt = 2; + bytes pk = 3; // sender's ephemeral curve PK + sender sender_pk = 4; // sender's encrypted ed25519 PK + repeated wrapped_key keys = 5; +} + +/* + * Sender info is wrapped using the data encryption key + */ +message sender { + bytes pk = 1; } /* @@ -26,8 +35,5 @@ message header { * key. WrappedKey describes such a wrapped key. */ message wrapped_key { - bytes pk_hash = 1; // hash of Ed25519 PK - bytes pk = 2; // curve25519 PK - bytes nonce = 3; // AEAD nonce - bytes key = 4; // AEAD encrypted key + bytes key = 2; } diff --git a/internal/pb/wrap.go b/internal/pb/wrap.go new file mode 100644 index 0000000..9471e68 --- /dev/null +++ b/internal/pb/wrap.go @@ -0,0 +1,97 @@ +// wrap.go - wrap keys and sender as needed +// +// (c) 2016 Sudhi Herle +// +// Licensing Terms: GPLv2 +// +// If you need a commercial license for this work, please contact +// the author. +// +// This software does not come with any express or implied +// warranty; it is provided "as is". No claim is made to its +// suitability for any purpose. +// + +package pb + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "fmt" + "io" +) + +const ( + WrapReceiverNonce = "Receiver PK" + WrapSenderNonce = "Sender PK" +) + +// Wrap sender's PK with the data encryption key +func WrapSenderPK(pk []byte, k, salt []byte) ([]byte, error) { + aes, err := aes.NewCipher(k) + if err != nil { + return nil, fmt.Errorf("wrap: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return nil, fmt.Errorf("wrap: %s", err) + } + + nonce := MakeNonce([]byte(WrapSenderNonce), salt) + buf := make([]byte, ae.Overhead()+len(pk)) + out := ae.Seal(buf[:0], nonce[:ae.NonceSize()], pk, nil) + return out, nil +} + +// Given a wrapped PK of sender 's', unwrap it using the given key and salt +func (s *Sender) UnwrapPK(k, salt []byte) ([]byte, error) { + aes, err := aes.NewCipher(k) + if err != nil { + return nil, fmt.Errorf("uwrap-sender: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return nil, fmt.Errorf("unwrap-sender: %s", err) + } + + nonce := MakeNonce([]byte(WrapSenderNonce), salt) + want := 32 + ae.Overhead() + if len(s.Pk) != want { + return nil, fmt.Errorf("unwrap-sender: incorrect decrypt bytes (need %d, saw %d)", want, 32) + } + + out := make([]byte, 32) + pk, err := ae.Open(out[:0], nonce[:ae.NonceSize()], s.Pk, nil) + if err != nil { + return nil, fmt.Errorf("unwrap-sender: %s", err) + } + + return pk, nil +} + +func MakeNonce(v ...[]byte) []byte { + h := sha256.New() + for _, x := range v { + h.Write(x) + } + return h.Sum(nil)[:] +} + +func Clamp(k []byte) []byte { + k[0] &= 248 + k[31] &= 127 + k[31] |= 64 + return k +} + +func Randread(b []byte) []byte { + _, err := io.ReadFull(rand.Reader, b) + if err != nil { + panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err)) + } + return b +} diff --git a/sign/encrypt.go b/sign/encrypt.go index 0731acb..f5aa0af 100644 --- a/sign/encrypt.go +++ b/sign/encrypt.go @@ -29,10 +29,16 @@ // - Shasum: 32 bytes (SHA256 of full header) // // The variable length segment consists of one or more -// recipients, their wrapped keys etc. This is encoded as +// recipients, each with their wrapped keys. This is encoded as // a protobuf message. This protobuf encoded message immediately // follows the fixed length header. // +// The input data is encrypted with an expanded random 32-byte key: +// - Prefix_string = "Encrypt Nonce" +// - datakey = SHA256(Prefix_string || header_checksum || random_key) +// - The header checksum is mixed in the above process to ensure we +// catch any malicious modification of the header. +// // The input data is broken up into "chunks"; each no larger than // maxChunkSize. The default block size is "chunkSize". Each block // is AEAD encrypted: @@ -50,7 +56,6 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/ed25519" "crypto/sha256" "crypto/sha512" "crypto/subtle" @@ -59,7 +64,8 @@ import ( "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" "io" - "math/big" + + "github.com/opencoff/sigtool/internal/pb" ) // Encryption chunk size = 4MB @@ -76,13 +82,18 @@ const ( // Encryptor holds the encryption context type Encryptor struct { - Header - key [32]byte // file encryption key + pb.Header + key []byte // file encryption key + + ae cipher.AEAD + + // sender ephemeral curve 25519 SK + // the corresponding PK is in Header above + senderSK []byte - ae cipher.AEAD - sender *PrivateKey started bool + hdrsum []byte buf []byte stream bool } @@ -102,29 +113,43 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) { blksz = uint32(blksize) } + csk, cpk, err := newSender() + if err != nil { + return nil, fmt.Errorf("encrypt: %s", err) + } + + key := make([]byte, 32) + salt := make([]byte, _AEADNonceLen) + + pb.Randread(key) + pb.Randread(salt) + + // if sender has provided their identity to authenticate, we will use their PK + senderPK := cpk + if sk != nil { + epk := sk.PublicKey() + senderPK = epk.toCurve25519PK() + } + + wPk, err := pb.WrapSenderPK(senderPK, key, salt) + if err != nil { + return nil, fmt.Errorf("encrypt: %s", err) + } + e := &Encryptor{ - Header: Header{ + Header: pb.Header{ ChunkSize: blksz, - Salt: make([]byte, _AEADNonceLen), + Salt: salt, + Pk: cpk, + SenderPk: &pb.Sender{ + Pk: wPk, + }, }, - sender: sk, + key: key, + senderSK: csk, } - randread(e.key[:]) - randread(e.Salt) - - aes, err := aes.NewCipher(e.key[:]) - if err != nil { - return nil, fmt.Errorf("encrypt: %s", err) - } - - e.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen) - if err != nil { - return nil, fmt.Errorf("encrypt: %s", err) - } - - e.buf = make([]byte, blksz+4+uint32(e.ae.Overhead())) return e, nil } @@ -134,20 +159,12 @@ func (e *Encryptor) AddRecipient(pk *PublicKey) error { return fmt.Errorf("encrypt: can't add new recipient after encryption has started") } - var w *WrappedKey - var err error - - if e.sender != nil { - w, err = e.sender.WrapKey(pk, e.key[:]) - } else { - w, err = pk.WrapKeyEphemeral(e.key[:]) - } - if err != nil { - return err + w, err := wrapKey(pk, e.key, e.senderSK, e.Salt) + if err == nil { + e.Keys = append(e.Keys, w) } - e.Keys = append(e.Keys, w) - return nil + return err } // Encrypt the input stream 'rd' and write encrypted stream to 'wr' @@ -206,7 +223,7 @@ func (e *Encryptor) start(wr io.Writer) error { binary.BigEndian.PutUint32(fixHdr[_MagicLen+1:], uint32(varSize)) // Now marshal the variable portion - _, err := e.MarshalToSizedBuffer(varHdr[:varSize]) + _, err := e.MarshalTo(varHdr[:varSize]) if err != nil { return fmt.Errorf("encrypt: can't marshal header: %s", err) } @@ -222,6 +239,26 @@ func (e *Encryptor) start(wr io.Writer) error { return fmt.Errorf("encrypt: %s", err) } + // we mix the header checksum to create the encryption key + h = sha256.New() + h.Write([]byte("Encrypt Nonce")) + h.Write(e.key) + h.Write(sumHdr) + key := h.Sum(nil) + + aes, err := aes.NewCipher(key) + if err != nil { + return fmt.Errorf("encrypt: %s", err) + } + + ae, err := cipher.NewGCMWithNonceSize(aes, _AEADNonceLen) + if err != nil { + return fmt.Errorf("encrypt: %s", err) + } + + e.buf = make([]byte, e.ChunkSize+4+uint32(ae.Overhead())) + e.ae = ae + e.started = true return nil } @@ -280,11 +317,12 @@ func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error // Decryptor holds the decryption context type Decryptor struct { - Header + pb.Header - ae cipher.AEAD - rd io.Reader - buf []byte + ae cipher.AEAD + rd io.Reader + buf []byte + hdrsum []byte // Decrypted key key []byte @@ -340,10 +378,11 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { } d := &Decryptor{ - rd: rd, + rd: rd, + hdrsum: cksum, } - err = d.Header.Unmarshal(varBuf[:varSize]) + err = d.Unmarshal(varBuf[:varSize]) if err != nil { return nil, fmt.Errorf("decrypt: decode error: %s", err) } @@ -362,23 +401,9 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { // sanity check on the wrapped keys for i, w := range d.Keys { - if len(w.PkHash) != PKHashLength { - return nil, fmt.Errorf("decrypt: wrapped key %d: invalid PkHash", i) + if len(w.Key) <= 32+12 { + return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i) } - - if len(w.Pk) != 32 { - return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Curve25519 PK", i) - } - - // XXX Default AES-256-GCM Nonce size is 12 - if len(w.Nonce) != 12 { - return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Nonce", i) - } - - if len(w.Key) == 0 { - return nil, fmt.Errorf("decrypt: wrapped key %d: missing encrypted key", i) - } - } return d, nil @@ -388,22 +413,45 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { // the sender func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error { var err error + var key []byte - pkh := sk.PublicKey().Hash() for i, w := range d.Keys { - if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 { - d.key, err = w.UnwrapKey(sk, senderPk) - if err != nil { - return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err) - } + key, err = unwrapKey(w.Key, sk, d.Pk, d.Salt) + if err != nil { + return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err) + } + if key != nil { goto havekey } } - return fmt.Errorf("decrypt: Can't find any public key to match the given private key") + return fmt.Errorf("decrypt: wrong key") havekey: - aes, err := aes.NewCipher(d.key) + if senderPk != nil { + hpk, err := d.SenderPk.UnwrapPK(key, d.Salt) + if err != nil { + return fmt.Errorf("decrypt: can't unwrap sender PK: %s", err) + } + + cpk := senderPk.toCurve25519PK() + if subtle.ConstantTimeCompare(cpk, hpk) == 0 { + return fmt.Errorf("decrypt: sender verification failed") + } + } + + // XXX do we need to verify d.Header.Sender.Key vs. d.Header.PK? + + d.key = key + + // we mix the header checksum into the key + h := sha256.New() + h.Write([]byte("Encrypt Nonce")) + h.Write(d.key) + h.Write(d.hdrsum) + key = h.Sum(nil) + + aes, err := aes.NewCipher(key) if err != nil { return fmt.Errorf("decrypt: %s", err) } @@ -416,8 +464,71 @@ havekey: return nil } +// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK +func wrapKey(pk *PublicKey, k, ourSK, salt []byte) (*pb.WrappedKey, error) { + shared, err := curve25519.X25519(ourSK, pk.toCurve25519PK()) + if err != nil { + return nil, fmt.Errorf("wrap: %s", err) + } + + aes, err := aes.NewCipher(shared) + if err != nil { + return nil, fmt.Errorf("wrap: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return nil, fmt.Errorf("wrap: %s", err) + } + + tagsize := ae.Overhead() + + nonce := pb.MakeNonce([]byte(pb.WrapReceiverNonce), salt) + buf := make([]byte, tagsize+len(shared)) + out := ae.Seal(buf[:0], nonce[:ae.NonceSize()], k, pk.Pk) + return &pb.WrappedKey{ + Key: out, + }, nil +} + +// Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and +// senders ephemeral PublicKey +func unwrapKey(wkey []byte, sk *PrivateKey, curvePK, salt []byte) ([]byte, error) { + ourSK := sk.toCurve25519SK() + shared, err := curve25519.X25519(ourSK, curvePK) + if err != nil { + return nil, fmt.Errorf("unwrap: %s", err) + } + + aes, err := aes.NewCipher(shared) + if err != nil { + return nil, fmt.Errorf("unwrap: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return nil, fmt.Errorf("unwrap: %s", err) + } + + want := 32 + ae.Overhead() + if len(wkey) != want { + return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(wkey)) + } + + nonce := pb.MakeNonce([]byte(pb.WrapReceiverNonce), salt) + pk := sk.PublicKey() + out := make([]byte, 32) + c, err := ae.Open(out[:0], nonce[:ae.NonceSize()], wkey, pk.Pk) + + // we indicate incorrect receiver SK by returning a nil key + if err != nil { + return nil, nil + } + return c, nil +} + // Return a list of Wrapped keys in the encrypted file header -func (d *Decryptor) WrappedKeys() []*WrappedKey { +func (d *Decryptor) WrappedKeys() []*pb.WrappedKey { return d.Keys } @@ -510,129 +621,6 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) { return p[:m], eof, nil } -// Wrap a shared key with the recipient's public key 'pk' by generating an ephemeral -// Curve25519 keypair. This function does not identify the sender (non-repudiation). -func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) { - var newSK [32]byte - - randread(newSK[:]) - clamp(newSK[:]) - - return wrapKey(pk, key, newSK[:]) -} - -// given a file-encryption-key, wrap it in the identity of the recipient 'pk' using our -// secret key. This function identifies the sender. -func (sk *PrivateKey) WrapKey(pk *PublicKey, key []byte) (*WrappedKey, error) { - return wrapKey(pk, key, sk.toCurve25519SK()) -} - -func wrapKey(pk *PublicKey, k []byte, ourSK []byte) (*WrappedKey, error) { - curvePK, err := curve25519.X25519(ourSK, curve25519.Basepoint) - if err != nil { - return nil, fmt.Errorf("wrap: %s", err) - } - - shared, err := curve25519.X25519(ourSK, pk.toCurve25519PK()) - if err != nil { - return nil, fmt.Errorf("wrap: %s", err) - } - - ek, nonce, err := aeadSeal(k, shared, pk.Pk) - if err != nil { - return nil, fmt.Errorf("wrap: %s", err) - } - - return &WrappedKey{ - PkHash: pk.hash, - Pk: curvePK, - Nonce: nonce, - Key: ek, - }, nil -} - -// Unwrap a wrapped key using the private key 'sk' -func (w *WrappedKey) UnwrapKey(sk *PrivateKey, senderPk *PublicKey) ([]byte, error) { - ourSK := sk.toCurve25519SK() - shared, err := curve25519.X25519(ourSK, w.Pk) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - - if senderPk != nil { - shared2, err := curve25519.X25519(ourSK, senderPk.toCurve25519PK()) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - - if subtle.ConstantTimeCompare(shared2, shared) != 1 { - return nil, fmt.Errorf("unwrap: sender validation failed") - } - } - - pk := sk.PublicKey() - key, err := aeadOpen(w.Key, w.Nonce, shared[:], pk.Pk) - if err != nil { - return nil, err - } - return key, nil -} - -// Convert an Ed25519 Private Key to Curve25519 Private key -func (sk *PrivateKey) toCurve25519SK() []byte { - if sk.ck == nil { - var ek [64]byte - - h := sha512.New() - h.Write(sk.Sk[:32]) - h.Sum(ek[:0]) - - sk.ck = clamp(ek[:32]) - } - - return sk.ck -} - -// from github.com/FiloSottile/age -var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) - -// Convert an Ed25519 Public Key to Curve25519 public key -// from github.com/FiloSottile/age -func (pk *PublicKey) toCurve25519PK() []byte { - if pk.ck != nil { - return pk.ck - } - - // ed25519.PublicKey is a little endian representation of the y-coordinate, - // with the most significant bit set based on the sign of the x-ccordinate. - bigEndianY := make([]byte, ed25519.PublicKeySize) - for i, b := range pk.Pk { - bigEndianY[ed25519.PublicKeySize-i-1] = b - } - bigEndianY[0] &= 0b0111_1111 - - // The Montgomery u-coordinate is derived through the bilinear map - // - // u = (1 + y) / (1 - y) - // - // See https://blog.filippo.io/using-ed25519-keys-for-encryption. - y := new(big.Int).SetBytes(bigEndianY) - denom := big.NewInt(1) - denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) - u := y.Mul(y.Add(y, big.NewInt(1)), denom) - u.Mod(u, curve25519P) - - out := make([]byte, 32) - uBytes := u.Bytes() - n := len(uBytes) - for i, b := range uBytes { - out[n-i-1] = b - } - - pk.ck = out - return out -} - // generate a KEK from a shared DH key and a Pub Key func expand(shared, pk []byte) ([]byte, error) { kek := make([]byte, 32) @@ -641,67 +629,12 @@ func expand(shared, pk []byte) ([]byte, error) { return kek, err } -// seal the data via AEAD after suitably expanding 'shared' -func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) { - kek, err := expand(shared[:], pk) - if err != nil { - return nil, nil, fmt.Errorf("wrap: %s", err) - } +func newSender() (sk, pk []byte, err error) { + var csk [32]byte - aes, err := aes.NewCipher(kek) - if err != nil { - return nil, nil, fmt.Errorf("wrap: %s", err) - } - - ae, err := cipher.NewGCM(aes) - if err != nil { - return nil, nil, fmt.Errorf("wrap: %s", err) - } - - noncesize := ae.NonceSize() - tagsize := ae.Overhead() - - buf := make([]byte, tagsize+len(kek)) - nonce := make([]byte, noncesize) - - randread(nonce) - - out := ae.Seal(buf[:0], nonce, data, nil) - return out, nonce, nil -} - -func aeadOpen(data, nonce, shared, pk []byte) ([]byte, error) { - // hkdf or HMAC-sha-256 - kek, err := expand(shared, pk) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - aes, err := aes.NewCipher(kek) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - - ae, err := cipher.NewGCM(aes) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - - want := 32 + ae.Overhead() - if len(data) != want { - return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(data)) - } - - c, err := ae.Open(data[:0], nonce, data, nil) - if err != nil { - return nil, fmt.Errorf("unwrap: %s", err) - } - - return c, nil -} - -func clamp(k []byte) []byte { - k[0] &= 248 - k[31] &= 127 - k[31] |= 64 - return k + pb.Randread(csk[:]) + pb.Clamp(csk[:]) + pk, err = curve25519.X25519(csk[:], curve25519.Basepoint) + sk = csk[:] + return } diff --git a/sign/encrypt_test.go b/sign/encrypt_test.go index a89e8f5..4283e10 100644 --- a/sign/encrypt_test.go +++ b/sign/encrypt_test.go @@ -154,6 +154,13 @@ func TestEncryptSenderVerified(t *testing.T) { dd, err := NewDecryptor(rd) assert(err == nil, "decryptor create fail: %s", err) + // first send a wrong sender key + randkey, err := NewKeypair() + assert(err == nil, "receiver rand keypair gen failed: %s", err) + + err = dd.SetPrivateKey(&receiver.Sec, &randkey.Pub) + assert(err != nil, "decryptor failed to verify sender") + err = dd.SetPrivateKey(&receiver.Sec, &sender.Pub) assert(err == nil, "decryptor can't add SK: %s", err) diff --git a/sign/keys.go b/sign/keys.go new file mode 100644 index 0000000..390459e --- /dev/null +++ b/sign/keys.go @@ -0,0 +1,548 @@ +// keys.go -- Ed25519 keys management +// +// (c) 2016 Sudhi Herle +// +// Licensing Terms: GPLv2 +// +// If you need a commercial license for this work, please contact +// the author. +// +// This software does not come with any express or implied +// warranty; it is provided "as is". No claim is made to its +// suitability for any purpose. + +// This file implements: +// - key generation, and key I/O +// - sign/verify of files and byte strings + +package sign + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/binary" + "fmt" + "hash" + "io/ioutil" + "math/big" + "os" + + Ed "crypto/ed25519" + "golang.org/x/crypto/scrypt" + "gopkg.in/yaml.v2" + + "github.com/opencoff/go-utils" + "github.com/opencoff/sigtool/internal/pb" +) + +// Private Ed25519 key +type PrivateKey struct { + Sk []byte + + // Encryption key: Curve25519 point corresponding to this Ed25519 key + ck []byte + + // Cached copy of the public key + pk *PublicKey +} + +// Public Ed25519 key +type PublicKey struct { + Pk []byte + + // Comment string + Comment string + + // Curve25519 point corresponding to this Ed25519 key + ck []byte + + hash []byte +} + +// Ed25519 key pair +type Keypair struct { + Sec PrivateKey + Pub PublicKey +} + +// An Ed25519 Signature +type Signature struct { + Sig []byte // Ed25519 sig bytes + pkhash []byte // [0:16] SHA256 hash of public key needed for verification +} + +// Length of Ed25519 Public Key Hash +const PKHashLength = 16 + +const ( + // Scrypt parameters + _N int = 1 << 19 + _r int = 8 + _p int = 1 + + // Algorithm used in the encrypted private key + sk_algo = "scrypt-sha256" + sig_algo = "sha512-ed25519" +) + +// Encrypted Private key +type serializedPrivKey struct { + Comment string `yaml:"comment,omitempty"` + + // Encrypted Sk + Esk string `yaml:"esk"` + Salt string `yaml:"salt,omitempty"` + + // Algorithm used for checksum and KDF + Algo string `yaml:"algo,omitempty"` + + // These are params for scrypt.Key() + // CPU Cost parameter; must be a power of 2 + N int `yaml:"Z,flow,omitempty"` + + // r * p should be less than 2^30 + R int `yaml:"r,flow,omitempty"` + P int `yaml:"p,flow,omitempty"` +} + +// serialized representation of public key +type serializedPubKey struct { + Comment string `yaml:"comment,omitempty"` + Pk string `yaml:"pk"` + Hash string `yaml:"hash"` +} + +// Serialized signature +type signature struct { + Comment string `yaml:"comment,omitempty"` + Pkhash string `yaml:"pkhash,omitempty"` + Signature string `yaml:"signature"` +} + +func pkhash(pk []byte) []byte { + z := sha256.Sum256(pk) + return z[:PKHashLength] +} + +// Generate a new Ed25519 keypair +func NewKeypair() (*Keypair, error) { + //kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}} + kp := &Keypair{} + sk := &kp.Sec + pk := &kp.Pub + sk.pk = pk + + p, s, err := Ed.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err) + } + + pk.Pk = []byte(p) + sk.Sk = []byte(s) + pk.hash = pkhash(pk.Pk) + + return kp, nil +} + +// Serialize the keypair to two separate files. The basename of the +// file is 'bn'; the public key goes in $bn.pub and the private key +// goes in $bn.key. +// If password is non-empty, then the private key is encrypted +// before writing to disk. +func (kp *Keypair) Serialize(bn, comment string, getpw func() ([]byte, error)) error { + + sk := &kp.Sec + pk := &kp.Pub + + skf := fmt.Sprintf("%s.key", bn) + pkf := fmt.Sprintf("%s.pub", bn) + + err := pk.serialize(pkf, comment) + if err != nil { + return fmt.Errorf("Can't serialize to %s: %s", pkf, err) + } + + err = sk.serialize(skf, comment, getpw) + if err != nil { + return fmt.Errorf("Can't serialize to %s: %s", pkf, err) + } + + return nil +} + +// Read the private key in 'fn', optionally decrypting it using +// password 'pw' and create new instance of PrivateKey +func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) { + yml, err := ioutil.ReadFile(fn) + if err != nil { + return nil, err + } + + if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 { + return parseSSHPrivateKey(yml, getpw) + } + + if pw, err := getpw(); err == nil { + return MakePrivateKey(yml, pw) + } + return nil, err +} + +// Make a private key from bytes 'yml' and password 'pw'. The bytes +// are assumed to be serialized version of the private key. +func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) { + var ssk serializedPrivKey + + err := yaml.Unmarshal(yml, &ssk) + if err != nil { + return nil, fmt.Errorf("make priv key: can't parse YAML: %s", err) + } + + if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 { + return nil, fmt.Errorf("sign: not YAML private key") + } + + b64 := base64.StdEncoding.DecodeString + + salt, err := b64(ssk.Salt) + if err != nil { + return nil, fmt.Errorf("make priv key: can't decode salt: %s", err) + } + + esk, err := b64(ssk.Esk) + if err != nil { + return nil, fmt.Errorf("make priv key: can't decode key: %s", err) + } + + // We take short passwords and extend them + pwb := sha512.Sum512(pw) + + // "32" == Length of AES-256 key + key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32) + if err != nil { + return nil, fmt.Errorf("make priv key: can't derive key: %s", err) + } + + aes, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("make priv key: aes failure: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return nil, fmt.Errorf("make priv key: aes failure: %s", err) + } + + skb, err := ae.Open(nil, salt[:ae.NonceSize()], esk, nil) + if err != nil { + return nil, fmt.Errorf("make priv key: wrong password") + } + + return PrivateKeyFromBytes(skb) +} + +// Make a private key from 64-bytes of extended Ed25519 key +func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) { + if len(buf) != 64 { + return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf)) + } + + skb := make([]byte, 64) + copy(skb, buf) + + edsk := Ed.PrivateKey(skb) + edpk := edsk.Public().(Ed.PublicKey) + + pk := &PublicKey{ + Pk: []byte(edpk), + hash: pkhash([]byte(edpk)), + } + sk := &PrivateKey{ + Sk: skb, + pk: pk, + } + + return sk, nil +} + +// Given a secret key, return the corresponding Public Key +func (sk *PrivateKey) PublicKey() *PublicKey { + return sk.pk +} + +// Convert an Ed25519 Private Key to Curve25519 Private key +func (sk *PrivateKey) toCurve25519SK() []byte { + if sk.ck == nil { + var ek [64]byte + + h := sha512.New() + h.Write(sk.Sk[:32]) + h.Sum(ek[:0]) + + sk.ck = clamp(ek[:32]) + } + + return sk.ck +} + +// from github.com/FiloSottile/age +var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) + +// Convert an Ed25519 Public Key to Curve25519 public key +// from github.com/FiloSottile/age +func (pk *PublicKey) toCurve25519PK() []byte { + if pk.ck != nil { + return pk.ck + } + + // ed25519.PublicKey is a little endian representation of the y-coordinate, + // with the most significant bit set based on the sign of the x-ccordinate. + bigEndianY := make([]byte, Ed.PublicKeySize) + for i, b := range pk.Pk { + bigEndianY[Ed.PublicKeySize-i-1] = b + } + bigEndianY[0] &= 0b0111_1111 + + // The Montgomery u-coordinate is derived through the bilinear map + // + // u = (1 + y) / (1 - y) + // + // See https://blog.filippo.io/using-ed25519-keys-for-encryption. + y := new(big.Int).SetBytes(bigEndianY) + denom := big.NewInt(1) + denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) + u := y.Mul(y.Add(y, big.NewInt(1)), denom) + u.Mod(u, curve25519P) + + out := make([]byte, 32) + uBytes := u.Bytes() + n := len(uBytes) + for i, b := range uBytes { + out[n-i-1] = b + } + + pk.ck = out + return out +} + +// Public Key Hash +func (pk *PublicKey) Hash() []byte { + return pk.hash +} + +// Serialize the private key to a file +// AEAD encryption for protecting the private key +// Format: YAML +// All []byte are in base64 (RawEncoding) +func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error { + pw, err := getpw() + if err != nil { + return err + } + + // expand the password into 64 bytes + pass := sha512.Sum512(pw) + salt := make([]byte, 32) + + pb.Randread(salt) + + // "32" == Length of AES-256 key + key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32) + if err != nil { + return fmt.Errorf("marshal: can't derive scrypt key: %s", err) + } + + aes, err := aes.NewCipher(key) + if err != nil { + return fmt.Errorf("marshal: %s", err) + } + + ae, err := cipher.NewGCM(aes) + if err != nil { + return fmt.Errorf("marshal: %s", err) + } + + tl := ae.Overhead() + buf := make([]byte, tl+len(sk.Sk)) + esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil) + + enc := base64.StdEncoding.EncodeToString + + ssk := serializedPrivKey{ + Comment: comment, + Esk: enc(esk), + Salt: enc(salt), + Algo: sk_algo, + N: _N, + R: _r, + P: _p, + } + + // We won't protect the Scrypt parameters with the hash above + // because it is not needed. If the parameters are wrong, the + // derived key will be wrong and thus, the hash will not match. + + out, err := yaml.Marshal(&ssk) + if err != nil { + return fmt.Errorf("can't marahal to YAML: %s", err) + } + + return writeFile(fn, out, 0600) +} + +// --- Public Key Methods --- + +// Read the public key from 'fn' and create new instance of +// PublicKey +func ReadPublicKey(fn string) (*PublicKey, error) { + var err error + var yml []byte + + if yml, err = ioutil.ReadFile(fn); err != nil { + return nil, err + } + + // first try to parse as a ssh key + pk, err := parseSSHPublicKey(yml) + if err != nil { + pk, err = MakePublicKey(yml) + } + return pk, err +} + +// Parse a serialized public in 'yml' and return the resulting +// public key instance +func MakePublicKey(yml []byte) (*PublicKey, error) { + var spk serializedPubKey + var err error + + if err = yaml.Unmarshal(yml, &spk); err != nil { + return nil, fmt.Errorf("can't parse YAML: %s", err) + } + + if len(spk.Pk) == 0 { + return nil, fmt.Errorf("sign: not a YAML public key") + } + + b64 := base64.StdEncoding.DecodeString + var pkb []byte + + if pkb, err = b64(spk.Pk); err != nil { + return nil, fmt.Errorf("can't decode YAML:Pk: %s", err) + } + + if pk, err := PublicKeyFromBytes(pkb); err == nil { + pk.Comment = spk.Comment + return pk, nil + } + return nil, err +} + +// Make a public key from a byte string +func PublicKeyFromBytes(b []byte) (*PublicKey, error) { + if len(b) != 32 { + return nil, fmt.Errorf("public key is malformed (len %d!)", len(b)) + } + + pk := &PublicKey{ + Pk: make([]byte, 32), + hash: pkhash(b), + } + + copy(pk.Pk, b) + return pk, nil +} + +// Serialize Public Keys +func (pk *PublicKey) serialize(fn, comment string) error { + b64 := base64.StdEncoding.EncodeToString + spk := &serializedPubKey{ + Comment: comment, + Pk: b64(pk.Pk), + Hash: b64(pk.hash), + } + + out, err := yaml.Marshal(spk) + if err != nil { + return fmt.Errorf("can't marahal to YAML: %s", err) + } + + return writeFile(fn, out, 0644) +} + +// -- Internal Utility Functions -- + +// Unlink a file. +func unlink(f string) { + st, err := os.Stat(f) + if err == nil { + if !st.Mode().IsRegular() { + panic(fmt.Sprintf("%s can't be unlinked. Not a regular file?", f)) + } + + os.Remove(f) + return + } +} + +// Simple function to reliably write data to a file. +// Does MORE than ioutil.WriteFile() - in that it doesn't trash the +// existing file with an incomplete write. +func writeFile(fn string, b []byte, mode uint32) error { + tmp := fmt.Sprintf("%s.tmp", fn) + unlink(tmp) + + fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode)) + if err != nil { + return fmt.Errorf("Can't create file %s: %s", tmp, err) + } + + _, err = fd.Write(b) + if err != nil { + fd.Close() + // XXX Do we delete the tmp file? + return fmt.Errorf("Can't write %v bytes to %s: %s", len(b), tmp, err) + } + + fd.Close() // we ignore close(2) errors; unrecoverable anyway. + + os.Rename(tmp, fn) + return nil +} + +// Generate file checksum out of hash function h +func fileCksum(fn string, h hash.Hash) ([]byte, error) { + + fd, err := os.Open(fn) + if err != nil { + return nil, fmt.Errorf("can't open %s: %s", fn, err) + } + + defer fd.Close() + + sz, err := utils.MmapReader(fd, 0, 0, h) + if err != nil { + return nil, err + } + + var b [8]byte + binary.BigEndian.PutUint64(b[:], uint64(sz)) + h.Write(b[:]) + + return h.Sum(nil), nil +} + +func clamp(k []byte) []byte { + k[0] &= 248 + k[31] &= 127 + k[31] |= 64 + return k +} + +// EOF +// vim: noexpandtab:ts=8:sw=8:tw=92: diff --git a/sign/sign.go b/sign/sign.go index 7c431c8..a17ba52 100644 --- a/sign/sign.go +++ b/sign/sign.go @@ -18,332 +18,29 @@ package sign import ( - "bytes" "crypto" - "crypto/aes" - "crypto/cipher" "crypto/rand" - "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/base64" - "encoding/binary" "fmt" - "hash" - "io" "io/ioutil" - "os" Ed "crypto/ed25519" - "golang.org/x/crypto/scrypt" "gopkg.in/yaml.v2" - - "github.com/opencoff/go-utils" ) -// Private Ed25519 key -type PrivateKey struct { - Sk []byte - - // Encryption key: Curve25519 point corresponding to this Ed25519 key - ck []byte - - // Cached copy of the public key - pk *PublicKey -} - -// Public Ed25519 key -type PublicKey struct { - Pk []byte - - // Comment string - Comment string - - // Curve25519 point corresponding to this Ed25519 key - ck []byte - - hash []byte -} - -// Ed25519 key pair -type Keypair struct { - Sec PrivateKey - Pub PublicKey -} - -// An Ed25519 Signature -type Signature struct { - Sig []byte // Ed25519 sig bytes - pkhash []byte // [0:16] SHA256 hash of public key needed for verification -} - -// Length of Ed25519 Public Key Hash -const PKHashLength = 16 - -const ( - // Scrypt parameters - _N int = 1 << 19 - _r int = 8 - _p int = 1 - - // Algorithm used in the encrypted private key - sk_algo = "scrypt-sha256" - sig_algo = "sha512-ed25519" -) - -// Encrypted Private key -type serializedPrivKey struct { - Comment string `yaml:"comment,omitempty"` - - // Encrypted Sk - Esk string `yaml:"esk"` - Salt string `yaml:"salt,omitempty"` - - // Algorithm used for checksum and KDF - Algo string `yaml:"algo,omitempty"` - - // These are params for scrypt.Key() - // CPU Cost parameter; must be a power of 2 - N int `yaml:"Z,flow,omitempty"` - - // r * p should be less than 2^30 - R int `yaml:"r,flow,omitempty"` - P int `yaml:"p,flow,omitempty"` -} - -// serialized representation of public key -type serializedPubKey struct { - Comment string `yaml:"comment,omitempty"` - Pk string `yaml:"pk"` - Hash string `yaml:"hash"` -} - -// Serialized signature -type signature struct { - Comment string `yaml:"comment,omitempty"` - Pkhash string `yaml:"pkhash,omitempty"` - Signature string `yaml:"signature"` -} - -func pkhash(pk []byte) []byte { - z := sha256.Sum256(pk) - return z[:PKHashLength] -} - -// Generate a new Ed25519 keypair -func NewKeypair() (*Keypair, error) { - //kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}} - kp := &Keypair{} - sk := &kp.Sec - pk := &kp.Pub - sk.pk = pk - - p, s, err := Ed.GenerateKey(rand.Reader) - if err != nil { - return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err) - } - - pk.Pk = []byte(p) - sk.Sk = []byte(s) - pk.hash = pkhash(pk.Pk) - - return kp, nil -} - -// Serialize the keypair to two separate files. The basename of the -// file is 'bn'; the public key goes in $bn.pub and the private key -// goes in $bn.key. -// If password is non-empty, then the private key is encrypted -// before writing to disk. -func (kp *Keypair) Serialize(bn, comment string, getpw func() ([]byte, error)) error { - - sk := &kp.Sec - pk := &kp.Pub - - skf := fmt.Sprintf("%s.key", bn) - pkf := fmt.Sprintf("%s.pub", bn) - - err := pk.serialize(pkf, comment) - if err != nil { - return fmt.Errorf("Can't serialize to %s: %s", pkf, err) - } - - err = sk.serialize(skf, comment, getpw) - if err != nil { - return fmt.Errorf("Can't serialize to %s: %s", pkf, err) - } - - return nil -} - -// Read the private key in 'fn', optionally decrypting it using -// password 'pw' and create new instance of PrivateKey -func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) { - yml, err := ioutil.ReadFile(fn) - if err != nil { - return nil, err - } - - if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 { - return parseSSHPrivateKey(yml, getpw) - } - - if pw, err := getpw(); err == nil { - return MakePrivateKey(yml, pw) - } - return nil, err -} - -// Make a private key from bytes 'yml' and password 'pw'. The bytes -// are assumed to be serialized version of the private key. -func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) { - var ssk serializedPrivKey - - err := yaml.Unmarshal(yml, &ssk) - if err != nil { - return nil, fmt.Errorf("make priv key: can't parse YAML: %s", err) - } - - b64 := base64.StdEncoding.DecodeString - - salt, err := b64(ssk.Salt) - if err != nil { - return nil, fmt.Errorf("make priv key: can't decode salt: %s", err) - } - - esk, err := b64(ssk.Esk) - if err != nil { - return nil, fmt.Errorf("make priv key: can't decode key: %s", err) - } - - // We take short passwords and extend them - pwb := sha512.Sum512(pw) - - // "32" == Length of AES-256 key - key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32) - if err != nil { - return nil, fmt.Errorf("make priv key: can't derive key: %s", err) - } - - aes, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("make priv key: aes failure: %s", err) - } - - ae, err := cipher.NewGCM(aes) - if err != nil { - return nil, fmt.Errorf("make priv key: aes failure: %s", err) - } - - skb, err := ae.Open(nil, salt[:ae.NonceSize()], esk, nil) - if err != nil { - return nil, fmt.Errorf("make priv key: wrong password") - } - - return PrivateKeyFromBytes(skb) -} - -// Make a private key from 64-bytes of extended Ed25519 key -func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) { - if len(buf) != 64 { - return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf)) - } - - skb := make([]byte, 64) - copy(skb, buf) - - edsk := Ed.PrivateKey(skb) - edpk := edsk.Public().(Ed.PublicKey) - - pk := &PublicKey{ - Pk: []byte(edpk), - hash: pkhash([]byte(edpk)), - } - sk := &PrivateKey{ - Sk: skb, - pk: pk, - } - - return sk, nil -} - -// Given a secret key, return the corresponding Public Key -func (sk *PrivateKey) PublicKey() *PublicKey { - return sk.pk -} - -// Public Key Hash -func (pk *PublicKey) Hash() []byte { - return pk.hash -} - -// Serialize the private key to a file -// AEAD encryption for protecting the private key -// Format: YAML -// All []byte are in base64 (RawEncoding) -func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error { - pw, err := getpw() - if err != nil { - return err - } - - // expand the password into 64 bytes - pass := sha512.Sum512(pw) - salt := make([]byte, 32) - - randread(salt) - - // "32" == Length of AES-256 key - key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32) - if err != nil { - return fmt.Errorf("marshal: can't derive scrypt key: %s", err) - } - - aes, err := aes.NewCipher(key) - if err != nil { - return fmt.Errorf("marshal: %s", err) - } - - ae, err := cipher.NewGCM(aes) - if err != nil { - return fmt.Errorf("marshal: %s", err) - } - - tl := ae.Overhead() - buf := make([]byte, tl+len(sk.Sk)) - esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil) - - enc := base64.StdEncoding.EncodeToString - - ssk := serializedPrivKey{ - Comment: comment, - Esk: enc(esk), - Salt: enc(salt), - Algo: sk_algo, - N: _N, - R: _r, - P: _p, - } - - // We won't protect the Scrypt parameters with the hash above - // because it is not needed. If the parameters are wrong, the - // derived key will be wrong and thus, the hash will not match. - - out, err := yaml.Marshal(&ssk) - if err != nil { - return fmt.Errorf("can't marahal to YAML: %s", err) - } - - return writeFile(fn, out, 0600) -} - // Sign a prehashed Message; return the signature as opaque bytes // Signature is an YAML file: // Comment: source file path // Signature: Ed25519 signature func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) { - x := Ed.PrivateKey(sk.Sk) + h := sha512.New() + h.Write([]byte("sigtool signed message")) + h.Write(ck) + ck = h.Sum(nil)[:] + x := Ed.PrivateKey(sk.Sk) sig, err := x.Sign(rand.Reader, ck, crypto.Hash(0)) if err != nil { return nil, fmt.Errorf("can't sign %x: %s", ck, err) @@ -441,82 +138,6 @@ func (sig *Signature) IsPKMatch(pk *PublicKey) bool { return subtle.ConstantTimeCompare(pk.hash, sig.pkhash) == 1 } -// --- Public Key Methods --- - -// Read the public key from 'fn' and create new instance of -// PublicKey -func ReadPublicKey(fn string) (*PublicKey, error) { - var err error - var yml []byte - - if yml, err = ioutil.ReadFile(fn); err != nil { - return nil, err - } - - // first try to parse as a ssh key - pk, err := parseSSHPublicKey(yml) - if err != nil { - pk, err = MakePublicKey(yml) - } - return pk, err -} - -// Parse a serialized public in 'yml' and return the resulting -// public key instance -func MakePublicKey(yml []byte) (*PublicKey, error) { - var spk serializedPubKey - var err error - - if err = yaml.Unmarshal(yml, &spk); err != nil { - return nil, fmt.Errorf("can't parse YAML: %s", err) - } - - b64 := base64.StdEncoding.DecodeString - var pkb []byte - - if pkb, err = b64(spk.Pk); err != nil { - return nil, fmt.Errorf("can't decode YAML:Pk: %s", err) - } - - if pk, err := PublicKeyFromBytes(pkb); err == nil { - pk.Comment = spk.Comment - return pk, nil - } - return nil, err -} - -// Make a public key from a byte string -func PublicKeyFromBytes(b []byte) (*PublicKey, error) { - if len(b) != 32 { - return nil, fmt.Errorf("public key is malformed (len %d!)", len(b)) - } - - pk := &PublicKey{ - Pk: make([]byte, 32), - hash: pkhash(b), - } - - copy(pk.Pk, b) - return pk, nil -} - -// Serialize Public Keys -func (pk *PublicKey) serialize(fn, comment string) error { - b64 := base64.StdEncoding.EncodeToString - spk := &serializedPubKey{ - Comment: comment, - Pk: b64(pk.Pk), - Hash: b64(pk.hash), - } - - out, err := yaml.Marshal(spk) - if err != nil { - return fmt.Errorf("can't marahal to YAML: %s", err) - } - - return writeFile(fn, out, 0644) -} - // Verify a signature 'sig' for file 'fn' against public key 'pk' // Return True if signature matches, False otherwise func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) { @@ -532,80 +153,13 @@ func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) { // Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk' // Return True if signature matches, False otherwise func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) (bool, error) { + h := sha512.New() + h.Write([]byte("sigtool signed message")) + h.Write(ck) + ck = h.Sum(nil)[:] x := Ed.PublicKey(pk.Pk) return Ed.Verify(x, ck, sig.Sig), nil } -// -- Internal Utility Functions -- - -// Unlink a file. -func unlink(f string) { - st, err := os.Stat(f) - if err == nil { - if !st.Mode().IsRegular() { - panic(fmt.Sprintf("%s can't be unlinked. Not a regular file?", f)) - } - - os.Remove(f) - return - } -} - -// Simple function to reliably write data to a file. -// Does MORE than ioutil.WriteFile() - in that it doesn't trash the -// existing file with an incomplete write. -func writeFile(fn string, b []byte, mode uint32) error { - tmp := fmt.Sprintf("%s.tmp", fn) - unlink(tmp) - - fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode)) - if err != nil { - return fmt.Errorf("Can't create file %s: %s", tmp, err) - } - - _, err = fd.Write(b) - if err != nil { - fd.Close() - // XXX Do we delete the tmp file? - return fmt.Errorf("Can't write %v bytes to %s: %s", len(b), tmp, err) - } - - fd.Close() // we ignore close(2) errors; unrecoverable anyway. - - os.Rename(tmp, fn) - return nil -} - -// Generate file checksum out of hash function h -func fileCksum(fn string, h hash.Hash) ([]byte, error) { - - fd, err := os.Open(fn) - if err != nil { - return nil, fmt.Errorf("can't open %s: %s", fn, err) - } - - defer fd.Close() - - sz, err := utils.MmapReader(fd, 0, 0, h) - if err != nil { - return nil, err - } - - var b [8]byte - binary.BigEndian.PutUint64(b[:], uint64(sz)) - h.Write(b[:]) - - return h.Sum(nil), nil -} - -func randread(b []byte) []byte { - _, err := io.ReadFull(rand.Reader, b) - if err != nil { - panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err)) - } - return b -} - -// EOF // vim: noexpandtab:ts=8:sw=8:tw=92: diff --git a/sign/sign_test.go b/sign/sign_test.go index 70e4159..14106c7 100644 --- a/sign/sign_test.go +++ b/sign/sign_test.go @@ -19,6 +19,8 @@ import ( "os" "path" "testing" + + "github.com/opencoff/sigtool/internal/pb" ) // Return a temp dir in a temp-dir @@ -28,7 +30,7 @@ func tempdir(t *testing.T) string { var b [10]byte dn := os.TempDir() - randread(b[:]) + pb.Randread(b[:]) tmp := path.Join(dn, fmt.Sprintf("%x", b[:])) err := os.MkdirAll(tmp, 0755) @@ -135,7 +137,7 @@ func TestSignRandBuf(t *testing.T) { var ck [64]byte // simulates sha512 sum - randread(ck[:]) + pb.Randread(ck[:]) pk := &kp.Pub sk := &kp.Sec @@ -148,7 +150,7 @@ func TestSignRandBuf(t *testing.T) { assert(ss.IsPKMatch(pk), "pk match fail") // Corrupt the pkhash and see - randread(ss.pkhash) + pb.Randread(ss.pkhash) assert(!ss.IsPKMatch(pk), "corrupt pk match fail") // Incorrect checksum == should fail verification @@ -185,7 +187,7 @@ func TestSignRandBuf(t *testing.T) { assert(err == nil, "file.dat creat file") for i := 0; i < 8; i++ { - randread(buf[:]) + pb.Randread(buf[:]) n, err := fd.Write(buf[:]) assert(err == nil, fmt.Sprintf("file.dat write fail: %s", err)) assert(n == 8192, fmt.Sprintf("file.dat i/o fail: exp 8192 saw %v", n)) @@ -286,7 +288,7 @@ func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) { func randbuf(sz uint) []byte { b := make([]byte, sz) - randread(b) + pb.Randread(b) return b } diff --git a/version b/version index f374f66..3eefcb9 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.9.1 +1.0.0