Better handling of sender verification
* Sender identity is never shared in the encrypted payload * Sender signs the data-encryption key via Ed25519 if sender-auth is desired; else a "signature" of all zeroes is used. In either case, this signature is encrypted with the same data-encryption key. * cleaned up stale code and updated tests
This commit is contained in:
parent
00542dec02
commit
e22fae05f7
10 changed files with 324 additions and 521 deletions
40
README.md
40
README.md
|
@ -138,11 +138,7 @@ recipient can decrypt using their private key.
|
|||
|
||||
## Technical Details
|
||||
|
||||
### 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.
|
||||
|
||||
### How is the Encryption done?
|
||||
### How is the file encryption done?
|
||||
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
|
||||
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.
|
||||
|
@ -152,13 +148,19 @@ 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?
|
||||
`sigtool` uses Curve25519 ECC to generate shared secrets between
|
||||
pairs of sender & recipients. This pairwise shared secret is expanded
|
||||
using HKDF to generate a key-encryption-key. The file-encryption key
|
||||
is AEAD encrypted with this key-encryption-key. Thus, each recipient
|
||||
has their own individual encrypted key blob.
|
||||
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
|
||||
between pairs of sender & one or more recipients. This pairwise shared
|
||||
secret is used as a key-encryption-key (KEK) to encrypt the
|
||||
data-encryption key in AEAD mode. Thus, each recipient has their own
|
||||
individual encrypted key blob.
|
||||
|
||||
The Ed25519 keys generated by `sigtool` are transformed to their
|
||||
If the sender authenticates the encryption by providing their secret
|
||||
key, the data-encryption key is signed via Ed25519 and the signature
|
||||
is encrypted (using the data-encryption key) and stored in the
|
||||
header. If the sender opts to not authenticate, a "signature" of all
|
||||
zeroes is encrypted instead.
|
||||
|
||||
The Ed25519 keys generated by `sigtool` or OpenSSH are transformed to their
|
||||
corresponding Curve25519 points in order to generate the shared secret.
|
||||
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].
|
||||
|
||||
|
@ -183,23 +185,16 @@ described as a protobuf file (sign/hdr.proto):
|
|||
uint32 chunk_size = 1;
|
||||
bytes salt = 2;
|
||||
bytes pk = 3; // sender's ephemeral curve PK
|
||||
sender sender_pk = 4; // sender's encrypted ed25519 PK
|
||||
bytes sender_sig = 4; // ed25519 signature of the key
|
||||
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 key = 2;
|
||||
bytes d_key = 1;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -220,6 +215,11 @@ computed.
|
|||
The chunk data and AEAD tag are treated as an atomic unit for AEAD
|
||||
decryption.
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
## Understanding the Code
|
||||
The core logic is in `src/sign`: it is a library that exposes all the
|
||||
functionality: key generation, key parsing, signing, encryption, decryption
|
||||
|
|
10
crypt.go
10
crypt.go
|
@ -43,7 +43,7 @@ func encrypt(args []string) {
|
|||
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
|
||||
fs.SizeVarP(&blksize, "block-size", "B", 128 * 1024, "Use `S` as the encryption block size")
|
||||
fs.SizeVarP(&blksize, "block-size", "B", 128*1024, "Use `S` as the encryption block size")
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
|
@ -298,6 +298,14 @@ func decrypt(args []string) {
|
|||
die("%s", err)
|
||||
}
|
||||
|
||||
if pk == nil && d.AuthenticatedSender() {
|
||||
var fn string = infile
|
||||
if len(fn) == 0 || fn == "-" {
|
||||
fn = "<stdin>"
|
||||
}
|
||||
warn("%s: Missing sender Public Key; can't authenticate sender ..", fn)
|
||||
}
|
||||
|
||||
err = d.Decrypt(outfd)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
It has these top-level messages:
|
||||
Header
|
||||
Sender
|
||||
WrappedKey
|
||||
*/
|
||||
package pb
|
||||
|
@ -46,7 +45,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"`
|
||||
SenderPk *Sender `protobuf:"bytes,4,opt,name=sender_pk,json=senderPk" json:"sender_pk,omitempty"`
|
||||
SenderSign []byte `protobuf:"bytes,4,opt,name=sender_sign,json=senderSign,proto3" json:"sender_sign,omitempty"`
|
||||
Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys" json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -75,9 +74,9 @@ func (m *Header) GetPk() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Header) GetSenderPk() *Sender {
|
||||
func (m *Header) GetSenderSign() []byte {
|
||||
if m != nil {
|
||||
return m.SenderPk
|
||||
return m.SenderSign
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -89,44 +88,26 @@ func (m *Header) GetKeys() []*WrappedKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// 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 *Sender) Reset() { *m = Sender{} }
|
||||
func (*Sender) ProtoMessage() {}
|
||||
func (*Sender) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{1} }
|
||||
|
||||
func (m *Sender) GetPk() []byte {
|
||||
if m != nil {
|
||||
return m.Pk
|
||||
}
|
||||
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"`
|
||||
DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *WrappedKey) Reset() { *m = WrappedKey{} }
|
||||
func (*WrappedKey) ProtoMessage() {}
|
||||
func (*WrappedKey) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{2} }
|
||||
func (*WrappedKey) Descriptor() ([]byte, []int) { return fileDescriptorHdr, []int{1} }
|
||||
|
||||
func (m *WrappedKey) GetKey() []byte {
|
||||
func (m *WrappedKey) GetDKey() []byte {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
return m.DKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Header)(nil), "pb.header")
|
||||
proto.RegisterType((*Sender)(nil), "pb.sender")
|
||||
proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key")
|
||||
}
|
||||
func (this *Header) Equal(that interface{}) bool {
|
||||
|
@ -163,7 +144,7 @@ func (this *Header) Equal(that interface{}) bool {
|
|||
if !bytes.Equal(this.Pk, that1.Pk) {
|
||||
return false
|
||||
}
|
||||
if !this.SenderPk.Equal(that1.SenderPk) {
|
||||
if !bytes.Equal(this.SenderSign, that1.SenderSign) {
|
||||
return false
|
||||
}
|
||||
if len(this.Keys) != len(that1.Keys) {
|
||||
|
@ -176,36 +157,6 @@ 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 {
|
||||
if this == nil {
|
||||
|
@ -231,7 +182,7 @@ func (this *WrappedKey) Equal(that interface{}) bool {
|
|||
} else if this == nil {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Key, that1.Key) {
|
||||
if !bytes.Equal(this.DKey, that1.DKey) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -245,32 +196,20 @@ 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")
|
||||
if this.SenderPk != nil {
|
||||
s = append(s, "SenderPk: "+fmt.Sprintf("%#v", this.SenderPk)+",\n")
|
||||
}
|
||||
s = append(s, "SenderSign: "+fmt.Sprintf("%#v", this.SenderSign)+",\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, 5)
|
||||
s = append(s, "&pb.WrappedKey{")
|
||||
s = append(s, "Key: "+fmt.Sprintf("%#v", this.Key)+",\n")
|
||||
s = append(s, "DKey: "+fmt.Sprintf("%#v", this.DKey)+",\n")
|
||||
s = append(s, "}")
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
|
@ -314,15 +253,11 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) {
|
|||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk)))
|
||||
i += copy(dAtA[i:], m.Pk)
|
||||
}
|
||||
if m.SenderPk != nil {
|
||||
if len(m.SenderSign) > 0 {
|
||||
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
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.SenderSign)))
|
||||
i += copy(dAtA[i:], m.SenderSign)
|
||||
}
|
||||
if len(m.Keys) > 0 {
|
||||
for _, msg := range m.Keys {
|
||||
|
@ -339,30 +274,6 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) {
|
|||
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)
|
||||
|
@ -378,11 +289,11 @@ func (m *WrappedKey) MarshalTo(dAtA []byte) (int, error) {
|
|||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Key) > 0 {
|
||||
dAtA[i] = 0x12
|
||||
if len(m.DKey) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Key)))
|
||||
i += copy(dAtA[i:], m.Key)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.DKey)))
|
||||
i += copy(dAtA[i:], m.DKey)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
@ -428,8 +339,8 @@ func (m *Header) Size() (n int) {
|
|||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
if m.SenderPk != nil {
|
||||
l = m.SenderPk.Size()
|
||||
l = len(m.SenderSign)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
if len(m.Keys) > 0 {
|
||||
|
@ -441,20 +352,10 @@ func (m *Header) Size() (n int) {
|
|||
return n
|
||||
}
|
||||
|
||||
func (m *Sender) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Pk)
|
||||
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)
|
||||
l = len(m.DKey)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
|
@ -482,28 +383,18 @@ func (this *Header) String() string {
|
|||
`ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`,
|
||||
`Salt:` + fmt.Sprintf("%v", this.Salt) + `,`,
|
||||
`Pk:` + fmt.Sprintf("%v", this.Pk) + `,`,
|
||||
`SenderPk:` + strings.Replace(fmt.Sprintf("%v", this.SenderPk), "Sender", "Sender", 1) + `,`,
|
||||
`SenderSign:` + fmt.Sprintf("%v", this.SenderSign) + `,`,
|
||||
`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
|
||||
}
|
||||
func (this *WrappedKey) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&WrappedKey{`,
|
||||
`Key:` + fmt.Sprintf("%v", this.Key) + `,`,
|
||||
`DKey:` + fmt.Sprintf("%v", this.DKey) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
|
@ -628,9 +519,9 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
|||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SenderPk", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SenderSign", wireType)
|
||||
}
|
||||
var msglen int
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
|
@ -640,23 +531,21 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
postIndex := iNdEx + byteLen
|
||||
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
|
||||
m.SenderSign = append(m.SenderSign[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.SenderSign == nil {
|
||||
m.SenderSign = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
|
@ -711,87 +600,6 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
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 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *WrappedKey) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
@ -821,9 +629,9 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error {
|
|||
return fmt.Errorf("proto: wrapped_key: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 2:
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DKey", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
|
@ -847,9 +655,9 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error {
|
|||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Key == nil {
|
||||
m.Key = []byte{}
|
||||
m.DKey = append(m.DKey[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.DKey == nil {
|
||||
m.DKey = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
|
@ -981,22 +789,21 @@ var (
|
|||
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,
|
||||
// 252 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0xcf, 0x31, 0x4e, 0xeb, 0x40,
|
||||
0x10, 0xc6, 0x71, 0x8f, 0xe3, 0x44, 0x7a, 0xe3, 0x3c, 0x90, 0x16, 0x21, 0xb9, 0x61, 0xb0, 0x4c,
|
||||
0xe3, 0x02, 0x39, 0x12, 0xdc, 0x80, 0x96, 0xce, 0x39, 0x80, 0x65, 0xe3, 0x51, 0x6c, 0x6d, 0xb4,
|
||||
0x59, 0xed, 0x1a, 0x21, 0xa7, 0xe2, 0x08, 0x70, 0x0b, 0x8e, 0x42, 0x99, 0x92, 0x12, 0x2f, 0x0d,
|
||||
0x65, 0x8e, 0x80, 0xb4, 0x34, 0x74, 0x9f, 0x7e, 0xff, 0x66, 0x06, 0xcf, 0x7b, 0x35, 0xb0, 0x51,
|
||||
0xf5, 0x76, 0xa5, 0x9b, 0x55, 0xd7, 0x9a, 0x42, 0x9b, 0xdd, 0xb0, 0x13, 0xa1, 0x6e, 0xb2, 0x57,
|
||||
0xc0, 0x45, 0xc7, 0x75, 0xcb, 0x46, 0x5c, 0x20, 0x3e, 0x74, 0x8f, 0x4a, 0x56, 0xb6, 0xdf, 0x73,
|
||||
0x02, 0x29, 0xe4, 0xff, 0xcb, 0x7f, 0x5e, 0xd6, 0xfd, 0x9e, 0x85, 0xc0, 0xc8, 0xd6, 0xdb, 0x21,
|
||||
0x09, 0x53, 0xc8, 0x97, 0xa5, 0xdf, 0xe2, 0x04, 0x43, 0x2d, 0x93, 0x99, 0x97, 0x50, 0x4b, 0x71,
|
||||
0x89, 0xb1, 0x65, 0xd5, 0xb2, 0xa9, 0x6c, 0xbf, 0x51, 0x49, 0xe4, 0x03, 0xfe, 0xd2, 0xba, 0xdf,
|
||||
0x28, 0x71, 0x85, 0x91, 0xe4, 0xd1, 0x26, 0xf3, 0x74, 0x96, 0xc7, 0x37, 0xa7, 0x85, 0x6e, 0x8a,
|
||||
0x27, 0x53, 0x6b, 0xcd, 0x6d, 0x25, 0x79, 0x2c, 0x7d, 0xcc, 0x32, 0x8c, 0xff, 0xa0, 0x38, 0xc3,
|
||||
0xb9, 0x1f, 0xfe, 0xa4, 0x65, 0x19, 0xb5, 0xf7, 0x3c, 0xde, 0x5d, 0x1f, 0x26, 0x0a, 0x3e, 0x26,
|
||||
0x0a, 0x8e, 0x13, 0xc1, 0xb3, 0x23, 0x78, 0x73, 0x04, 0xef, 0x8e, 0xe0, 0xe0, 0x08, 0x3e, 0x1d,
|
||||
0xc1, 0xb7, 0xa3, 0xe0, 0xe8, 0x08, 0x5e, 0xbe, 0x28, 0x68, 0x16, 0xfe, 0xe1, 0xdb, 0x9f, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0x88, 0x3d, 0x22, 0x56, 0x09, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -16,18 +16,11 @@ package pb;
|
|||
* protobuf format before writing to disk.
|
||||
*/
|
||||
message header {
|
||||
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;
|
||||
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
|
||||
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -35,5 +28,5 @@ message sender {
|
|||
* key. WrappedKey describes such a wrapped key.
|
||||
*/
|
||||
message wrapped_key {
|
||||
bytes key = 2;
|
||||
bytes d_key = 1; // encrypted data key
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
// wrap.go - wrap keys and sender as needed
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// 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
|
||||
}
|
304
sign/encrypt.go
304
sign/encrypt.go
|
@ -56,6 +56,7 @@ import (
|
|||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
|
@ -76,8 +77,12 @@ const (
|
|||
|
||||
_Magic = "SigTool"
|
||||
_MagicLen = len(_Magic)
|
||||
_AEADNonceLen = 32
|
||||
_AEADNonceLen = 16
|
||||
_FixedHdrLen = _MagicLen + 1 + 4
|
||||
|
||||
_WrapReceiverNonce = "Receiver Key Nonce"
|
||||
_WrapSenderNonce = "Sender Sig Nonce"
|
||||
_EncryptNonce = "Encrypt Nonce"
|
||||
)
|
||||
|
||||
// Encryptor holds the encryption context
|
||||
|
@ -87,9 +92,8 @@ type Encryptor struct {
|
|||
|
||||
ae cipher.AEAD
|
||||
|
||||
// sender ephemeral curve 25519 SK
|
||||
// the corresponding PK is in Header above
|
||||
senderSK []byte
|
||||
// ephemeral key
|
||||
encSK []byte
|
||||
|
||||
started bool
|
||||
|
||||
|
@ -98,9 +102,8 @@ type Encryptor struct {
|
|||
stream bool
|
||||
}
|
||||
|
||||
// Create a new Encryption context and use the optional private key 'sk' for
|
||||
// signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys
|
||||
// are generated and used with recipient's public key.
|
||||
// Create a new Encryption context for encrypting blocks of size 'blksize'.
|
||||
// If 'sk' is not nil, authenticate the sender to each receiver.
|
||||
func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
||||
var blksz uint32
|
||||
|
||||
|
@ -113,7 +116,8 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
|||
blksz = uint32(blksize)
|
||||
}
|
||||
|
||||
csk, cpk, err := newSender()
|
||||
// generate ephemeral Curve25519 keys
|
||||
esk, epk, err := newSender()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
|
@ -121,17 +125,25 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
|||
key := make([]byte, 32)
|
||||
salt := make([]byte, _AEADNonceLen)
|
||||
|
||||
pb.Randread(key)
|
||||
pb.Randread(salt)
|
||||
randRead(key)
|
||||
randRead(salt)
|
||||
|
||||
// if sender has provided their identity to authenticate, we will use their PK
|
||||
senderPK := cpk
|
||||
// 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.
|
||||
var senderSig []byte
|
||||
if sk != nil {
|
||||
epk := sk.PublicKey()
|
||||
senderPK = epk.toCurve25519PK()
|
||||
sig, err := sk.SignMessage(key, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: can't sign: %s", err)
|
||||
}
|
||||
|
||||
wPk, err := pb.WrapSenderPK(senderPK, key, salt)
|
||||
senderSig = sig.Sig
|
||||
} else {
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
senderSig = zero[:]
|
||||
}
|
||||
|
||||
wSig, err := wrapSenderSig(senderSig, key, salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
|
@ -140,14 +152,12 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
|||
Header: pb.Header{
|
||||
ChunkSize: blksz,
|
||||
Salt: salt,
|
||||
Pk: cpk,
|
||||
SenderPk: &pb.Sender{
|
||||
Pk: wPk,
|
||||
},
|
||||
Pk: epk,
|
||||
SenderSign: wSig,
|
||||
},
|
||||
|
||||
key: key,
|
||||
senderSK: csk,
|
||||
encSK: esk,
|
||||
}
|
||||
|
||||
return e, nil
|
||||
|
@ -159,7 +169,7 @@ func (e *Encryptor) AddRecipient(pk *PublicKey) error {
|
|||
return fmt.Errorf("encrypt: can't add new recipient after encryption has started")
|
||||
}
|
||||
|
||||
w, err := wrapKey(pk, e.key, e.senderSK, e.Salt)
|
||||
w, err := e.wrapKey(pk)
|
||||
if err == nil {
|
||||
e.Keys = append(e.Keys, w)
|
||||
}
|
||||
|
@ -241,7 +251,7 @@ func (e *Encryptor) start(wr io.Writer) error {
|
|||
|
||||
// we mix the header checksum to create the encryption key
|
||||
h = sha256.New()
|
||||
h.Write([]byte("Encrypt Nonce"))
|
||||
h.Write([]byte(_EncryptNonce))
|
||||
h.Write(e.key)
|
||||
h.Write(sumHdr)
|
||||
key := h.Sum(nil)
|
||||
|
@ -286,7 +296,7 @@ func fullwrite(buf []byte, wr io.Writer) error {
|
|||
// additional data in the AEAD construction.
|
||||
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error {
|
||||
var b [8]byte
|
||||
var noncebuf [32]byte
|
||||
var nonceb [32]byte
|
||||
var z uint32 = uint32(len(buf))
|
||||
|
||||
// mark last block
|
||||
|
@ -300,7 +310,7 @@ func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error
|
|||
h := sha256.New()
|
||||
h.Write(e.Salt)
|
||||
h.Write(b[:])
|
||||
nonce := h.Sum(noncebuf[:0])
|
||||
nonce := h.Sum(nonceb[:0])[:e.ae.NonceSize()]
|
||||
|
||||
copy(e.buf[:4], b[:4])
|
||||
cbuf := e.buf[4:]
|
||||
|
@ -324,6 +334,9 @@ type Decryptor struct {
|
|||
buf []byte
|
||||
hdrsum []byte
|
||||
|
||||
// flag set to true if sender signed the key
|
||||
auth bool
|
||||
|
||||
// Decrypted key
|
||||
key []byte
|
||||
eof bool
|
||||
|
@ -401,7 +414,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
|||
|
||||
// sanity check on the wrapped keys
|
||||
for i, w := range d.Keys {
|
||||
if len(w.Key) <= 32+12 {
|
||||
if len(w.DKey) <= 32 {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
|
||||
}
|
||||
}
|
||||
|
@ -416,7 +429,7 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
|||
var key []byte
|
||||
|
||||
for i, w := range d.Keys {
|
||||
key, err = unwrapKey(w.Key, sk, d.Pk, d.Salt)
|
||||
key, err = d.unwrapKey(w, sk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err)
|
||||
}
|
||||
|
@ -428,25 +441,15 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
|||
return fmt.Errorf("decrypt: wrong key")
|
||||
|
||||
havekey:
|
||||
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)
|
||||
if err := d.verifySender(key, sk, senderPk); err != nil {
|
||||
return fmt.Errorf("decrypt: %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([]byte(_EncryptNonce))
|
||||
h.Write(d.key)
|
||||
h.Write(d.hdrsum)
|
||||
key = h.Sum(nil)
|
||||
|
@ -464,72 +467,10 @@ 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() []*pb.WrappedKey {
|
||||
return d.Keys
|
||||
// AuthenticatedSender returns true if the sender authenticated themselves
|
||||
// (the data-encryption key is signed).
|
||||
func (d *Decryptor) AuthenticatedSender() bool {
|
||||
return d.auth
|
||||
}
|
||||
|
||||
// Decrypt the file and write to 'wr'
|
||||
|
@ -567,6 +508,143 @@ func (d *Decryptor) Decrypt(wr io.Writer) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Wrap sender's signature of the encryption key
|
||||
func wrapSenderSig(sig []byte, key, salt []byte) ([]byte, error) {
|
||||
aes, err := aes.NewCipher(key)
|
||||
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()
|
||||
nonceSize := ae.NonceSize()
|
||||
|
||||
nonce := makeNonce([]byte(_WrapSenderNonce), salt)[:nonceSize]
|
||||
esig := make([]byte, tagsize+len(sig))
|
||||
|
||||
return ae.Seal(esig[:0], nonce, sig, nil), 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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
nonceSize := ae.NonceSize()
|
||||
nonce := makeNonce([]byte(_WrapSenderNonce), d.Salt)[:nonceSize]
|
||||
sig := make([]byte, ed25519.SignatureSize)
|
||||
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: can't open sender info: %s", err)
|
||||
}
|
||||
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
|
||||
// Did the sender actually sign anything?
|
||||
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
|
||||
d.auth = true
|
||||
|
||||
if senderPK != nil {
|
||||
ss := &Signature{
|
||||
Sig: sig,
|
||||
}
|
||||
|
||||
ok := senderPK.VerifyMessage(key, ss)
|
||||
if !ok {
|
||||
return fmt.Errorf("unwrap: sender verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
|
||||
// basically, we do two scalarmults:
|
||||
// a) Ephemeral encryption/decryption SK x receiver PK
|
||||
// b) Sender's SK x receiver PK
|
||||
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
||||
rxPK := pk.toCurve25519PK()
|
||||
dkek, err := curve25519.X25519(e.encSK, rxPK)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(dkek)
|
||||
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()
|
||||
nonceSize := ae.NonceSize()
|
||||
|
||||
nonceR := makeNonce([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize]
|
||||
ekey := make([]byte, tagsize+len(e.key))
|
||||
|
||||
w := &pb.WrappedKey{
|
||||
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk),
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and
|
||||
// senders ephemeral PublicKey
|
||||
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
||||
ourSK := sk.toCurve25519SK()
|
||||
dkek, err := curve25519.X25519(ourSK, d.Pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(dkek)
|
||||
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)
|
||||
}
|
||||
|
||||
// 32 == AES-256 key size
|
||||
want := 32 + 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 := makeNonce([]byte(_WrapReceiverNonce), d.Salt)[:nonceSize]
|
||||
pk := sk.PublicKey()
|
||||
|
||||
dkey := make([]byte, 32) // 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)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return dkey, nil
|
||||
}
|
||||
|
||||
// Decrypt exactly one chunk of data
|
||||
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
||||
var b [8]byte
|
||||
|
@ -605,7 +683,7 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
|||
h := sha256.New()
|
||||
h.Write(d.Salt)
|
||||
h.Write(b[:])
|
||||
nonce := h.Sum(nonceb[:0])
|
||||
nonce := h.Sum(nonceb[:0])[:d.ae.NonceSize()]
|
||||
|
||||
z := m + ovh
|
||||
n, err = io.ReadFull(d.rd, d.buf[:z])
|
||||
|
@ -632,9 +710,19 @@ func expand(shared, pk []byte) ([]byte, error) {
|
|||
func newSender() (sk, pk []byte, err error) {
|
||||
var csk [32]byte
|
||||
|
||||
pb.Randread(csk[:])
|
||||
pb.Clamp(csk[:])
|
||||
randRead(csk[:])
|
||||
clamp(csk[:])
|
||||
pk, err = curve25519.X25519(csk[:], curve25519.Basepoint)
|
||||
sk = csk[:]
|
||||
return
|
||||
}
|
||||
|
||||
func makeNonce(v ...[]byte) []byte {
|
||||
h := sha256.New()
|
||||
for _, x := range v {
|
||||
h.Write(x)
|
||||
}
|
||||
return h.Sum(nil)[:]
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
|
18
sign/keys.go
18
sign/keys.go
|
@ -28,6 +28,7 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -37,7 +38,6 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/opencoff/go-utils"
|
||||
"github.com/opencoff/sigtool/internal/pb"
|
||||
)
|
||||
|
||||
// Private Ed25519 key
|
||||
|
@ -70,12 +70,6 @@ type Keypair struct {
|
|||
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
|
||||
|
||||
|
@ -349,7 +343,7 @@ func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)
|
|||
pass := sha512.Sum512(pw)
|
||||
salt := make([]byte, 32)
|
||||
|
||||
pb.Randread(salt)
|
||||
randRead(salt)
|
||||
|
||||
// "32" == Length of AES-256 key
|
||||
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
|
||||
|
@ -544,5 +538,13 @@ func clamp(k []byte) []byte {
|
|||
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
|
||||
}
|
||||
|
||||
// EOF
|
||||
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
||||
|
|
12
sign/sign.go
12
sign/sign.go
|
@ -30,6 +30,12 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// An Ed25519 Signature
|
||||
type Signature struct {
|
||||
Sig []byte // Ed25519 sig bytes
|
||||
pkhash []byte // [0:16] SHA256 hash of public key needed for verification
|
||||
}
|
||||
|
||||
// Sign a prehashed Message; return the signature as opaque bytes
|
||||
// Signature is an YAML file:
|
||||
// Comment: source file path
|
||||
|
@ -147,19 +153,19 @@ func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
return pk.VerifyMessage(ck, sig)
|
||||
return pk.VerifyMessage(ck, sig), nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) bool {
|
||||
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
|
||||
return Ed.Verify(x, ck, sig.Sig)
|
||||
}
|
||||
|
||||
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
||||
|
|
|
@ -19,8 +19,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/opencoff/sigtool/internal/pb"
|
||||
)
|
||||
|
||||
// Return a temp dir in a temp-dir
|
||||
|
@ -30,7 +28,7 @@ func tempdir(t *testing.T) string {
|
|||
var b [10]byte
|
||||
|
||||
dn := os.TempDir()
|
||||
pb.Randread(b[:])
|
||||
randRead(b[:])
|
||||
|
||||
tmp := path.Join(dn, fmt.Sprintf("%x", b[:]))
|
||||
err := os.MkdirAll(tmp, 0755)
|
||||
|
@ -137,7 +135,7 @@ func TestSignRandBuf(t *testing.T) {
|
|||
|
||||
var ck [64]byte // simulates sha512 sum
|
||||
|
||||
pb.Randread(ck[:])
|
||||
randRead(ck[:])
|
||||
|
||||
pk := &kp.Pub
|
||||
sk := &kp.Sec
|
||||
|
@ -150,17 +148,15 @@ func TestSignRandBuf(t *testing.T) {
|
|||
assert(ss.IsPKMatch(pk), "pk match fail")
|
||||
|
||||
// Corrupt the pkhash and see
|
||||
pb.Randread(ss.pkhash)
|
||||
randRead(ss.pkhash)
|
||||
assert(!ss.IsPKMatch(pk), "corrupt pk match fail")
|
||||
|
||||
// Incorrect checksum == should fail verification
|
||||
ok, err := pk.VerifyMessage(ck[:16], ss)
|
||||
assert(err == nil, "bad ck verify err fail")
|
||||
ok := pk.VerifyMessage(ck[:16], ss)
|
||||
assert(!ok, "bad ck verify fail")
|
||||
|
||||
// proper checksum == should work
|
||||
ok, err = pk.VerifyMessage(ck[:], ss)
|
||||
assert(err == nil, "verify err")
|
||||
ok = pk.VerifyMessage(ck[:], ss)
|
||||
assert(ok, "verify fail")
|
||||
|
||||
// Now sign a file
|
||||
|
@ -187,7 +183,7 @@ func TestSignRandBuf(t *testing.T) {
|
|||
assert(err == nil, "file.dat creat file")
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
pb.Randread(buf[:])
|
||||
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))
|
||||
|
@ -288,7 +284,7 @@ func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) {
|
|||
|
||||
func randbuf(sz uint) []byte {
|
||||
b := make([]byte, sz)
|
||||
pb.Randread(b)
|
||||
randRead(b)
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
1.0.0
|
||||
1.1.0
|
||||
|
|
Loading…
Add table
Reference in a new issue