crypto/openpgp: add package

R=bradfitzgo
CC=golang-dev
https://golang.org/cl/3989052
diff --git a/src/pkg/Makefile b/src/pkg/Makefile
index 87b43ed..109af1f 100644
--- a/src/pkg/Makefile
+++ b/src/pkg/Makefile
@@ -42,6 +42,11 @@
 	crypto/md4\
 	crypto/md5\
 	crypto/ocsp\
+	crypto/openpgp\
+	crypto/openpgp/armor\
+	crypto/openpgp/error\
+	crypto/openpgp/packet\
+	crypto/openpgp/s2k\
 	crypto/rand\
 	crypto/rc4\
 	crypto/ripemd160\
@@ -158,6 +163,7 @@
 
 NOTEST=\
 	crypto\
+	crypto/openpgp/error\
 	debug/proc\
 	exp/draw/x11\
 	go/ast\
diff --git a/src/pkg/crypto/openpgp/Makefile b/src/pkg/crypto/openpgp/Makefile
new file mode 100644
index 0000000..b46ac2b
--- /dev/null
+++ b/src/pkg/crypto/openpgp/Makefile
@@ -0,0 +1,14 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../Make.inc
+
+TARG=crypto/openpgp
+GOFILES=\
+	canonical_text.go\
+	keys.go\
+	read.go\
+	write.go\
+
+include ../../../Make.pkg
diff --git a/src/pkg/crypto/openpgp/canonical_text.go b/src/pkg/crypto/openpgp/canonical_text.go
new file mode 100644
index 0000000..293eff3
--- /dev/null
+++ b/src/pkg/crypto/openpgp/canonical_text.go
@@ -0,0 +1,58 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"hash"
+	"os"
+)
+
+// NewCanonicalTextHash reformats text written to it into the canonical
+// form and then applies the hash h.  See RFC 4880, section 5.2.1.
+func NewCanonicalTextHash(h hash.Hash) hash.Hash {
+	return &canonicalTextHash{h, 0}
+}
+
+type canonicalTextHash struct {
+	h hash.Hash
+	s int
+}
+
+var newline = []byte{'\r', '\n'}
+
+func (cth *canonicalTextHash) Write(buf []byte) (int, os.Error) {
+	start := 0
+
+	for i, c := range buf {
+		switch cth.s {
+		case 0:
+			if c == '\r' {
+				cth.s = 1
+			} else if c == '\n' {
+				cth.h.Write(buf[start:i])
+				cth.h.Write(newline)
+				start = i + 1
+			}
+		case 1:
+			cth.s = 0
+		}
+	}
+
+	cth.h.Write(buf[start:])
+	return len(buf), nil
+}
+
+func (cth *canonicalTextHash) Sum() []byte {
+	return cth.h.Sum()
+}
+
+func (cth *canonicalTextHash) Reset() {
+	cth.h.Reset()
+	cth.s = 0
+}
+
+func (cth *canonicalTextHash) Size() int {
+	return cth.h.Size()
+}
diff --git a/src/pkg/crypto/openpgp/canonical_text_test.go b/src/pkg/crypto/openpgp/canonical_text_test.go
new file mode 100644
index 0000000..69ecf91
--- /dev/null
+++ b/src/pkg/crypto/openpgp/canonical_text_test.go
@@ -0,0 +1,50 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"os"
+	"testing"
+)
+
+type recordingHash struct {
+	buf *bytes.Buffer
+}
+
+func (r recordingHash) Write(b []byte) (n int, err os.Error) {
+	return r.buf.Write(b)
+}
+
+func (r recordingHash) Sum() []byte {
+	return r.buf.Bytes()
+}
+
+func (r recordingHash) Reset() {
+	panic("shouldn't be called")
+}
+
+func (r recordingHash) Size() int {
+	panic("shouldn't be called")
+}
+
+
+func testCanonicalText(t *testing.T, input, expected string) {
+	r := recordingHash{bytes.NewBuffer(nil)}
+	c := NewCanonicalTextHash(r)
+	c.Write([]byte(input))
+	result := c.Sum()
+	if expected != string(result) {
+		t.Errorf("input: %x got: %x want: %x", input, result, expected)
+	}
+}
+
+func TestCanonicalText(t *testing.T) {
+	testCanonicalText(t, "foo\n", "foo\r\n")
+	testCanonicalText(t, "foo", "foo")
+	testCanonicalText(t, "foo\r\n", "foo\r\n")
+	testCanonicalText(t, "foo\r\nbar", "foo\r\nbar")
+	testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n")
+}
diff --git a/src/pkg/crypto/openpgp/keys.go b/src/pkg/crypto/openpgp/keys.go
new file mode 100644
index 0000000..ecaa86f
--- /dev/null
+++ b/src/pkg/crypto/openpgp/keys.go
@@ -0,0 +1,280 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"crypto/openpgp/error"
+	"crypto/openpgp/packet"
+	"io"
+	"os"
+)
+
+// PublicKeyType is the armor type for a PGP public key.
+var PublicKeyType = "PGP PUBLIC KEY BLOCK"
+
+// An Entity represents the components of an OpenPGP key: a primary public key
+// (which must be a signing key), one or more identities claimed by that key,
+// and zero or more subkeys, which may be encryption keys.
+type Entity struct {
+	PrimaryKey *packet.PublicKey
+	PrivateKey *packet.PrivateKey
+	Identities map[string]*Identity // indexed by Identity.Name
+	Subkeys    []Subkey
+}
+
+// An Identity represents an identity claimed by an Entity and zero or more
+// assertions by other entities about that claim.
+type Identity struct {
+	Name          string // by convention, has the form "Full Name (comment) <email@example.com>"
+	UserId        *packet.UserId
+	SelfSignature *packet.Signature
+	Signatures    []*packet.Signature
+}
+
+// A Subkey is an additional public key in an Entity. Subkeys can be used for
+// encryption.
+type Subkey struct {
+	PublicKey  *packet.PublicKey
+	PrivateKey *packet.PrivateKey
+	Sig        *packet.Signature
+}
+
+// A Key identifies a specific public key in an Entity. This is either the
+// Entity's primary key or a subkey.
+type Key struct {
+	Entity        *Entity
+	PublicKey     *packet.PublicKey
+	PrivateKey    *packet.PrivateKey
+	SelfSignature *packet.Signature
+}
+
+// A KeyRing provides access to public and private keys.
+type KeyRing interface {
+	// KeysById returns the set of keys that have the given key id.
+	KeysById(id uint64) []Key
+	// DecryptionKeys returns all private keys that are valid for
+	// decryption.
+	DecryptionKeys() []Key
+}
+
+// An EntityList contains one or more Entities.
+type EntityList []*Entity
+
+// KeysById returns the set of keys that have the given key id.
+func (el EntityList) KeysById(id uint64) (keys []Key) {
+	for _, e := range el {
+		if e.PrimaryKey.KeyId == id {
+			var selfSig *packet.Signature
+			for _, ident := range e.Identities {
+				if selfSig == nil {
+					selfSig = ident.SelfSignature
+				} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+					selfSig = ident.SelfSignature
+					break
+				}
+			}
+			keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
+		}
+
+		for _, subKey := range e.Subkeys {
+			if subKey.PublicKey.KeyId == id {
+				keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+			}
+		}
+	}
+	return
+}
+
+// DecryptionKeys returns all private keys that are valid for decryption.
+func (el EntityList) DecryptionKeys() (keys []Key) {
+	for _, e := range el {
+		for _, subKey := range e.Subkeys {
+			if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
+				keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+			}
+		}
+	}
+	return
+}
+
+// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
+func ReadArmoredKeyRing(r io.Reader) (EntityList, os.Error) {
+	body, err := readArmored(r, PublicKeyType)
+	if err != nil {
+		return nil, err
+	}
+
+	return ReadKeyRing(body)
+}
+
+// ReadKeyRing reads one or more public/private keys, ignoring unsupported keys.
+func ReadKeyRing(r io.Reader) (el EntityList, err os.Error) {
+	packets := packet.NewReader(r)
+
+	for {
+		var e *Entity
+		e, err = readEntity(packets)
+		if err != nil {
+			if _, ok := err.(error.UnsupportedError); ok {
+				err = readToNextPublicKey(packets)
+			}
+			if err == os.EOF {
+				err = nil
+				return
+			}
+			if err != nil {
+				el = nil
+				return
+			}
+		} else {
+			el = append(el, e)
+		}
+	}
+	return
+}
+
+// readToNextPublicKey reads packets until the start of the entity and leaves
+// the first packet of the new entity in the Reader.
+func readToNextPublicKey(packets *packet.Reader) (err os.Error) {
+	var p packet.Packet
+	for {
+		p, err = packets.Next()
+		if err == os.EOF {
+			return
+		} else if err != nil {
+			if _, ok := err.(error.UnsupportedError); ok {
+				err = nil
+				continue
+			}
+			return
+		}
+
+		if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
+			packets.Unread(p)
+			return
+		}
+	}
+
+	panic("unreachable")
+}
+
+// readEntity reads an entity (public key, identities, subkeys etc) from the
+// given Reader.
+func readEntity(packets *packet.Reader) (*Entity, os.Error) {
+	e := new(Entity)
+	e.Identities = make(map[string]*Identity)
+
+	p, err := packets.Next()
+	if err != nil {
+		return nil, err
+	}
+
+	var ok bool
+	if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
+		if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
+			packets.Unread(p)
+			return nil, error.StructuralError("first packet was not a public/private key")
+		} else {
+			e.PrimaryKey = &e.PrivateKey.PublicKey
+		}
+	}
+
+	var current *Identity
+EachPacket:
+	for {
+		p, err := packets.Next()
+		if err == os.EOF {
+			break
+		} else if err != nil {
+			return nil, err
+		}
+
+		switch pkt := p.(type) {
+		case *packet.UserId:
+			current = new(Identity)
+			current.Name = pkt.Id
+			current.UserId = pkt
+			e.Identities[pkt.Id] = current
+			p, err = packets.Next()
+			if err == os.EOF {
+				err = io.ErrUnexpectedEOF
+			}
+			if err != nil {
+				if _, ok := err.(error.UnsupportedError); ok {
+					return nil, err
+				}
+				return nil, error.StructuralError("identity self-signature invalid: " + err.String())
+			}
+			current.SelfSignature, ok = p.(*packet.Signature)
+			if !ok {
+				return nil, error.StructuralError("user ID packet not followed by self signature")
+			}
+			if current.SelfSignature.SigType != packet.SigTypePositiveCert {
+				return nil, error.StructuralError("user ID self-signature with wrong type")
+			}
+			if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, current.SelfSignature); err != nil {
+				return nil, error.StructuralError("user ID self-signature invalid: " + err.String())
+			}
+		case *packet.Signature:
+			if current == nil {
+				return nil, error.StructuralError("signature packet found before user id packet")
+			}
+			current.Signatures = append(current.Signatures, pkt)
+		case *packet.PrivateKey:
+			if pkt.IsSubkey == false {
+				packets.Unread(p)
+				break EachPacket
+			}
+			err = addSubkey(e, packets, &pkt.PublicKey, pkt)
+			if err != nil {
+				return nil, err
+			}
+		case *packet.PublicKey:
+			if pkt.IsSubkey == false {
+				packets.Unread(p)
+				break EachPacket
+			}
+			err = addSubkey(e, packets, pkt, nil)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			// we ignore unknown packets
+		}
+	}
+
+	if len(e.Identities) == 0 {
+		return nil, error.StructuralError("entity without any identities")
+	}
+
+	return e, nil
+}
+
+func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) os.Error {
+	var subKey Subkey
+	subKey.PublicKey = pub
+	subKey.PrivateKey = priv
+	p, err := packets.Next()
+	if err == os.EOF {
+		return io.ErrUnexpectedEOF
+	}
+	if err != nil {
+		return error.StructuralError("subkey signature invalid: " + err.String())
+	}
+	var ok bool
+	subKey.Sig, ok = p.(*packet.Signature)
+	if !ok {
+		return error.StructuralError("subkey packet not followed by signature")
+	}
+	if subKey.Sig.SigType != packet.SigTypeSubkeyBinding {
+		return error.StructuralError("subkey signature with wrong type")
+	}
+	err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig)
+	if err != nil {
+		return error.StructuralError("subkey signature invalid: " + err.String())
+	}
+	e.Subkeys = append(e.Subkeys, subKey)
+	return nil
+}
diff --git a/src/pkg/crypto/openpgp/packet/packet.go b/src/pkg/crypto/openpgp/packet/packet.go
index be8ce75..269603b 100644
--- a/src/pkg/crypto/openpgp/packet/packet.go
+++ b/src/pkg/crypto/openpgp/packet/packet.go
@@ -261,13 +261,13 @@
 	case packetTypePrivateKey, packetTypePrivateSubkey:
 		pk := new(PrivateKey)
 		if tag == packetTypePrivateSubkey {
-			pk.IsSubKey = true
+			pk.IsSubkey = true
 		}
 		p = pk
 	case packetTypePublicKey, packetTypePublicSubkey:
 		pk := new(PublicKey)
 		if tag == packetTypePublicSubkey {
-			pk.IsSubKey = true
+			pk.IsSubkey = true
 		}
 		p = pk
 	case packetTypeCompressed:
