Commit 8834bb0b authored by Adam Langley's avatar Adam Langley

crypto/openpgp: flesh out Encrypt by adding support for signing.

R=bradfitz
CC=golang-dev
https://golang.org/cl/4572059
parent d164b608
...@@ -305,7 +305,7 @@ EachPacket: ...@@ -305,7 +305,7 @@ EachPacket:
return nil, error.StructuralError("user ID packet not followed by self-signature") return nil, error.StructuralError("user ID packet not followed by self-signature")
} }
if sig.SigType == packet.SigTypePositiveCert && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId { if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil { if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil {
return nil, error.StructuralError("user ID self-signature invalid: " + err.String()) return nil, error.StructuralError("user ID self-signature invalid: " + err.String())
} }
......
...@@ -24,6 +24,8 @@ type OnePassSignature struct { ...@@ -24,6 +24,8 @@ type OnePassSignature struct {
IsLast bool IsLast bool
} }
const onePassSignatureVersion = 3
func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) { func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) {
var buf [13]byte var buf [13]byte
...@@ -31,7 +33,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) { ...@@ -31,7 +33,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) {
if err != nil { if err != nil {
return return
} }
if buf[0] != 3 { if buf[0] != onePassSignatureVersion {
err = error.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) err = error.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
} }
...@@ -47,3 +49,26 @@ func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) { ...@@ -47,3 +49,26 @@ func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) {
ops.IsLast = buf[12] != 0 ops.IsLast = buf[12] != 0
return return
} }
// Serialize marshals the given OnePassSignature to w.
func (ops *OnePassSignature) Serialize(w io.Writer) os.Error {
var buf [13]byte
buf[0] = onePassSignatureVersion
buf[1] = uint8(ops.SigType)
var ok bool
buf[2], ok = s2k.HashToHashId(ops.Hash)
if !ok {
return error.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
}
buf[3] = uint8(ops.PubKeyAlgo)
binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
if ops.IsLast {
buf[12] = 1
}
if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
return err
}
_, err := w.Write(buf[:])
return err
}
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"crypto/openpgp/s2k" "crypto/openpgp/s2k"
"crypto/rand" "crypto/rand"
_ "crypto/sha256" _ "crypto/sha256"
"hash"
"io" "io"
"os" "os"
"strconv" "strconv"
...@@ -144,11 +145,18 @@ func hashToHashId(h crypto.Hash) uint8 { ...@@ -144,11 +145,18 @@ func hashToHashId(h crypto.Hash) uint8 {
} }
// Encrypt encrypts a message to a number of recipients and, optionally, signs // Encrypt encrypts a message to a number of recipients and, optionally, signs
// it. (Note: signing is not yet implemented.) hints contains optional // it. hints contains optional information, that is also encrypted, that aids
// information, that is also encrypted, that aids the recipients in processing // the recipients in processing the message. The resulting WriteCloser must
// the message. The resulting WriteCloser must be closed after the contents of // be closed after the contents of the file have been written.
// the file have been written.
func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints) (plaintext io.WriteCloser, err os.Error) { func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints) (plaintext io.WriteCloser, err os.Error) {
var signer *packet.PrivateKey
if signed != nil {
signer = signed.signingKey().PrivateKey
if signer == nil || signer.Encrypted {
return nil, error.InvalidArgumentError("signing key must be decrypted")
}
}
// These are the possible ciphers that we'll use for the message. // These are the possible ciphers that we'll use for the message.
candidateCiphers := []uint8{ candidateCiphers := []uint8{
uint8(packet.CipherAES128), uint8(packet.CipherAES128),
...@@ -194,7 +202,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint ...@@ -194,7 +202,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
} }
cipher := packet.CipherFunction(candidateCiphers[0]) cipher := packet.CipherFunction(candidateCiphers[0])
// hash := s2k.HashIdToHash(candidateHashes[0]) hash, _ := s2k.HashIdToHash(candidateHashes[0])
symKey := make([]byte, cipher.KeySize()) symKey := make([]byte, cipher.KeySize())
if _, err := io.ReadFull(rand.Reader, symKey); err != nil { if _, err := io.ReadFull(rand.Reader, symKey); err != nil {
return nil, err return nil, err
...@@ -206,13 +214,95 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint ...@@ -206,13 +214,95 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
} }
} }
w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey) encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey)
if err != nil { if err != nil {
return return
} }
if signer != nil {
ops := &packet.OnePassSignature{
SigType: packet.SigTypeBinary,
Hash: hash,
PubKeyAlgo: signer.PubKeyAlgo,
KeyId: signer.KeyId,
IsLast: true,
}
if err := ops.Serialize(encryptedData); err != nil {
return nil, err
}
}
if hints == nil { if hints == nil {
hints = &FileHints{} hints = &FileHints{}
} }
return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, hints.EpochSeconds)
w := encryptedData
if signer != nil {
// If we need to write a signature packet after the literal
// data then we need to stop literalData from closing
// encryptedData.
w = noOpCloser{encryptedData}
}
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, hints.EpochSeconds)
if err != nil {
return nil, err
}
if signer != nil {
return signatureWriter{encryptedData, literalData, hash, hash.New(), signer}, nil
}
return literalData, nil
}
// signatureWriter hashes the contents of a message while passing it along to
// literalData. When closed, it closes literalData, writes a signature packet
// to encryptedData and then also closes encryptedData.
type signatureWriter struct {
encryptedData io.WriteCloser
literalData io.WriteCloser
hashType crypto.Hash
h hash.Hash
signer *packet.PrivateKey
}
func (s signatureWriter) Write(data []byte) (int, os.Error) {
s.h.Write(data)
return s.literalData.Write(data)
}
func (s signatureWriter) Close() os.Error {
sig := &packet.Signature{
SigType: packet.SigTypeBinary,
PubKeyAlgo: s.signer.PubKeyAlgo,
Hash: s.hashType,
CreationTime: uint32(time.Seconds()),
IssuerKeyId: &s.signer.KeyId,
}
if err := sig.Sign(s.h, s.signer); err != nil {
return err
}
if err := s.literalData.Close(); err != nil {
return err
}
if err := sig.Serialize(s.encryptedData); err != nil {
return err
}
return s.encryptedData.Close()
}
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
// TODO: we have two of these in OpenPGP packages alone. This probably needs
// to be promoted somewhere more common.
type noOpCloser struct {
w io.Writer
}
func (c noOpCloser) Write(data []byte) (n int, err os.Error) {
return c.w.Write(data)
}
func (c noOpCloser) Close() os.Error {
return nil
} }
...@@ -122,11 +122,16 @@ func TestSymmetricEncryption(t *testing.T) { ...@@ -122,11 +122,16 @@ func TestSymmetricEncryption(t *testing.T) {
} }
} }
func TestEncryption(t *testing.T) { func testEncryption(t *testing.T, isSigned bool) {
kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
var signed *Entity
if isSigned {
signed = kring[0]
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
w, err := Encrypt(buf, kring[:1], nil, /* not signed */ nil /* no hints */ ) w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */ )
if err != nil { if err != nil {
t.Errorf("error in Encrypt: %s", err) t.Errorf("error in Encrypt: %s", err)
return return
...@@ -150,6 +155,16 @@ func TestEncryption(t *testing.T) { ...@@ -150,6 +155,16 @@ func TestEncryption(t *testing.T) {
return return
} }
if isSigned {
expectedKeyId := kring[0].signingKey().PublicKey.KeyId
if md.SignedByKeyId != expectedKeyId {
t.Errorf("message signed by wrong key id, got: %d, want: %d", *md.SignedBy, expectedKeyId)
}
if md.SignedBy == nil {
t.Errorf("failed to find the signing Entity")
}
}
plaintext, err := ioutil.ReadAll(md.UnverifiedBody) plaintext, err := ioutil.ReadAll(md.UnverifiedBody)
if err != nil { if err != nil {
t.Errorf("error reading encrypted contents: %s", err) t.Errorf("error reading encrypted contents: %s", err)
...@@ -164,4 +179,21 @@ func TestEncryption(t *testing.T) { ...@@ -164,4 +179,21 @@ func TestEncryption(t *testing.T) {
if string(plaintext) != message { if string(plaintext) != message {
t.Errorf("got: %s, want: %s", string(plaintext), message) t.Errorf("got: %s, want: %s", string(plaintext), message)
} }
if isSigned {
if md.SignatureError != nil {
t.Errorf("signature error: %s", err)
}
if md.Signature == nil {
t.Error("signature missing")
}
}
}
func TestEncryption(t *testing.T) {
testEncryption(t, false /* not signed */ )
}
func TestEncryptAndSign(t *testing.T) {
testEncryption(t, true /* signed */ )
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment