]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/openpgp: add package
authorAdam Langley <agl@golang.org>
Fri, 25 Feb 2011 01:19:53 +0000 (20:19 -0500)
committerAdam Langley <agl@golang.org>
Fri, 25 Feb 2011 01:19:53 +0000 (20:19 -0500)
R=bradfitzgo
CC=golang-dev
https://golang.org/cl/3989052

src/pkg/Makefile
src/pkg/crypto/openpgp/Makefile [new file with mode: 0644]
src/pkg/crypto/openpgp/canonical_text.go [new file with mode: 0644]
src/pkg/crypto/openpgp/canonical_text_test.go [new file with mode: 0644]
src/pkg/crypto/openpgp/keys.go [new file with mode: 0644]
src/pkg/crypto/openpgp/packet/packet.go
src/pkg/crypto/openpgp/packet/public_key.go
src/pkg/crypto/openpgp/read.go [new file with mode: 0644]
src/pkg/crypto/openpgp/read_test.go [new file with mode: 0644]
src/pkg/crypto/openpgp/write.go [new file with mode: 0644]
src/pkg/crypto/openpgp/write_test.go [new file with mode: 0644]

index 87b43ed3d70845007001e2e6168102d3493374d8..109af1fc1e38d3f45b2939ec7a7219e68b588fbd 100644 (file)
@@ -42,6 +42,11 @@ DIRS=\
        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 @@ endif
 
 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 (file)
index 0000000..b46ac2b
--- /dev/null
@@ -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 (file)
index 0000000..293eff3
--- /dev/null
@@ -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 (file)
index 0000000..69ecf91
--- /dev/null
@@ -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 (file)
index 0000000..ecaa86f
--- /dev/null
@@ -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
+}
index be8ce75f5f16a2bf1d2ec847152e58007ac78a26..269603ba4985557a0885db6c43a85697cf22a318 100644 (file)
@@ -261,13 +261,13 @@ func Read(r io.Reader) (p Packet, err os.Error) {
        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:
index 4a2ed0aca1536650afa3c73f7a5e7de3e1ccc46e..8866bdaaa9474c280c2fd06bb7703eccd74b2a1d 100644 (file)
@@ -23,7 +23,7 @@ type PublicKey struct {
        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 (file)
index 0000000..ac6998f
--- /dev/null
@@ -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 (file)
index 0000000..7e73dec
--- /dev/null
@@ -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 (file)
index 0000000..1a2e2bf
--- /dev/null
@@ -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 (file)
index 0000000..33e8809
--- /dev/null
@@ -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")
+}