IsLast bool
}
+const onePassSignatureVersion = 3
+
func (ops *OnePassSignature) parse(r io.Reader) (err os.Error) {
var buf [13]byte
if err != nil {
return
}
- if buf[0] != 3 {
+ if buf[0] != onePassSignatureVersion {
err = error.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
}
ops.IsLast = buf[12] != 0
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
+}
"crypto/openpgp/s2k"
"crypto/rand"
_ "crypto/sha256"
+ "hash"
"io"
"os"
"strconv"
}
// Encrypt encrypts a message to a number of recipients and, optionally, signs
-// it. (Note: signing is not yet implemented.) hints contains optional
-// information, that is also encrypted, that aids the recipients in processing
-// the message. The resulting WriteCloser must be closed after the contents of
-// the file have been written.
+// it. hints contains optional information, that is also encrypted, that aids
+// the recipients in processing the message. The resulting WriteCloser must
+// be closed after the contents of the file have been written.
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.
candidateCiphers := []uint8{
uint8(packet.CipherAES128),
}
cipher := packet.CipherFunction(candidateCiphers[0])
- // hash := s2k.HashIdToHash(candidateHashes[0])
+ hash, _ := s2k.HashIdToHash(candidateHashes[0])
symKey := make([]byte, cipher.KeySize())
if _, err := io.ReadFull(rand.Reader, symKey); err != nil {
return nil, err
}
}
- w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey)
+ encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey)
if err != nil {
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 {
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
}
}
}
-func TestEncryption(t *testing.T) {
+func testEncryption(t *testing.T, isSigned bool) {
kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ var signed *Entity
+ if isSigned {
+ signed = kring[0]
+ }
+
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 {
t.Errorf("error in Encrypt: %s", err)
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)
if err != nil {
t.Errorf("error reading encrypted contents: %s", err)
if 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 */ )
}