diff --git a/src/pkg/crypto/openpgp/packet/public_key.go b/src/pkg/crypto/openpgp/packet/public_key.go
index 4a2ed0a..8866bda 100644
--- a/src/pkg/crypto/openpgp/packet/public_key.go
+++ b/src/pkg/crypto/openpgp/packet/public_key.go
@@ -23,7 +23,7 @@
 	PublicKey    interface{} // Either a *rsa.PublicKey or *dsa.PublicKey
 	Fingerprint  [20]byte
 	KeyId        uint64
-	IsSubKey     bool
+	IsSubkey     bool
 
 	n, e, p, q, g, y parsedMPI
 }
diff --git a/src/pkg/crypto/openpgp/read.go b/src/pkg/crypto/openpgp/read.go
new file mode 100644
index 0000000..ac6998f
--- /dev/null
+++ b/src/pkg/crypto/openpgp/read.go
@@ -0,0 +1,413 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This openpgp package implements high level operations on OpenPGP messages.
+package openpgp
+
+import (
+	"crypto"
+	"crypto/openpgp/armor"
+	"crypto/openpgp/error"
+	"crypto/openpgp/packet"
+	"crypto/rsa"
+	_ "crypto/sha256"
+	"hash"
+	"io"
+	"os"
+	"strconv"
+)
+
+// SignatureType is the armor type for a PGP signature.
+var SignatureType = "PGP SIGNATURE"
+
+// readArmored reads an armored block with the given type.
+func readArmored(r io.Reader, expectedType string) (body io.Reader, err os.Error) {
+	block, err := armor.Decode(r)
+	if err != nil {
+		return
+	}
+
+	if block.Type != expectedType {
+		return nil, error.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
+	}
+
+	return block.Body, nil
+}
+
+// MessageDetails contains the result of parsing an OpenPGP encrypted and/or
+// signed message.
+type MessageDetails struct {
+	IsEncrypted              bool                // true if the message was encrypted.
+	EncryptedToKeyIds        []uint64            // the list of recipient key ids.
+	IsSymmetricallyEncrypted bool                // true if a passphrase could have decrypted the message.
+	DecryptedWith            Key                 // the private key used to decrypt the message, if any.
+	IsSigned                 bool                // true if the message is signed.
+	SignedByKeyId            uint64              // the key id of the signer, if any.
+	SignedBy                 *Key                // the key of the signer, if availible.
+	LiteralData              *packet.LiteralData // the metadata of the contents
+	UnverifiedBody           io.Reader           // the contents of the message.
+
+	// If IsSigned is true and SignedBy is non-zero then the signature will
+	// be verified as UnverifiedBody is read. The signature cannot be
+	// checked until the whole of UnverifiedBody is read so UnverifiedBody
+	// must be consumed until EOF before the data can trusted. Even if a
+	// message isn't signed (or the signer is unknown) the data may contain
+	// an authentication code that is only checked once UnverifiedBody has
+	// been consumed. Once EOF has been seen, the following fields are
+	// valid. (An authentication code failure is reported as a
+	// SignatureError error when reading from UnverifiedBody.)
+
+	SignatureError os.Error          // nil if the signature is good.
+	Signature      *packet.Signature // the signature packet itself.
+
+	decrypted io.ReadCloser
+}
+
+// A PromptFunction is used as a callback by functions that may need to decrypt
+// a private key, or prompt for a passphrase. It is called with a list of
+// acceptable, encrypted private keys and a boolean that indicates whether a
+// passphrase is usable. It should either decrypt a private key or return a
+// passphrase to try. If the decrypted private key or given passphrase isn't
+// correct, the function will be called again, forever. Any error returned will
+// be passed up.
+type PromptFunction func(keys []Key, symmetric bool) ([]byte, os.Error)
+
+// A keyEnvelopePair is used to store a private key with the envelope that
+// contains a symmetric key, encrypted with that key.
+type keyEnvelopePair struct {
+	key          Key
+	encryptedKey *packet.EncryptedKey
+}
+
+// ReadMessage parses an OpenPGP message that may be signed and/or encrypted.
+// The given KeyRing should contain both public keys (for signature
+// verification) and, possibly encrypted, private keys for decrypting.
+func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction) (md *MessageDetails, err os.Error) {
+	var p packet.Packet
+
+	var symKeys []*packet.SymmetricKeyEncrypted
+	var pubKeys []keyEnvelopePair
+	var se *packet.SymmetricallyEncrypted
+
+	packets := packet.NewReader(r)
+	md = new(MessageDetails)
+	md.IsEncrypted = true
+
+	// The message, if encrypted, starts with a number of packets
+	// containing an encrypted decryption key. The decryption key is either
+	// encrypted to a public key, or with a passphrase. This loop
+	// collects these packets.
+ParsePackets:
+	for {
+		p, err = packets.Next()
+		if err != nil {
+			return nil, err
+		}
+		switch p := p.(type) {
+		case *packet.SymmetricKeyEncrypted:
+			// This packet contains the decryption key encrypted with a passphrase.
+			md.IsSymmetricallyEncrypted = true
+			symKeys = append(symKeys, p)
+		case *packet.EncryptedKey:
+			// This packet contains the decryption key encrypted to a public key.
+			md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId)
+			if p.Algo != packet.PubKeyAlgoRSA && p.Algo != packet.PubKeyAlgoRSAEncryptOnly {
+				continue
+			}
+			var keys []Key
+			if p.KeyId == 0 {
+				keys = keyring.DecryptionKeys()
+			} else {
+				keys = keyring.KeysById(p.KeyId)
+			}
+			for _, k := range keys {
+				pubKeys = append(pubKeys, keyEnvelopePair{k, p})
+			}
+		case *packet.SymmetricallyEncrypted:
+			se = p
+			break ParsePackets
+		case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
+			// This message isn't encrypted.
+			if len(symKeys) != 0 || len(pubKeys) != 0 {
+				return nil, error.StructuralError("key material not followed by encrypted message")
+			}
+			packets.Unread(p)
+			return readSignedMessage(packets, nil, keyring)
+		}
+	}
+
+	var candidates []Key
+	var decrypted io.ReadCloser
+
+	// Now that we have the list of encrypted keys we need to decrypt at
+	// least one of them or, if we cannot, we need to call the prompt
+	// function so that it can decrypt a key or give us a passphrase.
+FindKey:
+	for {
+		// See if any of the keys already have a private key availible
+		candidates = candidates[:0]
+		candidateFingerprints := make(map[string]bool)
+
+		for _, pk := range pubKeys {
+			if pk.key.PrivateKey == nil {
+				continue
+			}
+			if !pk.key.PrivateKey.Encrypted {
+				if len(pk.encryptedKey.Key) == 0 {
+					pk.encryptedKey.DecryptRSA(pk.key.PrivateKey.PrivateKey.(*rsa.PrivateKey))
+				}
+				if len(pk.encryptedKey.Key) == 0 {
+					continue
+				}
+				decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
+				if err != nil && err != error.KeyIncorrectError {
+					return nil, err
+				}
+				if decrypted != nil {
+					md.DecryptedWith = pk.key
+					break FindKey
+				}
+			} else {
+				fpr := string(pk.key.PublicKey.Fingerprint[:])
+				if v := candidateFingerprints[fpr]; v {
+					continue
+				}
+				candidates = append(candidates, pk.key)
+				candidateFingerprints[fpr] = true
+			}
+		}
+
+		if len(candidates) == 0 && len(symKeys) == 0 {
+			return nil, error.KeyIncorrectError
+		}
+
+		if prompt == nil {
+			return nil, error.KeyIncorrectError
+		}
+
+		passphrase, err := prompt(candidates, len(symKeys) != 0)
+		if err != nil {
+			return nil, err
+		}
+
+		// Try the symmetric passphrase first
+		if len(symKeys) != 0 && passphrase != nil {
+			for _, s := range symKeys {
+				err = s.Decrypt(passphrase)
+				if err == nil && !s.Encrypted {
+					decrypted, err = se.Decrypt(s.CipherFunc, s.Key)
+					if err != nil && err != error.KeyIncorrectError {
+						return nil, err
+					}
+					if decrypted != nil {
+						break FindKey
+					}
+				}
+
+			}
+		}
+	}
+
+	md.decrypted = decrypted
+	packets.Push(decrypted)
+	return readSignedMessage(packets, md, keyring)
+}
+
+// readSignedMessage reads a possibily signed message if mdin is non-zero then
+// that structure is updated and returned. Otherwise a fresh MessageDetails is
+// used.
+func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing) (md *MessageDetails, err os.Error) {
+	if mdin == nil {
+		mdin = new(MessageDetails)
+	}
+	md = mdin
+
+	var p packet.Packet
+	var h hash.Hash
+	var wrappedHash hash.Hash
+FindLiteralData:
+	for {
+		p, err = packets.Next()
+		if err != nil {
+			return nil, err
+		}
+		switch p := p.(type) {
+		case *packet.Compressed:
+			packets.Push(p.Body)
+		case *packet.OnePassSignature:
+			if !p.IsLast {
+				return nil, error.UnsupportedError("nested signatures")
+			}
+
+			h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
+			if err != nil {
+				md = nil
+				return
+			}
+
+			md.IsSigned = true
+			md.SignedByKeyId = p.KeyId
+			keys := keyring.KeysById(p.KeyId)
+			for _, key := range keys {
+				if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+					continue
+				}
+				md.SignedBy = &key
+			}
+		case *packet.LiteralData:
+			md.LiteralData = p
+			break FindLiteralData
+		}
+	}
+
+	if md.SignedBy != nil {
+		md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md}
+	} else if md.decrypted != nil {
+		md.UnverifiedBody = checkReader{md}
+	} else {
+		md.UnverifiedBody = md.LiteralData.Body
+	}
+
+	return md, nil
+}
+
+// hashForSignature returns a pair of hashes that can be used to verify a
+// signature. The signature may specify that the contents of the signed message
+// should be preprocessed (i.e. to normalise line endings). Thus this function
+// returns two hashes. The second should be used to hash the message itself and
+// performs any needed preprocessing.
+func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, os.Error) {
+	h := hashId.New()
+	if h == nil {
+		return nil, nil, error.UnsupportedError("hash not availible: " + strconv.Itoa(int(hashId)))
+	}
+
+	switch sigType {
+	case packet.SigTypeBinary:
+		return h, h, nil
+	case packet.SigTypeText:
+		return h, NewCanonicalTextHash(h), nil
+	}
+
+	return nil, nil, error.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
+}
+
+// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
+// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger
+// MDC checks.
+type checkReader struct {
+	md *MessageDetails
+}
+
+func (cr checkReader) Read(buf []byte) (n int, err os.Error) {
+	n, err = cr.md.LiteralData.Body.Read(buf)
+	if err == os.EOF {
+		mdcErr := cr.md.decrypted.Close()
+		if mdcErr != nil {
+			err = mdcErr
+		}
+	}
+	return
+}
+
+// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes
+// the data as it is read. When it sees an EOF from the underlying io.Reader
+// it parses and checks a trailing Signature packet and triggers any MDC checks.
+type signatureCheckReader struct {
+	packets        *packet.Reader
+	h, wrappedHash hash.Hash
+	md             *MessageDetails
+}
+
+func (scr *signatureCheckReader) Read(buf []byte) (n int, err os.Error) {
+	n, err = scr.md.LiteralData.Body.Read(buf)
+	scr.wrappedHash.Write(buf[:n])
+	if err == os.EOF {
+		var p packet.Packet
+		p, scr.md.SignatureError = scr.packets.Next()
+		if scr.md.SignatureError != nil {
+			return
+		}
+
+		var ok bool
+		if scr.md.Signature, ok = p.(*packet.Signature); !ok {
+			scr.md.SignatureError = error.StructuralError("LiteralData not followed by Signature")
+			return
+		}
+
+		scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature)
+
+		// The SymmetricallyEncrypted packet, if any, might have an
+		// unsigned hash of its own. In order to check this we need to
+		// close that Reader.
+		if scr.md.decrypted != nil {
+			mdcErr := scr.md.decrypted.Close()
+			if mdcErr != nil {
+				err = mdcErr
+			}
+		}
+	}
+	return
+}
+
+// CheckDetachedSignature takes a signed file and a detached signature and
+// returns the signer if the signature is valid. If the signer isn't know,
+// UnknownIssuerError is returned.
+func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err os.Error) {
+	p, err := packet.Read(signature)
+	if err != nil {
+		return
+	}
+
+	sig, ok := p.(*packet.Signature)
+	if !ok {
+		return nil, error.StructuralError("non signature packet found")
+	}
+
+	if sig.IssuerKeyId == nil {
+		return nil, error.StructuralError("signature doesn't have an issuer")
+	}
+
+	keys := keyring.KeysById(*sig.IssuerKeyId)
+	if len(keys) == 0 {
+		return nil, error.UnknownIssuerError
+	}
+
+	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+	if err != nil {
+		return
+	}
+
+	_, err = io.Copy(wrappedHash, signed)
+	if err != nil && err != os.EOF {
+		return
+	}
+
+	for _, key := range keys {
+		if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+			continue
+		}
+		err = key.PublicKey.VerifySignature(h, sig)
+		if err == nil {
+			return key.Entity, nil
+		}
+	}
+
+	if err != nil {
+		return
+	}
+
+	return nil, error.UnknownIssuerError
+}
+
+// CheckArmoredDetachedSignature performs the same actions as
+// CheckDetachedSignature but expects the signature to be armored.
+func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err os.Error) {
+	body, err := readArmored(signature, SignatureType)
+	if err != nil {
+		return
+	}
+
+	return CheckDetachedSignature(keyring, signed, body)
+}
diff --git a/src/pkg/crypto/openpgp/read_test.go b/src/pkg/crypto/openpgp/read_test.go
new file mode 100644
index 0000000..7e73dec
--- /dev/null
+++ b/src/pkg/crypto/openpgp/read_test.go
@@ -0,0 +1,237 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"crypto/openpgp/error"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func readerFromHex(s string) io.Reader {
+	data, err := hex.DecodeString(s)
+	if err != nil {
+		panic("readerFromHex: bad input")
+	}
+	return bytes.NewBuffer(data)
+}
+
+func TestReadKeyRing(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B {
+		t.Errorf("bad keyring: %#v", kring)
+	}
+}
+
+func TestReadPrivateKeyRing(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil {
+		t.Errorf("bad keyring: %#v", kring)
+	}
+}
+
+func TestGetKeyById(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+	keys := kring.KeysById(0xa34d7e18c20c31bb)
+	if len(keys) != 1 || keys[0].Entity != kring[0] {
+		t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+	}
+
+	keys = kring.KeysById(0xfd94408d4543314f)
+	if len(keys) != 1 || keys[0].Entity != kring[0] {
+		t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+	}
+}
+
+func checkSignedMessage(t *testing.T, signedHex, expected string) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+	md, err := ReadMessage(readerFromHex(signedHex), kring, nil)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.IsSymmetricallyEncrypted {
+		t.Errorf("bad MessageDetails: %#v", md)
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error reading UnverifiedBody: %s", err)
+	}
+	if string(contents) != expected {
+		t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+	}
+	if md.SignatureError != nil || md.Signature == nil {
+		t.Error("failed to validate: %s", md.SignatureError)
+	}
+}
+
+func TestSignedMessage(t *testing.T) {
+	checkSignedMessage(t, signedMessageHex, signedInput)
+}
+
+func TestTextSignedMessage(t *testing.T) {
+	checkSignedMessage(t, signedTextMessageHex, signedTextInput)
+}
+
+func TestSignedEncryptedMessage(t *testing.T) {
+	expected := "Signed and encrypted message\n"
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	prompt := func(keys []Key, symmetric bool) ([]byte, os.Error) {
+		if symmetric {
+			t.Errorf("prompt: message was marked as symmetrically encrypted")
+			return nil, error.KeyIncorrectError
+		}
+
+		if len(keys) == 0 {
+			t.Error("prompt: no keys requested")
+			return nil, error.KeyIncorrectError
+		}
+
+		err := keys[0].PrivateKey.Decrypt([]byte("passphrase"))
+		if err != nil {
+			t.Errorf("prompt: error decrypting key: %s", err)
+			return nil, error.KeyIncorrectError
+		}
+
+		return nil, nil
+	}
+
+	md, err := ReadMessage(readerFromHex(signedEncryptedMessageHex), kring, prompt)
+	if err != nil {
+		t.Errorf("error reading message: %s", err)
+		return
+	}
+
+	if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != 0x2a67d68660df41c7 {
+		t.Errorf("bad MessageDetails: %#v", md)
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error reading UnverifiedBody: %s", err)
+	}
+	if string(contents) != expected {
+		t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+	}
+
+	if md.SignatureError != nil || md.Signature == nil {
+		t.Error("failed to validate: %s", md.SignatureError)
+	}
+}
+
+func TestUnspecifiedRecipient(t *testing.T) {
+	expected := "Recipient unspecified\n"
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+
+	md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil)
+	if err != nil {
+		t.Errorf("error reading message: %s", err)
+		return
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error reading UnverifiedBody: %s", err)
+	}
+	if string(contents) != expected {
+		t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+	}
+}
+
+func TestSymmetricallyEncrypted(t *testing.T) {
+	expected := "Symmetrically encrypted.\n"
+
+	prompt := func(keys []Key, symmetric bool) ([]byte, os.Error) {
+		if len(keys) != 0 {
+			t.Errorf("prompt: len(keys) = %d (want 0)", len(keys))
+		}
+
+		if !symmetric {
+			t.Errorf("symmetric is not set")
+		}
+
+		return []byte("password"), nil
+	}
+
+	md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt)
+	if err != nil {
+		t.Errorf("ReadMessage: %s", err)
+		return
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("ReadAll: %s", err)
+	}
+
+	expectedCreatationTime := uint32(1295992998)
+	if md.LiteralData.Time != expectedCreatationTime {
+		t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreatationTime)
+	}
+
+	if string(contents) != expected {
+		t.Errorf("contents got: %s want: %s", string(contents), expected)
+	}
+}
+
+func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string) {
+	signed := bytes.NewBufferString(sigInput)
+	signer, err := CheckDetachedSignature(kring, signed, signature)
+	if err != nil {
+		t.Errorf("%s: signature error: %s", tag, err)
+		return
+	}
+	if signer == nil {
+		t.Errorf("%s: signer is nil")
+		return
+	}
+	expectedSignerKeyId := uint64(0xa34d7e18c20c31bb)
+	if signer.PrimaryKey.KeyId != expectedSignerKeyId {
+		t.Errorf("%s: wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, expectedSignerKeyId)
+	}
+}
+
+func TestDetachedSignature(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+	testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary")
+	testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text")
+}
+
+const signedInput = "Signed message\nline 2\nline 3\n"
+const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
+
+const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b"
+
+const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77"
+
+const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39"
+
+const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003"
+
+const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000"
+
+const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300"
+
+const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200"
+
+const signedEncryptedMessageHex = "848c032a67d68660df41c70103ff5789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8d2c03b018bd210b1d3791e1aba74b0f1034e122ab72e760492c192383cf5e20b5628bd043272d63df9b923f147eb6091cd897553204832aba48fec54aa447547bb16305a1024713b90e77fd0065f1918271947549205af3c74891af22ee0b56cd29bfec6d6e351901cd4ab3ece7c486f1e32a792d4e474aed98ee84b3f591c7dff37b64e0ecd68fd036d517e412dcadf85840ce184ad7921ad446c4ee28db80447aea1ca8d4f574db4d4e37688158ddd19e14ee2eab4873d46947d65d14a23e788d912cf9a19624ca7352469b72a83866b7c23cb5ace3deab3c7018061b0ba0f39ed2befe27163e5083cf9b8271e3e3d52cc7ad6e2a3bd81d4c3d7022f8d"
+
+const symmetricallyEncryptedCompressedHex = "8c0d04030302eb4a03808145d0d260c92f714339e13de5a79881216431925bf67ee2898ea61815f07894cd0703c50d0a76ef64d482196f47a8bc729af9b80bb6"
diff --git a/src/pkg/crypto/openpgp/write.go b/src/pkg/crypto/openpgp/write.go
new file mode 100644
index 0000000..1a2e2bf
--- /dev/null
+++ b/src/pkg/crypto/openpgp/write.go
@@ -0,0 +1,92 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"crypto"
+	"crypto/openpgp/armor"
+	"crypto/openpgp/error"
+	"crypto/openpgp/packet"
+	"crypto/rsa"
+	_ "crypto/sha256"
+	"io"
+	"os"
+	"strconv"
+	"time"
+)
+
+// DetachSign signs message with the private key from signer (which must
+// already have been decrypted) and writes the signature to w.
+func DetachSign(w io.Writer, signer *Entity, message io.Reader) os.Error {
+	return detachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// ArmoredDetachSign signs message with the private key from signer (which
+// must already have been decrypted) and writes an armored signature to w.
+func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader) (err os.Error) {
+	return armoredDetachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// DetachSignText signs message (after canonicalising the line endings) with
+// the private key from signer (which must already have been decrypted) and
+// writes the signature to w.
+func DetachSignText(w io.Writer, signer *Entity, message io.Reader) os.Error {
+	return detachSign(w, signer, message, packet.SigTypeText)
+}
+
+// ArmoredDetachSignText signs message (after canonicalising the line endings)
+// with the private key from signer (which must already have been decrypted)
+// and writes an armored signature to w.
+func SignTextDetachedArmored(w io.Writer, signer *Entity, message io.Reader) os.Error {
+	return armoredDetachSign(w, signer, message, packet.SigTypeText)
+}
+
+func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err os.Error) {
+	out, err := armor.Encode(w, SignatureType, nil)
+	if err != nil {
+		return
+	}
+	err = detachSign(out, signer, message, sigType)
+	if err != nil {
+		return
+	}
+	return out.Close()
+}
+
+func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err os.Error) {
+	if signer.PrivateKey == nil {
+		return error.InvalidArgumentError("signing key doesn't have a private key")
+	}
+	if signer.PrivateKey.Encrypted {
+		return error.InvalidArgumentError("signing key is encrypted")
+	}
+
+	sig := new(packet.Signature)
+	sig.SigType = sigType
+	sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo
+	sig.Hash = crypto.SHA256
+	sig.CreationTime = uint32(time.Seconds())
+	sig.IssuerKeyId = &signer.PrivateKey.KeyId
+
+	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+	if err != nil {
+		return
+	}
+	io.Copy(wrappedHash, message)
+
+	switch signer.PrivateKey.PubKeyAlgo {
+	case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSASignOnly:
+		priv := signer.PrivateKey.PrivateKey.(*rsa.PrivateKey)
+		err = sig.SignRSA(h, priv)
+	default:
+		err = error.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
+	}
+
+	if err != nil {
+		return
+	}
+
+	return sig.Serialize(w)
+}
diff --git a/src/pkg/crypto/openpgp/write_test.go b/src/pkg/crypto/openpgp/write_test.go
new file mode 100644
index 0000000..33e8809
--- /dev/null
+++ b/src/pkg/crypto/openpgp/write_test.go
@@ -0,0 +1,34 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestSignDetached(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSign(out, kring[0], message)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check")
+}
+
+func TestSignTextDetached(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSignText(out, kring[0], message)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check")
+}