Add sender authenticated message integrity; fixup KDF
- use HKDF for producing keys, nonces - add running hmac of plaintext; sender-sign the hmac as trailer - use header checksum as "salt" for data encryption keys, nonces - generate explicit nonce for wrapping root keys for each recipient (previous impl had brittleness)
This commit is contained in:
parent
a428db8feb
commit
f343d45a8e
6 changed files with 495 additions and 249 deletions
|
@ -34,7 +34,7 @@ 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"`
|
||||
Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"`
|
||||
SenderSign []byte `protobuf:"bytes,4,opt,name=sender_sign,json=senderSign,proto3" json:"sender_sign,omitempty"`
|
||||
Sender []byte `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"`
|
||||
Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -91,9 +91,9 @@ func (m *Header) GetPk() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Header) GetSenderSign() []byte {
|
||||
func (m *Header) GetSender() []byte {
|
||||
if m != nil {
|
||||
return m.SenderSign
|
||||
return m.Sender
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ func (m *Header) GetKeys() []*WrappedKey {
|
|||
// key. WrappedKey describes such a wrapped key.
|
||||
type WrappedKey struct {
|
||||
DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"`
|
||||
Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
}
|
||||
|
||||
func (m *WrappedKey) Reset() { *m = WrappedKey{} }
|
||||
|
@ -151,6 +152,13 @@ func (m *WrappedKey) GetDKey() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) GetNonce() []byte {
|
||||
if m != nil {
|
||||
return m.Nonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Header)(nil), "pb.header")
|
||||
proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key")
|
||||
|
@ -159,24 +167,24 @@ func init() {
|
|||
func init() { proto.RegisterFile("internal/pb/hdr.proto", fileDescriptor_c715362029a696e2) }
|
||||
|
||||
var fileDescriptor_c715362029a696e2 = []byte{
|
||||
// 257 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xcf, 0x31, 0x4e, 0xeb, 0x40,
|
||||
0x10, 0xc6, 0xf1, 0x1d, 0xc7, 0x89, 0xf4, 0x26, 0x79, 0x20, 0x2d, 0x42, 0x72, 0xc3, 0x60, 0x99,
|
||||
0xc6, 0x95, 0x23, 0x01, 0x27, 0xa0, 0xa5, 0x73, 0x0e, 0x60, 0xd9, 0x78, 0x14, 0x5b, 0x8e, 0x36,
|
||||
0xab, 0xb5, 0x11, 0x72, 0x2a, 0x8e, 0x00, 0xb7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75, 0x43,
|
||||
0x99, 0x23, 0x20, 0x2d, 0x0d, 0xdd, 0xa7, 0xdf, 0xbf, 0x99, 0xc1, 0xcb, 0x5a, 0x75, 0x6c, 0x54,
|
||||
0xbe, 0x5b, 0xeb, 0x62, 0x5d, 0x95, 0x26, 0xd1, 0x66, 0xdf, 0xed, 0xa5, 0xa7, 0x8b, 0xe8, 0x1d,
|
||||
0x70, 0x51, 0x71, 0x5e, 0xb2, 0x91, 0x57, 0x88, 0x4f, 0xd5, 0xb3, 0x6a, 0xb2, 0xb6, 0x3e, 0x70,
|
||||
0x00, 0x21, 0xc4, 0xff, 0xd3, 0x7f, 0x4e, 0x36, 0xf5, 0x81, 0xa5, 0x44, 0xbf, 0xcd, 0x77, 0x5d,
|
||||
0xe0, 0x85, 0x10, 0xaf, 0x52, 0xb7, 0xe5, 0x19, 0x7a, 0xba, 0x09, 0x66, 0x4e, 0x3c, 0xdd, 0xc8,
|
||||
0x6b, 0x5c, 0xb6, 0xac, 0x4a, 0x36, 0x59, 0x5b, 0x6f, 0x55, 0xe0, 0xbb, 0x80, 0xbf, 0xb4, 0xa9,
|
||||
0xb7, 0x4a, 0xde, 0xa0, 0xdf, 0x70, 0xdf, 0x06, 0xf3, 0x70, 0x16, 0x2f, 0x6f, 0xcf, 0x13, 0x5d,
|
||||
0x24, 0x2f, 0x26, 0xd7, 0x9a, 0xcb, 0xac, 0xe1, 0x3e, 0x75, 0x31, 0x8a, 0x70, 0xf9, 0x07, 0xe5,
|
||||
0x05, 0xce, 0xdd, 0x70, 0x27, 0xad, 0x52, 0xbf, 0x7c, 0xe4, 0xfe, 0xe1, 0x7e, 0x18, 0x49, 0x1c,
|
||||
0x47, 0x12, 0xa7, 0x91, 0xe0, 0xd5, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0x30, 0x58, 0x82, 0x2f,
|
||||
0x4b, 0xf0, 0x6d, 0x49, 0x9c, 0x2c, 0xc1, 0xdb, 0x44, 0x62, 0x98, 0x48, 0x1c, 0x27, 0x12, 0xc5,
|
||||
0xc2, 0x3d, 0x7e, 0xf7, 0x13, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x7a, 0x39, 0x5a, 0x11, 0x01, 0x00,
|
||||
0x00,
|
||||
// 262 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xd0, 0x3f, 0x4e, 0xf3, 0x40,
|
||||
0x10, 0x05, 0xf0, 0x1d, 0xc7, 0x89, 0xf4, 0x4d, 0xf2, 0x81, 0xb4, 0xfc, 0x91, 0x1b, 0x46, 0x56,
|
||||
0x68, 0x5c, 0x39, 0x12, 0x50, 0x50, 0xd3, 0xd2, 0x99, 0x03, 0x44, 0x76, 0x3c, 0x92, 0x2d, 0x47,
|
||||
0xeb, 0xd5, 0xda, 0x08, 0x39, 0x15, 0x25, 0x25, 0xc7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75,
|
||||
0x43, 0x99, 0x23, 0x20, 0x2d, 0x29, 0xe8, 0xde, 0xfb, 0x4d, 0xf3, 0x34, 0x78, 0x51, 0xaa, 0x96,
|
||||
0x8d, 0x4a, 0xb7, 0x2b, 0x9d, 0xad, 0x8a, 0xdc, 0xc4, 0xda, 0xd4, 0x6d, 0x2d, 0x3d, 0x9d, 0x2d,
|
||||
0xdf, 0x00, 0x67, 0x05, 0xa7, 0x39, 0x1b, 0x79, 0x85, 0xb8, 0x29, 0x9e, 0x55, 0xb5, 0x6e, 0xca,
|
||||
0x1d, 0x07, 0x10, 0x42, 0xf4, 0x3f, 0xf9, 0xe7, 0xe4, 0xa9, 0xdc, 0xb1, 0x94, 0xe8, 0x37, 0xe9,
|
||||
0xb6, 0x0d, 0xbc, 0x10, 0xa2, 0x45, 0xe2, 0xb2, 0x3c, 0x41, 0x4f, 0x57, 0xc1, 0xc4, 0x89, 0xa7,
|
||||
0x2b, 0x79, 0x89, 0xb3, 0x86, 0x55, 0xce, 0x26, 0xf0, 0x9d, 0x1d, 0x9b, 0xbc, 0x46, 0xbf, 0xe2,
|
||||
0xae, 0x09, 0xa6, 0xe1, 0x24, 0x9a, 0xdf, 0x9c, 0xc6, 0x3a, 0x8b, 0x5f, 0x4c, 0xaa, 0x35, 0xe7,
|
||||
0xeb, 0x8a, 0xbb, 0xc4, 0x1d, 0x97, 0xf7, 0x38, 0xff, 0x83, 0xf2, 0x0c, 0xa7, 0x2e, 0xb8, 0x25,
|
||||
0x8b, 0xc4, 0xcf, 0x1f, 0xb9, 0x93, 0xe7, 0x38, 0x55, 0xb5, 0xda, 0xf0, 0x71, 0xc5, 0x6f, 0x79,
|
||||
0xb8, 0xeb, 0x07, 0x12, 0xfb, 0x81, 0xc4, 0x61, 0x20, 0x78, 0xb5, 0x04, 0x1f, 0x96, 0xe0, 0xd3,
|
||||
0x12, 0xf4, 0x96, 0xe0, 0xcb, 0x12, 0x7c, 0x5b, 0x12, 0x07, 0x4b, 0xf0, 0x3e, 0x92, 0xe8, 0x47,
|
||||
0x12, 0xfb, 0x91, 0x44, 0x36, 0x73, 0x5f, 0xb8, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x06, 0x40,
|
||||
0x0b, 0xb4, 0x1e, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (this *Header) Equal(that interface{}) bool {
|
||||
|
@ -207,7 +215,7 @@ func (this *Header) Equal(that interface{}) bool {
|
|||
if !bytes.Equal(this.Pk, that1.Pk) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.SenderSign, that1.SenderSign) {
|
||||
if !bytes.Equal(this.Sender, that1.Sender) {
|
||||
return false
|
||||
}
|
||||
if len(this.Keys) != len(that1.Keys) {
|
||||
|
@ -242,6 +250,9 @@ func (this *WrappedKey) Equal(that interface{}) bool {
|
|||
if !bytes.Equal(this.DKey, that1.DKey) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Nonce, that1.Nonce) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (this *Header) GoString() string {
|
||||
|
@ -253,7 +264,7 @@ func (this *Header) GoString() string {
|
|||
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")
|
||||
s = append(s, "SenderSign: "+fmt.Sprintf("%#v", this.SenderSign)+",\n")
|
||||
s = append(s, "Sender: "+fmt.Sprintf("%#v", this.Sender)+",\n")
|
||||
if this.Keys != nil {
|
||||
s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n")
|
||||
}
|
||||
|
@ -264,9 +275,10 @@ func (this *WrappedKey) GoString() string {
|
|||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := make([]string, 0, 5)
|
||||
s := make([]string, 0, 6)
|
||||
s = append(s, "&pb.WrappedKey{")
|
||||
s = append(s, "DKey: "+fmt.Sprintf("%#v", this.DKey)+",\n")
|
||||
s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n")
|
||||
s = append(s, "}")
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
|
@ -312,10 +324,10 @@ func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
|||
dAtA[i] = 0x2a
|
||||
}
|
||||
}
|
||||
if len(m.SenderSign) > 0 {
|
||||
i -= len(m.SenderSign)
|
||||
copy(dAtA[i:], m.SenderSign)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.SenderSign)))
|
||||
if len(m.Sender) > 0 {
|
||||
i -= len(m.Sender)
|
||||
copy(dAtA[i:], m.Sender)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Sender)))
|
||||
i--
|
||||
dAtA[i] = 0x22
|
||||
}
|
||||
|
@ -361,6 +373,13 @@ func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
|||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
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] = 0x12
|
||||
}
|
||||
if len(m.DKey) > 0 {
|
||||
i -= len(m.DKey)
|
||||
copy(dAtA[i:], m.DKey)
|
||||
|
@ -399,7 +418,7 @@ func (m *Header) Size() (n int) {
|
|||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
l = len(m.SenderSign)
|
||||
l = len(m.Sender)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
|
@ -422,6 +441,10 @@ func (m *WrappedKey) Size() (n int) {
|
|||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
l = len(m.Nonce)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -444,7 +467,7 @@ func (this *Header) String() string {
|
|||
`ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`,
|
||||
`Salt:` + fmt.Sprintf("%v", this.Salt) + `,`,
|
||||
`Pk:` + fmt.Sprintf("%v", this.Pk) + `,`,
|
||||
`SenderSign:` + fmt.Sprintf("%v", this.SenderSign) + `,`,
|
||||
`Sender:` + fmt.Sprintf("%v", this.Sender) + `,`,
|
||||
`Keys:` + repeatedStringForKeys + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
|
@ -456,6 +479,7 @@ func (this *WrappedKey) String() string {
|
|||
}
|
||||
s := strings.Join([]string{`&WrappedKey{`,
|
||||
`DKey:` + fmt.Sprintf("%v", this.DKey) + `,`,
|
||||
`Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
|
@ -586,7 +610,7 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
|||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SenderSign", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
|
@ -613,9 +637,9 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
|||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.SenderSign = append(m.SenderSign[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.SenderSign == nil {
|
||||
m.SenderSign = []byte{}
|
||||
m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Sender == nil {
|
||||
m.Sender = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
|
@ -736,6 +760,40 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error {
|
|||
m.DKey = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
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
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipHdr(dAtA[iNdEx:])
|
||||
|
|
|
@ -19,7 +19,7 @@ message header {
|
|||
uint32 chunk_size = 1; // encryption block size
|
||||
bytes salt = 2; // master salt (nonces are derived from this)
|
||||
bytes pk = 3; // ephemeral curve PK
|
||||
bytes sender_sign = 4; // signature block of sender
|
||||
bytes sender = 4; // sender signed artifacts
|
||||
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
|
||||
}
|
||||
|
||||
|
@ -29,4 +29,6 @@ message header {
|
|||
*/
|
||||
message wrapped_key {
|
||||
bytes d_key = 1; // encrypted data key
|
||||
bytes nonce = 2; // nonce used for encryption
|
||||
}
|
||||
|
||||
|
|
523
sign/encrypt.go
523
sign/encrypt.go
|
@ -26,29 +26,36 @@
|
|||
//
|
||||
// Variable Length Segment:
|
||||
// - Protobuf encoded, per-recipient wrapped keys
|
||||
// - Shasum: 32 bytes (SHA256 of full header)
|
||||
//
|
||||
// The variable length segment consists of one or more
|
||||
// recipients, each with their wrapped keys. This is encoded as
|
||||
// a protobuf message. This protobuf encoded message immediately
|
||||
// follows the fixed length header.
|
||||
// recipients, each with their individually wrapped keys.
|
||||
//
|
||||
// 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.
|
||||
// - hkdf-sha512 of random key, salt, context
|
||||
// - the hkdf process yields a data-encryption key, nonce and hmac key.
|
||||
// - we use the header checksum as the 'salt' for HKDF; this ensures that
|
||||
// any modification of the header yields different keys
|
||||
//
|
||||
// We also calculate the cumulative hmac-sha256 of the plaintext blocks.
|
||||
// - When sender identity is present, we sign the final hmac and append
|
||||
// the signature as the "trailer".
|
||||
// - When sender identity is NOT present, we put random bytes as the
|
||||
// "signature". ie in either case, there is a trailer.
|
||||
//
|
||||
// Note: If the trailer is missing from a sigtool encrypted file - the
|
||||
// recipient has no guarantees of content immutability (ie tampering
|
||||
// from one of the _other_ recipients).
|
||||
//
|
||||
// The input data is broken up into "chunks"; each no larger than
|
||||
// maxChunkSize. The default block size is "chunkSize". Each block
|
||||
// is AEAD encrypted:
|
||||
// AEAD nonce = header.salt || block# || block-size
|
||||
// AEAD nonce = header.nonce || block#
|
||||
// AD of AEAD = chunk length+eof marker
|
||||
//
|
||||
// The encrypted block (includes the AEAD tag) length is written
|
||||
// as a big-endian 4-byte prefix. The high-order bit of this length
|
||||
// field is set for the last-block (denoting EOF).
|
||||
//
|
||||
// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen).
|
||||
|
||||
package sign
|
||||
|
||||
|
@ -57,6 +64,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
|
@ -64,42 +72,55 @@ import (
|
|||
"fmt"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/opencoff/sigtool/internal/pb"
|
||||
)
|
||||
|
||||
// Encryption chunk size = 4MB
|
||||
const (
|
||||
chunkSize uint32 = 4 * 1048576
|
||||
// The latest version of the tool's output file format
|
||||
_SigtoolVersion = 3
|
||||
|
||||
chunkSize uint32 = 4 * 1048576 // 4 MB
|
||||
maxChunkSize uint32 = 1 << 30
|
||||
_EOF uint32 = 1 << 31
|
||||
|
||||
_Magic = "SigTool"
|
||||
_MagicLen = len(_Magic)
|
||||
_SigtoolVersion = 2
|
||||
_AEADNonceLen = 32
|
||||
_FixedHdrLen = _MagicLen + 1 + 4
|
||||
_FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment
|
||||
|
||||
_WrapReceiverNonce = "Receiver Key Nonce"
|
||||
_WrapSenderNonce = "Sender Sig Nonce"
|
||||
_EncryptNonce = "Encrypt Nonce"
|
||||
_AesKeySize = 32
|
||||
_AEADNonceSize = 12
|
||||
_SaltSize = 32
|
||||
_RxNonceSize = 12 // nonce size of per-recipient encrypted blocks
|
||||
|
||||
_WrapReceiver = "Receiver Key"
|
||||
_WrapSender = "Sender Sig"
|
||||
_DataKeyExpansion = "Data Key Expansion"
|
||||
)
|
||||
|
||||
// Encryptor holds the encryption context
|
||||
type Encryptor struct {
|
||||
pb.Header
|
||||
key []byte // file encryption key
|
||||
key []byte // root key
|
||||
|
||||
nonce []byte // nonce for the data encrypting cipher
|
||||
buf []byte // I/O buf (chunk-sized)
|
||||
|
||||
ae cipher.AEAD
|
||||
hmac hash.Hash
|
||||
|
||||
// ephemeral key
|
||||
encSK []byte
|
||||
|
||||
started bool
|
||||
// sender identity
|
||||
sender *PrivateKey
|
||||
|
||||
hdrsum []byte
|
||||
buf []byte
|
||||
auth bool // set if the sender idetity is sent
|
||||
started bool
|
||||
stream bool
|
||||
}
|
||||
|
||||
|
@ -123,27 +144,23 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
|||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
salt := make([]byte, _AEADNonceLen)
|
||||
|
||||
randRead(key)
|
||||
randRead(salt)
|
||||
|
||||
wSig, err := wrapSenderSig(sk, key, salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
key := randBuf(_AesKeySize)
|
||||
salt := randBuf(_SaltSize)
|
||||
|
||||
e := &Encryptor{
|
||||
Header: pb.Header{
|
||||
ChunkSize: blksz,
|
||||
Salt: salt,
|
||||
Pk: epk,
|
||||
SenderSign: wSig,
|
||||
},
|
||||
|
||||
key: key,
|
||||
encSK: esk,
|
||||
sender: sk,
|
||||
}
|
||||
|
||||
if err = e.addSenderSig(sk); err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
|
@ -210,8 +227,8 @@ func (e *Encryptor) start(wr io.Writer) error {
|
|||
|
||||
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
|
||||
fixHdr := buffer[:_FixedHdrLen]
|
||||
varHdr := buffer[_FixedHdrLen:]
|
||||
sumHdr := varHdr[varSize:]
|
||||
varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize]
|
||||
sumHdr := buffer[_FixedHdrLen+varSize:]
|
||||
|
||||
// Now assemble the fixed header
|
||||
copy(fixHdr[:], []byte(_Magic))
|
||||
|
@ -224,38 +241,45 @@ func (e *Encryptor) start(wr io.Writer) error {
|
|||
return fmt.Errorf("encrypt: can't marshal header: %w", err)
|
||||
}
|
||||
|
||||
// Now calculate checksum of everything
|
||||
h := sha256.New()
|
||||
h.Write(buffer[:_FixedHdrLen+varSize])
|
||||
h.Sum(sumHdr[:0])
|
||||
cksum := h.Sum(sumHdr[:0])
|
||||
|
||||
// Finally write it out
|
||||
// now make the data encryption keys, nonces etc.
|
||||
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
|
||||
|
||||
// we mix the header checksum (and it captures the sigtool version, sender
|
||||
// identity, etc.)
|
||||
buf := expand(outbuf, e.key, cksum, []byte(_DataKeyExpansion))
|
||||
|
||||
var dkey, hmackey []byte
|
||||
|
||||
e.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
|
||||
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
hmackey = buf
|
||||
|
||||
aes, err := aes.NewCipher(dkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
if e.ae, err = cipher.NewGCM(aes); err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
// Finally write out the header
|
||||
err = fullwrite(buffer, wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
// we mix the header checksum to create the encryption key
|
||||
h = sha256.New()
|
||||
h.Write([]byte(_EncryptNonce))
|
||||
h.Write(e.key)
|
||||
h.Write(sumHdr)
|
||||
key := h.Sum(nil)
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
e.buf = make([]byte, e.ChunkSize+4+uint32(ae.Overhead()))
|
||||
e.ae = ae
|
||||
|
||||
e.hmac = hmac.New(sha256.New, hmackey)
|
||||
e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead()))
|
||||
e.started = true
|
||||
|
||||
debug("encrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
|
||||
cksum, dkey, e.nonce, hmackey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -264,30 +288,70 @@ func (e *Encryptor) start(wr io.Writer) error {
|
|||
// This protects the output stream from re-ordering attacks and length
|
||||
// modification attacks. The encoded length & block number is used as
|
||||
// additional data in the AEAD construction.
|
||||
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error {
|
||||
var z uint32 = uint32(len(buf))
|
||||
var nbuf [_AEADNonceLen]byte
|
||||
func (e *Encryptor) encrypt(pt []byte, wr io.Writer, i uint32, eof bool) error {
|
||||
var z uint32 = uint32(len(pt))
|
||||
var nonce [_AEADNonceSize]byte
|
||||
|
||||
// mark last block
|
||||
if eof {
|
||||
z |= _EOF
|
||||
}
|
||||
|
||||
b := e.buf[:8]
|
||||
binary.BigEndian.PutUint32(b[:4], z)
|
||||
binary.BigEndian.PutUint32(b[4:], i)
|
||||
copy(nonce[:], e.nonce)
|
||||
|
||||
nonce := makeNonceV2(nbuf[:], e.Salt, b)
|
||||
// now change the upper bytes to track the block#; we use the len+eof as AD
|
||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
||||
|
||||
cbuf := e.buf[4:]
|
||||
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
|
||||
// put the encoded length+eof at the start of the output buf
|
||||
b := e.buf[:4]
|
||||
ctbuf := e.buf[4:]
|
||||
|
||||
binary.BigEndian.PutUint32(b, z)
|
||||
ct := e.ae.Seal(ctbuf[:0], nonce[:], pt, b)
|
||||
|
||||
// total number of bytes written
|
||||
n := len(c) + 4
|
||||
n := len(ct) + 4
|
||||
err := fullwrite(e.buf[:n], wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
e.hmac.Write(b)
|
||||
e.hmac.Write(pt)
|
||||
|
||||
if eof {
|
||||
return e.writeTrailer(wr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write a trailer:
|
||||
// - if authenticating sender, sign the hmac and put the signature in the trailer
|
||||
// - if not authenticating sender, write random bytes to the trailer
|
||||
func (e *Encryptor) writeTrailer(wr io.Writer) error {
|
||||
var tr []byte
|
||||
|
||||
switch e.auth {
|
||||
case true:
|
||||
var hmac [sha256.Size]byte
|
||||
|
||||
e.hmac.Sum(hmac[:0])
|
||||
|
||||
// We know sender is non null.
|
||||
sig, err := e.sender.SignMessage(hmac[:], "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: trailer: %w", err)
|
||||
}
|
||||
tr = sig.Sig
|
||||
|
||||
case false:
|
||||
tr = randBuf(ed25519.SignatureSize)
|
||||
|
||||
}
|
||||
|
||||
if err := fullwrite(tr, wr); err != nil {
|
||||
return fmt.Errorf("encrypt: trailer %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -296,15 +360,17 @@ type Decryptor struct {
|
|||
pb.Header
|
||||
|
||||
ae cipher.AEAD
|
||||
hmac hash.Hash
|
||||
|
||||
sender *PublicKey
|
||||
|
||||
rd io.Reader
|
||||
buf []byte
|
||||
hdrsum []byte
|
||||
nonce []byte // nonce for the data decrypting cipher
|
||||
|
||||
// flag set to true if sender signed the key
|
||||
auth bool
|
||||
|
||||
// Decrypted key
|
||||
key []byte
|
||||
key []byte // Decrypted root key
|
||||
hdrsum []byte // cached header checksum
|
||||
auth bool // flag set to true if sender signed the key
|
||||
eof bool
|
||||
stream bool
|
||||
}
|
||||
|
@ -347,14 +413,18 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
|||
return nil, fmt.Errorf("decrypt: error while reading header: %w", err)
|
||||
}
|
||||
|
||||
// The checksum in the header
|
||||
verify := varBuf[varSize:]
|
||||
|
||||
// the checksum we calculated
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(b[:])
|
||||
h.Write(varBuf[:varSize])
|
||||
cksum := h.Sum(nil)
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
|
||||
if subtle.ConstantTimeCompare(verify, cksum) == 0 {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
|
||||
|
@ -372,7 +442,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
|||
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
|
||||
}
|
||||
|
||||
if len(d.Salt) != _AEADNonceLen {
|
||||
if len(d.Salt) != _SaltSize {
|
||||
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
|
||||
}
|
||||
|
||||
|
@ -382,7 +452,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
|||
|
||||
// sanity check on the wrapped keys
|
||||
for i, w := range d.Keys {
|
||||
if len(w.DKey) <= 32 {
|
||||
if len(w.DKey) <= _AesKeySize {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
|
||||
}
|
||||
}
|
||||
|
@ -402,6 +472,8 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
|||
return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err)
|
||||
}
|
||||
if key != nil {
|
||||
d.key = key
|
||||
d.sender = senderPk
|
||||
goto havekey
|
||||
}
|
||||
}
|
||||
|
@ -409,28 +481,37 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
|||
return ErrBadKey
|
||||
|
||||
havekey:
|
||||
if err := d.verifySender(key, sk, senderPk); err != nil {
|
||||
if err := d.verifySender(key, senderPk); err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
d.key = key
|
||||
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
|
||||
|
||||
// we mix the header checksum into the key
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_EncryptNonce))
|
||||
h.Write(d.key)
|
||||
h.Write(d.hdrsum)
|
||||
key = h.Sum(nil)
|
||||
buf := expand(outbuf, d.key, d.hdrsum, []byte(_DataKeyExpansion))
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
var dkey, hmackey []byte
|
||||
|
||||
d.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
|
||||
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
hmackey = buf
|
||||
|
||||
d.hmac = hmac.New(sha256.New, hmackey)
|
||||
|
||||
aes, err := aes.NewCipher(dkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
||||
d.ae, err = cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
debug("decrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
|
||||
d.hdrsum, dkey, d.nonce, hmackey)
|
||||
|
||||
// We have a separate on-stack buffer for reading the header (4 bytes).
|
||||
// Thus, the actual I/O buf will never be larger than the chunksize + AEAD Overhead
|
||||
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
|
||||
return nil
|
||||
}
|
||||
|
@ -477,17 +558,16 @@ func (d *Decryptor) Decrypt(wr io.Writer) error {
|
|||
|
||||
// Decrypt exactly one chunk of data
|
||||
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
||||
var b [8]byte
|
||||
var nonceb [32]byte
|
||||
var ovh uint32 = uint32(d.ae.Overhead())
|
||||
var p []byte
|
||||
var b [4]byte
|
||||
var nonce [_AEADNonceSize]byte
|
||||
|
||||
n, err := io.ReadFull(d.rd, b[:4])
|
||||
n, err := io.ReadFull(d.rd, b[:])
|
||||
if err != nil || n == 0 {
|
||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
|
||||
}
|
||||
|
||||
m := binary.BigEndian.Uint32(b[:4])
|
||||
m := binary.BigEndian.Uint32(b[:])
|
||||
eof := (m & _EOF) > 0
|
||||
m &= (_EOF - 1)
|
||||
|
||||
|
@ -500,13 +580,14 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
|||
if !eof {
|
||||
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
|
||||
}
|
||||
return p, eof, nil
|
||||
return nil, eof, nil
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(b[4:], i)
|
||||
nonce := makeNonceV2(nonceb[:], d.Salt, b[:])
|
||||
// make the nonce - top 4 bytes are the counter
|
||||
copy(nonce[:], d.nonce)
|
||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
||||
|
||||
z := m + ovh
|
||||
n, err = io.ReadFull(d.rd, d.buf[:z])
|
||||
|
@ -514,57 +595,115 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
|||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %w", i, err)
|
||||
}
|
||||
|
||||
p, err = d.ae.Open(d.buf[:0], nonce, d.buf[:n], b[:])
|
||||
pt, err := d.ae.Open(d.buf[:0], nonce[:], d.buf[:n], b[:])
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
|
||||
}
|
||||
|
||||
return p[:m], eof, nil
|
||||
if uint32(len(pt)) != m {
|
||||
return nil, false, fmt.Errorf("decrypt: partial unsealed bytes; exp %d, saw %d", m, len(pt))
|
||||
}
|
||||
|
||||
// Wrap sender's signature of the encryption key
|
||||
// if sender has provided their identity to authenticate, we sign the data-enc key
|
||||
// and encrypt the signature. At no point will we send the sender's identity.
|
||||
func wrapSenderSig(sk *PrivateKey, key, salt []byte) ([]byte, error) {
|
||||
d.hmac.Write(b[:])
|
||||
d.hmac.Write(pt)
|
||||
|
||||
if eof {
|
||||
return d.processTrailer(pt, eof)
|
||||
}
|
||||
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
func (d *Decryptor) processTrailer(pt []byte, eof bool) ([]byte, bool, error) {
|
||||
var rd [ed25519.SignatureSize]byte
|
||||
|
||||
_, err := io.ReadFull(d.rd, rd[:])
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading trailer: %w", err)
|
||||
}
|
||||
|
||||
if !d.auth {
|
||||
// these are random bytes; ignore em
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
var hmac [sha256.Size]byte
|
||||
|
||||
cksum := d.hmac.Sum(hmac[:0])
|
||||
ss := &Signature{
|
||||
Sig: rd[:],
|
||||
}
|
||||
|
||||
if ok := d.sender.VerifyMessage(cksum, ss); !ok {
|
||||
return nil, eof, ErrBadTrailer
|
||||
}
|
||||
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
// optionally sign the checksum and encrypt everything
|
||||
func (e *Encryptor) addSenderSig(sk *PrivateKey) error {
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
var sig []byte
|
||||
var auth bool
|
||||
sig := zero[:]
|
||||
|
||||
switch {
|
||||
case sk == nil:
|
||||
sig = zero[:]
|
||||
if e.sender != nil {
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
default:
|
||||
xsig, err := sk.SignMessage(key, "")
|
||||
// We capture essential meta-data from the sender; viz:
|
||||
// - Sender tool version
|
||||
// - Sender generated curve25519 PK
|
||||
// - session salt, root key
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_Magic))
|
||||
h.Write([]byte{_SigtoolVersion})
|
||||
h.Write(e.Pk)
|
||||
h.Write(e.Salt)
|
||||
h.Write(e.key)
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
xsig, err := e.sender.SignMessage(cksum, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: can't sign: %w", err)
|
||||
return fmt.Errorf("wrap: can't sign: %w", err)
|
||||
}
|
||||
|
||||
sig = xsig.Sig
|
||||
auth = true
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
buf := make([]byte, _AesKeySize+_AEADNonceSize)
|
||||
buf = expand(buf, e.key, e.Salt, []byte(_WrapSender))
|
||||
|
||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
return fmt.Errorf("senderId: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
return fmt.Errorf("senderId: %w", err)
|
||||
}
|
||||
|
||||
tagsize := ae.Overhead()
|
||||
nonceSize := ae.NonceSize()
|
||||
outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead())
|
||||
buf = ae.Seal(outbuf[:0], nonce, sig, nil)
|
||||
|
||||
nonce := sha256Slices([]byte(_WrapSenderNonce), salt)[:nonceSize]
|
||||
esig := make([]byte, tagsize+len(sig))
|
||||
e.auth = auth
|
||||
e.Sender = buf
|
||||
|
||||
return ae.Seal(esig[:0], nonce, sig, nil), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// unwrap sender's signature using 'key' and extract the signature
|
||||
// Optionally, verify the signature using the sender's PK (if provided).
|
||||
func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error {
|
||||
aes, err := aes.NewCipher(key)
|
||||
func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
|
||||
outbuf := make([]byte, _AEADNonceSize+_AesKeySize)
|
||||
buf := expand(outbuf, key, d.Salt, []byte(_WrapSender))
|
||||
|
||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
@ -574,31 +713,42 @@ func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey
|
|||
return fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
||||
nonceSize := ae.NonceSize()
|
||||
nonce := sha256Slices([]byte(_WrapSenderNonce), d.Salt)[:nonceSize]
|
||||
sig := make([]byte, ed25519.SignatureSize)
|
||||
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil)
|
||||
var sigbuf [ed25519.SignatureSize]byte
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
|
||||
sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: can't open sender info: %w", err)
|
||||
}
|
||||
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
|
||||
// Did the sender actually sign anything?
|
||||
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
|
||||
// we set this to indicate that the sender authenticated themselves;
|
||||
d.auth = true
|
||||
if senderPk == nil {
|
||||
return ErrNoSenderPK
|
||||
}
|
||||
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_Magic))
|
||||
h.Write([]byte{_SigtoolVersion})
|
||||
h.Write(d.Pk)
|
||||
h.Write(d.Salt)
|
||||
h.Write(key)
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
if senderPK != nil {
|
||||
ss := &Signature{
|
||||
Sig: sig,
|
||||
}
|
||||
|
||||
if ok := senderPK.VerifyMessage(key, ss); !ok {
|
||||
return fmt.Errorf("unwrap: sender verification failed")
|
||||
}
|
||||
if ok := senderPk.VerifyMessage(cksum, ss); !ok {
|
||||
return ErrBadSender
|
||||
}
|
||||
|
||||
// we set this to indicate that the sender authenticated themselves;
|
||||
d.auth = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -606,12 +756,26 @@ func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey
|
|||
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
|
||||
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
||||
rxPK := pk.ToCurve25519PK()
|
||||
dkek, err := curve25519.X25519(e.encSK, rxPK)
|
||||
sekrit, err := curve25519.X25519(e.encSK, rxPK)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(dkek)
|
||||
var shasum [sha256.Size]byte
|
||||
|
||||
rbuf := randBuf(_RxNonceSize)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(e.Salt)
|
||||
h.Write(rbuf[:])
|
||||
h.Sum(shasum[:0])
|
||||
|
||||
out := make([]byte, _AesKeySize+_RxNonceSize)
|
||||
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
|
||||
|
||||
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
@ -621,14 +785,10 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
|||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
tagsize := ae.Overhead()
|
||||
nonceSize := ae.NonceSize()
|
||||
|
||||
nonceR := sha256Slices([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize]
|
||||
ekey := make([]byte, tagsize+len(e.key))
|
||||
|
||||
ekey := make([]byte, ae.Overhead()+len(e.key))
|
||||
w := &pb.WrappedKey{
|
||||
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk),
|
||||
DKey: ae.Seal(ekey[:0], nonce, e.key, pk.Pk),
|
||||
Nonce: rbuf,
|
||||
}
|
||||
|
||||
return w, nil
|
||||
|
@ -638,40 +798,48 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
|||
// senders ephemeral PublicKey
|
||||
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
||||
ourSK := sk.ToCurve25519SK()
|
||||
dkek, err := curve25519.X25519(ourSK, d.Pk)
|
||||
sekrit, err := curve25519.X25519(ourSK, d.Pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(dkek)
|
||||
var shasum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(d.Salt)
|
||||
h.Write(w.Nonce)
|
||||
h.Sum(shasum[:0])
|
||||
|
||||
out := make([]byte, _AesKeySize+_RxNonceSize)
|
||||
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
|
||||
|
||||
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %w", err)
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %w", err)
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
// 32 == AES-256 key size
|
||||
want := 32 + ae.Overhead()
|
||||
want := _AesKeySize + ae.Overhead()
|
||||
if len(w.DKey) != want {
|
||||
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
|
||||
}
|
||||
|
||||
nonceSize := ae.NonceSize()
|
||||
|
||||
nonceR := sha256Slices([]byte(_WrapReceiverNonce), d.Salt)[:nonceSize]
|
||||
pk := sk.PublicKey()
|
||||
|
||||
dkey := make([]byte, 32) // decrypted data decryption key
|
||||
dkey := make([]byte, _AesKeySize) // decrypted data decryption key
|
||||
|
||||
// we indicate incorrect receiver SK by returning a nil key
|
||||
dkey, err = ae.Open(dkey[:0], nonceR, w.DKey, pk.Pk)
|
||||
dkey, err = ae.Open(dkey[:0], nonce, w.DKey, pk.Pk)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// we have successfully found the correct recipient
|
||||
return dkey, nil
|
||||
}
|
||||
|
||||
|
@ -691,30 +859,14 @@ func fullwrite(buf []byte, wr io.Writer) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// make aead nonce from salt, chunk-size and block#
|
||||
// First 8 bytes are chunk-size and nonce (in 'ad')
|
||||
func makeNonceV2(dest []byte, salt []byte, ad []byte) []byte {
|
||||
n := len(ad)
|
||||
copy(dest, ad)
|
||||
copy(dest[n:], salt)
|
||||
return dest
|
||||
}
|
||||
|
||||
// make aead nonce from salt, chunk-size and block# for v1
|
||||
// This is here for historical documentation purposes
|
||||
func makeNonceV1(dest []byte, salt []byte, ad []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(salt)
|
||||
h.Write(ad)
|
||||
return h.Sum(dest[:0])
|
||||
}
|
||||
|
||||
// generate a KEK from a shared DH key and a Pub Key
|
||||
func expand(shared, pk []byte) ([]byte, error) {
|
||||
kek := make([]byte, 32)
|
||||
h := hkdf.New(sha512.New, shared, pk, nil)
|
||||
_, err := io.ReadFull(h, kek)
|
||||
return kek, err
|
||||
func expand(out []byte, shared, salt, ad []byte) []byte {
|
||||
h := hkdf.New(sha512.New, shared, salt, ad)
|
||||
_, err := io.ReadFull(h, out)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newSender() (sk, pk []byte, err error) {
|
||||
|
@ -736,4 +888,25 @@ func sha256Slices(v ...[]byte) []byte {
|
|||
return h.Sum(nil)[:]
|
||||
}
|
||||
|
||||
var _debug int = 0
|
||||
|
||||
// Enable debugging of this module;
|
||||
// level > 0 elicits debug messages on os.Stderr
|
||||
func Debug(level int) {
|
||||
_debug = level
|
||||
}
|
||||
|
||||
func debug(s string, v ...interface{}) {
|
||||
if _debug <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
z := fmt.Sprintf(s, v...)
|
||||
if n := len(z); z[n-1] != '\n' {
|
||||
z += "\n"
|
||||
}
|
||||
os.Stderr.WriteString(z)
|
||||
os.Stderr.Sync()
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
|
|
@ -20,17 +20,19 @@ import (
|
|||
|
||||
var (
|
||||
ErrClosed = errors.New("encrypt: stream already closed")
|
||||
ErrNoKey = errors.New("decrypt: No private key set for decryption")
|
||||
ErrNoKey = errors.New("decrypt: no private key set for decryption")
|
||||
ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started")
|
||||
ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started")
|
||||
ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O")
|
||||
ErrNotSigTool = errors.New("decrypt: Not a sigtool encrypted file?")
|
||||
ErrNotSigTool = errors.New("decrypt: not a sigtool encrypted file?")
|
||||
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
|
||||
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
|
||||
ErrBadHeader = errors.New("decrypt: header corrupted")
|
||||
ErrNoWrappedKeys = errors.New("decrypt: No wrapped keys in encrypted file")
|
||||
ErrNoWrappedKeys = errors.New("decrypt: no wrapped keys in encrypted file")
|
||||
ErrBadKey = errors.New("decrypt: wrong key")
|
||||
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
|
||||
ErrBadSender = errors.New("unwrap: sender verification failed")
|
||||
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
|
||||
|
||||
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
|
||||
ErrNoPEMFound = errors.New("ssh: no PEM block found")
|
||||
|
|
|
@ -38,3 +38,8 @@ func randRead(b []byte) []byte {
|
|||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func randBuf(sz int) []byte {
|
||||
b := make([]byte, sz)
|
||||
return randRead(b)
|
||||
}
|
||||
|
|
10
sigtool.go
10
sigtool.go
|
@ -30,12 +30,13 @@ var Z string = path.Base(os.Args[0])
|
|||
|
||||
func main() {
|
||||
|
||||
var ver, help bool
|
||||
var ver, help, debug bool
|
||||
|
||||
mf := flag.NewFlagSet(Z, flag.ExitOnError)
|
||||
mf.SetInterspersed(false)
|
||||
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
|
||||
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
|
||||
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
|
||||
mf.Parse(os.Args[1:])
|
||||
|
||||
if ver {
|
||||
|
@ -80,6 +81,10 @@ func main() {
|
|||
Die("can't map command %s", canon)
|
||||
}
|
||||
|
||||
if debug {
|
||||
sign.Debug(1)
|
||||
}
|
||||
|
||||
cmd(args[1:])
|
||||
|
||||
// always call Exit so that at-exit handlers are called.
|
||||
|
@ -323,7 +328,8 @@ Usage: %s [global-options] command [options] arg [args..]
|
|||
|
||||
Global options:
|
||||
-h, --help Show help and exit
|
||||
-v, --version Show version info and exit.
|
||||
-v, --version Show version info and exit
|
||||
--debug Enable debug (DANGEROUS)
|
||||
|
||||
Commands:
|
||||
generate, g Generate a new Ed25519 keypair
|
||||
|
|
Loading…
Add table
Reference in a new issue