浏览代码

openpgp/packet: Add support for V3 public keys and signatures.

My goal here is to allow read-only access to legacy key material without allowing new V3 keys or signatures.

R=agl
CC=golang-dev
https://golang.org/cl/12746046
Casey Marshall 12 年之前
父节点
当前提交
964142eb52

+ 11 - 21
openpgp/packet/opaque_test.go

@@ -6,36 +6,20 @@ package packet
 
 import (
 	"bytes"
-	"code.google.com/p/go.crypto/openpgp/armor"
+	"encoding/hex"
 	"io"
 	"testing"
 )
 
-// This key contains version 2 public key and signature packets.
-// If these are ever supported, this test will need to be updated
-// with bad packets that won't parse.
-const UnsupportedKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: SKS 1.0.10
-
-mI0CLnoYogAAAQQA1qwA2SuJwfQ5bCQ6u5t20ulnOtY0gykf7YjiK4LiVeRBwHjGq7v30tGV
-5Qti7qqRW4Ww7CDCJc4sZMFnystucR2vLkXaSoNWoFm4Fg47NiisDdhDezHwbVPW6OpCFNSi
-ZAamtj4QAUBu8j4LswafrJqZqR9336/V3g8Yil2l48kABRG0J0FybWluIE0uIFdhcmRhIDx3
-YXJkYUBuZXBoaWxpbS5ydWhyLmRlPoiVAgUQLok2xwXR6zmeWEiZAQE/DgP/WgxPQh40/Po4
-gSkWZCDAjNdph7zexvAb0CcUWahcwiBIgg3U5ErCx9I5CNVA9U+s8bNrDZwgSIeBzp3KhWUx
-524uhGgm6ZUTOAIKA6CbV6pfqoLpJnRYvXYQU5mIWsNa99wcu2qu18OeEDnztb7aLA6Ra9OF
-YFCbq4EjXRoOrYM=
-=LPjs
------END PGP PUBLIC KEY BLOCK-----`
-
 // Test packet.Read error handling in OpaquePacket.Parse,
 // which attempts to re-read an OpaquePacket as a supported
 // Packet type.
 func TestOpaqueParseReason(t *testing.T) {
-	armorBlock, err := armor.Decode(bytes.NewBufferString(UnsupportedKeyArmor))
+	buf, err := hex.DecodeString(UnsupportedKeyHex)
 	if err != nil {
-		t.Fatalf("armor Decode failed: %v", err)
+		t.Fatal(err)
 	}
-	or := NewOpaqueReader(armorBlock.Body)
+	or := NewOpaqueReader(bytes.NewBuffer(buf))
 	count := 0
 	badPackets := 0
 	var uid *UserId
@@ -64,7 +48,7 @@ func TestOpaqueParseReason(t *testing.T) {
 		count++
 	}
 
-	const expectedBad = 2
+	const expectedBad = 3
 	// Test post-conditions, make sure we actually parsed packets as expected.
 	if badPackets != expectedBad {
 		t.Errorf("unexpected # unparseable packets: %d (want %d)", badPackets, expectedBad)
@@ -75,3 +59,9 @@ func TestOpaqueParseReason(t *testing.T) {
 		t.Errorf("unexpected UID: %v", uid.Id)
 	}
 }
+
+// This key material has public key and signature packet versions modified to
+// an unsupported value (1), so that trying to parse the OpaquePacket to
+// a typed packet will get an error. It also contains a GnuPG trust packet.
+// (Created with: od -An -t x1 pubring.gpg | xargs | sed 's/ //g')
+const UnsupportedKeyHex = `988d012e7a18a20000010400d6ac00d92b89c1f4396c243abb9b76d2e9673ad63483291fed88e22b82e255e441c078c6abbbf7d2d195e50b62eeaa915b85b0ec20c225ce2c64c167cacb6e711daf2e45da4a8356a059b8160e3b3628ac0dd8437b31f06d53d6e8ea4214d4a26406a6b63e1001406ef23e0bb3069fac9a99a91f77dfafd5de0f188a5da5e3c9000511b42741726d696e204d2e205761726461203c7761726461406e657068696c696d2e727568722e64653e8900950105102e8936c705d1eb399e58489901013f0e03ff5a0c4f421e34fcfa388129166420c08cd76987bcdec6f01bd0271459a85cc22048820dd4e44ac2c7d23908d540f54facf1b36b0d9c20488781ce9dca856531e76e2e846826e9951338020a03a09b57aa5faa82e9267458bd76105399885ac35af7dc1cbb6aaed7c39e1039f3b5beda2c0e916bd38560509bab81235d1a0ead83b0020000`

+ 33 - 5
openpgp/packet/packet.go

@@ -7,6 +7,7 @@
 package packet
 
 import (
+	"bufio"
 	"code.google.com/p/go.crypto/cast5"
 	"code.google.com/p/go.crypto/openpgp/errors"
 	"crypto/aes"
@@ -297,6 +298,19 @@ const (
 	packetTypeSymmetricallyEncryptedMDC packetType = 18
 )
 
+// peekVersion detects the version of a public key packet about to
+// be read. A bufio.Reader at the original position of the io.Reader
+// is returned.
+func peekVersion(r io.Reader) (bufr *bufio.Reader, ver byte, err error) {
+	bufr = bufio.NewReader(r)
+	var verBuf []byte
+	if verBuf, err = bufr.Peek(1); err != nil {
+		return
+	}
+	ver = verBuf[0]
+	return
+}
+
 // Read reads a single OpenPGP packet from the given io.Reader. If there is an
 // error parsing a packet, the whole packet is consumed from the input.
 func Read(r io.Reader) (p Packet, err error) {
@@ -309,7 +323,16 @@ func Read(r io.Reader) (p Packet, err error) {
 	case packetTypeEncryptedKey:
 		p = new(EncryptedKey)
 	case packetTypeSignature:
-		p = new(Signature)
+		var version byte
+		// Detect signature version
+		if contents, version, err = peekVersion(contents); err != nil {
+			return
+		}
+		if version < 4 {
+			p = new(SignatureV3)
+		} else {
+			p = new(Signature)
+		}
 	case packetTypeSymmetricKeyEncrypted:
 		p = new(SymmetricKeyEncrypted)
 	case packetTypeOnePassSignature:
@@ -321,11 +344,16 @@ func Read(r io.Reader) (p Packet, err error) {
 		}
 		p = pk
 	case packetTypePublicKey, packetTypePublicSubkey:
-		pk := new(PublicKey)
-		if tag == packetTypePublicSubkey {
-			pk.IsSubkey = true
+		var version byte
+		if contents, version, err = peekVersion(contents); err != nil {
+			return
+		}
+		isSubkey := tag == packetTypePublicSubkey
+		if version < 4 {
+			p = &PublicKeyV3{IsSubkey: isSubkey}
+		} else {
+			p = &PublicKey{IsSubkey: isSubkey}
 		}
-		p = pk
 	case packetTypeCompressed:
 		p = new(Compressed)
 	case packetTypeSymmetricallyEncrypted:

+ 71 - 8
openpgp/packet/public_key.go

@@ -8,6 +8,7 @@ import (
 	"bytes"
 	"code.google.com/p/go.crypto/openpgp/elgamal"
 	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto"
 	"crypto/dsa"
 	"crypto/ecdsa"
 	"crypto/elliptic"
@@ -163,6 +164,13 @@ type PublicKey struct {
 	ecdh *ecdhKdf
 }
 
+// signingKey provides a convenient abstraction over signature verification
+// for v3 and v4 public keys.
+type signingKey interface {
+	SerializeSignaturePrefix(io.Writer)
+	serializeWithoutHeaders(io.Writer) error
+}
+
 func fromBig(n *big.Int) parsedMPI {
 	return parsedMPI{
 		bytes:     n.Bytes(),
@@ -493,13 +501,58 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
 	panic("unreachable")
 }
 
+// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKey) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
+	if !pk.CanSign() {
+		return errors.InvalidArgumentError("public key cannot generate signatures")
+	}
+
+	suffix := make([]byte, 5)
+	suffix[0] = byte(sig.SigType)
+	binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
+	signed.Write(suffix)
+	hashBytes := signed.Sum(nil)
+
+	if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+		return errors.SignatureError("hash tag doesn't match")
+	}
+
+	if pk.PubKeyAlgo != sig.PubKeyAlgo {
+		return errors.InvalidArgumentError("public key and signature use different algorithms")
+	}
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		rsaPublicKey := pk.PublicKey.(*rsa.PublicKey)
+		if err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil {
+			return errors.SignatureError("RSA verification failure")
+		}
+		return
+	case PubKeyAlgoDSA:
+		dsaPublicKey := pk.PublicKey.(*dsa.PublicKey)
+		// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+		subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
+		if len(hashBytes) > subgroupSize {
+			hashBytes = hashBytes[:subgroupSize]
+		}
+		if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
+			return errors.SignatureError("DSA verification failure")
+		}
+		return nil
+	default:
+		panic("shouldn't happen")
+	}
+	panic("unreachable")
+}
+
 // keySignatureHash returns a Hash of the message that needs to be signed for
 // pk to assert a subkey relationship to signed.
-func keySignatureHash(pk, signed *PublicKey, sig *Signature) (h hash.Hash, err error) {
-	h = sig.Hash.New()
-	if h == nil {
+func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
+	if !hashFunc.Available() {
 		return nil, errors.UnsupportedError("hash function")
 	}
+	h = hashFunc.New()
 
 	// RFC 4880, section 5.2.4
 	pk.SerializeSignaturePrefix(h)
@@ -512,7 +565,7 @@ func keySignatureHash(pk, signed *PublicKey, sig *Signature) (h hash.Hash, err e
 // VerifyKeySignature returns nil iff sig is a valid signature, made by this
 // public key, of signed.
 func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err error) {
-	h, err := keySignatureHash(pk, signed, sig)
+	h, err := keySignatureHash(pk, signed, sig.Hash)
 	if err != nil {
 		return err
 	}
@@ -521,11 +574,11 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err
 
 // userIdSignatureHash returns a Hash of the message that needs to be signed
 // to assert that pk is a valid key for id.
-func userIdSignatureHash(id string, pk *PublicKey, sig *Signature) (h hash.Hash, err error) {
-	h = sig.Hash.New()
-	if h == nil {
+func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
+	if !hashFunc.Available() {
 		return nil, errors.UnsupportedError("hash function")
 	}
+	h = hashFunc.New()
 
 	// RFC 4880, section 5.2.4
 	pk.SerializeSignaturePrefix(h)
@@ -546,13 +599,23 @@ func userIdSignatureHash(id string, pk *PublicKey, sig *Signature) (h hash.Hash,
 // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
 // public key, of id.
 func (pk *PublicKey) VerifyUserIdSignature(id string, sig *Signature) (err error) {
-	h, err := userIdSignatureHash(id, pk, sig)
+	h, err := userIdSignatureHash(id, pk, sig.Hash)
 	if err != nil {
 		return err
 	}
 	return pk.VerifySignature(h, sig)
 }
 
+// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of id.
+func (pk *PublicKey) VerifyUserIdSignatureV3(id string, sig *SignatureV3) (err error) {
+	h, err := userIdSignatureV3Hash(id, pk, sig.Hash)
+	if err != nil {
+		return err
+	}
+	return pk.VerifySignatureV3(h, sig)
+}
+
 // KeyIdString returns the public key's fingerprint in capital hex
 // (e.g. "6C7EE1B8621CC013").
 func (pk *PublicKey) KeyIdString() string {

+ 274 - 0
openpgp/packet/public_key_v3.go

@@ -0,0 +1,274 @@
+// Copyright 2013 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 packet
+
+import (
+	"crypto"
+	"crypto/md5"
+	"crypto/rsa"
+	"encoding/binary"
+	"fmt"
+	"hash"
+	"io"
+	"math/big"
+	"strconv"
+	"time"
+
+	"code.google.com/p/go.crypto/openpgp/errors"
+)
+
+// PublicKeyV3 represents older, version 3 public keys. These keys are less secure and
+// should not be used for signing or encrypting. They are supported here only for
+// parsing version 3 key material and validating signatures.
+// See RFC 4880, section 5.5.2.
+type PublicKeyV3 struct {
+	CreationTime time.Time
+	DaysToExpire uint16
+	PubKeyAlgo   PublicKeyAlgorithm
+	PublicKey    *rsa.PublicKey
+	Fingerprint  [16]byte
+	KeyId        uint64
+	IsSubkey     bool
+
+	n, e parsedMPI
+}
+
+// newRSAPublicKeyV3 returns a PublicKey that wraps the given rsa.PublicKey.
+// Included here for testing purposes only. RFC 4880, section 5.5.2:
+// "an implementation MUST NOT generate a V3 key, but MAY accept it."
+func newRSAPublicKeyV3(creationTime time.Time, pub *rsa.PublicKey) *PublicKeyV3 {
+	pk := &PublicKeyV3{
+		CreationTime: creationTime,
+		PublicKey:    pub,
+		n:            fromBig(pub.N),
+		e:            fromBig(big.NewInt(int64(pub.E))),
+	}
+
+	pk.setFingerPrintAndKeyId()
+	return pk
+}
+
+func (pk *PublicKeyV3) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.5.2
+	var buf [8]byte
+	if _, err = readFull(r, buf[:]); err != nil {
+		return
+	}
+	if buf[0] < 2 || buf[0] > 3 {
+		return errors.UnsupportedError("public key version")
+	}
+	pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
+	pk.DaysToExpire = binary.BigEndian.Uint16(buf[5:7])
+	pk.PubKeyAlgo = PublicKeyAlgorithm(buf[7])
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		err = pk.parseRSA(r)
+	default:
+		err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
+	}
+	if err != nil {
+		return
+	}
+
+	pk.setFingerPrintAndKeyId()
+	return
+}
+
+func (pk *PublicKeyV3) setFingerPrintAndKeyId() {
+	// RFC 4880, section 12.2
+	fingerPrint := md5.New()
+	fingerPrint.Write(pk.n.bytes)
+	fingerPrint.Write(pk.e.bytes)
+	fingerPrint.Sum(pk.Fingerprint[:0])
+	pk.KeyId = binary.BigEndian.Uint64(pk.n.bytes[len(pk.n.bytes)-8:])
+}
+
+// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKeyV3) parseRSA(r io.Reader) (err error) {
+	if pk.n.bytes, pk.n.bitLength, err = readMPI(r); err != nil {
+		return
+	}
+	if pk.e.bytes, pk.e.bitLength, err = readMPI(r); err != nil {
+		return
+	}
+
+	if len(pk.e.bytes) > 3 {
+		err = errors.UnsupportedError("large public exponent")
+		return
+	}
+	rsa := &rsa.PublicKey{N: new(big.Int).SetBytes(pk.n.bytes)}
+	for i := 0; i < len(pk.e.bytes); i++ {
+		rsa.E <<= 8
+		rsa.E |= int(pk.e.bytes[i])
+	}
+	pk.PublicKey = rsa
+	return
+}
+
+// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
+// The prefix is used when calculating a signature over this public key. See
+// RFC 4880, section 5.2.4.
+func (pk *PublicKeyV3) SerializeSignaturePrefix(w io.Writer) {
+	var pLength uint16
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		pLength += 2 + uint16(len(pk.n.bytes))
+		pLength += 2 + uint16(len(pk.e.bytes))
+	default:
+		panic("unknown public key algorithm")
+	}
+	pLength += 6
+	w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
+	return
+}
+
+func (pk *PublicKeyV3) Serialize(w io.Writer) (err error) {
+	length := 8 // 8 byte header
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		length += 2 + len(pk.n.bytes)
+		length += 2 + len(pk.e.bytes)
+	default:
+		panic("unknown public key algorithm")
+	}
+
+	packetType := packetTypePublicKey
+	if pk.IsSubkey {
+		packetType = packetTypePublicSubkey
+	}
+	if err = serializeHeader(w, packetType, length); err != nil {
+		return
+	}
+	return pk.serializeWithoutHeaders(w)
+}
+
+// serializeWithoutHeaders marshals the PublicKey to w in the form of an
+// OpenPGP public key packet, not including the packet header.
+func (pk *PublicKeyV3) serializeWithoutHeaders(w io.Writer) (err error) {
+	var buf [8]byte
+	// Version 3
+	buf[0] = 3
+	// Creation time
+	t := uint32(pk.CreationTime.Unix())
+	buf[1] = byte(t >> 24)
+	buf[2] = byte(t >> 16)
+	buf[3] = byte(t >> 8)
+	buf[4] = byte(t)
+	// Days to expire
+	buf[5] = byte(pk.DaysToExpire >> 8)
+	buf[6] = byte(pk.DaysToExpire)
+	// Public key algorithm
+	buf[7] = byte(pk.PubKeyAlgo)
+
+	if _, err = w.Write(buf[:]); err != nil {
+		return
+	}
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		return writeMPIs(w, pk.n, pk.e)
+	}
+	return errors.InvalidArgumentError("bad public-key algorithm")
+}
+
+// CanSign returns true iff this public key can generate signatures
+func (pk *PublicKeyV3) CanSign() bool {
+	return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly
+}
+
+// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKeyV3) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
+	if !pk.CanSign() {
+		return errors.InvalidArgumentError("public key cannot generate signatures")
+	}
+
+	suffix := make([]byte, 5)
+	suffix[0] = byte(sig.SigType)
+	binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
+	signed.Write(suffix)
+	hashBytes := signed.Sum(nil)
+
+	if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+		return errors.SignatureError("hash tag doesn't match")
+	}
+
+	if pk.PubKeyAlgo != sig.PubKeyAlgo {
+		return errors.InvalidArgumentError("public key and signature use different algorithms")
+	}
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		if err = rsa.VerifyPKCS1v15(pk.PublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil {
+			return errors.SignatureError("RSA verification failure")
+		}
+		return
+	default:
+		// V3 public keys only support RSA.
+		panic("shouldn't happen")
+	}
+	panic("unreachable")
+}
+
+// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of id.
+func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, sig *SignatureV3) (err error) {
+	h, err := userIdSignatureV3Hash(id, pk, sig.Hash)
+	if err != nil {
+		return err
+	}
+	return pk.VerifySignatureV3(h, sig)
+}
+
+// VerifyKeySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of signed.
+func (pk *PublicKeyV3) VerifyKeySignatureV3(signed *PublicKeyV3, sig *SignatureV3) (err error) {
+	h, err := keySignatureHash(pk, signed, sig.Hash)
+	if err != nil {
+		return err
+	}
+	return pk.VerifySignatureV3(h, sig)
+}
+
+// userIdSignatureV3Hash returns a Hash of the message that needs to be signed
+// to assert that pk is a valid key for id.
+func userIdSignatureV3Hash(id string, pk signingKey, hfn crypto.Hash) (h hash.Hash, err error) {
+	if h = hfn.New(); h == nil {
+		return nil, errors.UnsupportedError("hash function")
+	}
+
+	// RFC 4880, section 5.2.4
+	pk.SerializeSignaturePrefix(h)
+	pk.serializeWithoutHeaders(h)
+
+	h.Write([]byte(id))
+
+	return
+}
+
+// KeyIdString returns the public key's fingerprint in capital hex
+// (e.g. "6C7EE1B8621CC013").
+func (pk *PublicKeyV3) KeyIdString() string {
+	return fmt.Sprintf("%X", pk.KeyId)
+}
+
+// KeyIdShortString returns the short form of public key's fingerprint
+// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
+func (pk *PublicKeyV3) KeyIdShortString() string {
+	return fmt.Sprintf("%X", pk.KeyId&0xFFFFFFFF)
+}
+
+// BitLength returns the bit length for the given public key.
+func (pk *PublicKeyV3) BitLength() (bitLength uint16, err error) {
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		bitLength = pk.n.bitLength
+	default:
+		err = errors.InvalidArgumentError("bad public-key algorithm")
+	}
+	return
+}

+ 82 - 0
openpgp/packet/public_key_v3_test.go

@@ -0,0 +1,82 @@
+// Copyright 2013 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 packet
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+	"time"
+)
+
+var pubKeyV3Test = struct {
+	hexFingerprint string
+	creationTime   time.Time
+	pubKeyAlgo     PublicKeyAlgorithm
+	keyId          uint64
+	keyIdString    string
+	keyIdShort     string
+}{
+	"103BECF5BD1E837C89D19E98487767F7",
+	time.Unix(779753634, 0),
+	PubKeyAlgoRSA,
+	0xDE0F188A5DA5E3C9,
+	"DE0F188A5DA5E3C9",
+	"5DA5E3C9"}
+
+func TestPublicKeyV3Read(t *testing.T) {
+	i, test := 0, pubKeyV3Test
+	packet, err := Read(v3KeyReader(t))
+	if err != nil {
+		t.Fatalf("#%d: Read error: %s", i, err)
+	}
+	pk, ok := packet.(*PublicKeyV3)
+	if !ok {
+		t.Fatalf("#%d: failed to parse, got: %#v", i, packet)
+	}
+	if pk.PubKeyAlgo != test.pubKeyAlgo {
+		t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo)
+	}
+	if !pk.CreationTime.Equal(test.creationTime) {
+		t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime)
+	}
+	expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint)
+	if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) {
+		t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint)
+	}
+	if pk.KeyId != test.keyId {
+		t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId)
+	}
+	if g, e := pk.KeyIdString(), test.keyIdString; g != e {
+		t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e)
+	}
+	if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e {
+		t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e)
+	}
+}
+
+func TestPublicKeyV3Serialize(t *testing.T) {
+	//for i, test := range pubKeyV3Tests {
+	i := 0
+	packet, err := Read(v3KeyReader(t))
+	if err != nil {
+		t.Fatalf("#%d: Read error: %s", i, err)
+	}
+	pk, ok := packet.(*PublicKeyV3)
+	if !ok {
+		t.Fatalf("#%d: failed to parse, got: %#v", i, packet)
+	}
+	var serializeBuf bytes.Buffer
+	if err = pk.Serialize(&serializeBuf); err != nil {
+		t.Fatalf("#%d: failed to serialize: %s", i, err)
+	}
+
+	if packet, err = Read(bytes.NewBuffer(serializeBuf.Bytes())); err != nil {
+		t.Fatalf("#%d: Read error (from serialized data): %s", i, err)
+	}
+	if pk, ok = packet.(*PublicKeyV3); !ok {
+		t.Fatalf("#%d: failed to parse serialized data, got: %#v", i, packet)
+	}
+}

+ 2 - 2
openpgp/packet/signature.go

@@ -481,7 +481,7 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
 // Serialize to write it out.
 // If config is nil, sensible defaults will be used.
 func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error {
-	h, err := userIdSignatureHash(id, pub, sig)
+	h, err := userIdSignatureHash(id, pub, sig.Hash)
 	if err != nil {
 		return nil
 	}
@@ -492,7 +492,7 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co
 // success, the signature is stored in sig. Call Serialize to write it out.
 // If config is nil, sensible defaults will be used.
 func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error {
-	h, err := keySignatureHash(&priv.PublicKey, pub, sig)
+	h, err := keySignatureHash(&priv.PublicKey, pub, sig.Hash)
 	if err != nil {
 		return err
 	}

+ 146 - 0
openpgp/packet/signature_v3.go

@@ -0,0 +1,146 @@
+// Copyright 2013 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 packet
+
+import (
+	"crypto"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"strconv"
+	"time"
+
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+)
+
+// SignatureV3 represents older version 3 signatures. These signatures are less secure
+// than version 4 and should not be used to create new signatures. They are included
+// here for backwards compatibility to read and validate with older key material.
+// See RFC 4880, section 5.2.2.
+type SignatureV3 struct {
+	SigType      SignatureType
+	CreationTime time.Time
+	IssuerKeyId  uint64
+	PubKeyAlgo   PublicKeyAlgorithm
+	Hash         crypto.Hash
+	HashTag      [2]byte
+
+	RSASignature     parsedMPI
+	DSASigR, DSASigS parsedMPI
+}
+
+func (sig *SignatureV3) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.2.2
+	var buf [8]byte
+	if _, err = readFull(r, buf[:1]); err != nil {
+		return
+	}
+	if buf[0] < 2 || buf[0] > 3 {
+		err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
+		return
+	}
+	if _, err = readFull(r, buf[:1]); err != nil {
+		return
+	}
+	if buf[0] != 5 {
+		err = errors.UnsupportedError(
+			"invalid hashed material length " + strconv.Itoa(int(buf[0])))
+		return
+	}
+
+	// Read hashed material: signature type + creation time
+	if _, err = readFull(r, buf[:5]); err != nil {
+		return
+	}
+	sig.SigType = SignatureType(buf[0])
+	t := binary.BigEndian.Uint32(buf[1:5])
+	sig.CreationTime = time.Unix(int64(t), 0)
+
+	// Eight-octet Key ID of signer.
+	if _, err = readFull(r, buf[:8]); err != nil {
+		return
+	}
+	sig.IssuerKeyId = binary.BigEndian.Uint64(buf[:])
+
+	// Public-key and hash algorithm
+	if _, err = readFull(r, buf[:2]); err != nil {
+		return
+	}
+	sig.PubKeyAlgo = PublicKeyAlgorithm(buf[0])
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+	default:
+		err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
+		return
+	}
+	var ok bool
+	if sig.Hash, ok = s2k.HashIdToHash(buf[1]); !ok {
+		return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
+	}
+
+	// Two-octet field holding left 16 bits of signed hash value.
+	if _, err = readFull(r, sig.HashTag[:2]); err != nil {
+		return
+	}
+
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
+	case PubKeyAlgoDSA:
+		if sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r); err != nil {
+			return
+		}
+		sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
+	default:
+		panic("unreachable")
+	}
+	return
+}
+
+// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
+// called first.
+func (sig *SignatureV3) Serialize(w io.Writer) (err error) {
+	buf := make([]byte, 8)
+
+	// Write the sig type and creation time
+	buf[0] = byte(sig.SigType)
+	binary.BigEndian.PutUint32(buf[1:5], uint32(sig.CreationTime.Unix()))
+	if _, err = w.Write(buf[:5]); err != nil {
+		return
+	}
+
+	// Write the issuer long key ID
+	binary.BigEndian.PutUint64(buf[:8], sig.IssuerKeyId)
+	if _, err = w.Write(buf[:8]); err != nil {
+		return
+	}
+
+	// Write public key algorithm, hash ID, and hash value
+	buf[0] = byte(sig.PubKeyAlgo)
+	hashId, ok := s2k.HashToHashId(sig.Hash)
+	if !ok {
+		return errors.UnsupportedError(fmt.Sprintf("hash function %v", sig.Hash))
+	}
+	buf[1] = hashId
+	copy(buf[2:4], sig.HashTag[:])
+	if _, err = w.Write(buf[:4]); err != nil {
+		return
+	}
+
+	if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
+		return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
+	}
+
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		err = writeMPIs(w, sig.RSASignature)
+	case PubKeyAlgoDSA:
+		err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+	default:
+		panic("impossible")
+	}
+	return
+}

+ 92 - 0
openpgp/packet/signature_v3_test.go

@@ -0,0 +1,92 @@
+// Copyright 2013 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 packet
+
+import (
+	"bytes"
+	"crypto"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"testing"
+
+	"code.google.com/p/go.crypto/openpgp/armor"
+)
+
+func TestSignatureV3Read(t *testing.T) {
+	r := v3KeyReader(t)
+	Read(r)                // Skip public key
+	Read(r)                // Skip uid
+	packet, err := Read(r) // Signature
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	sig, ok := packet.(*SignatureV3)
+	if !ok || sig.SigType != SigTypeGenericCert || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.MD5 {
+		t.Errorf("failed to parse, got: %#v", packet)
+	}
+}
+
+func TestSignatureV3Reserialize(t *testing.T) {
+	r := v3KeyReader(t)
+	Read(r) // Skip public key
+	Read(r) // Skip uid
+	packet, err := Read(r)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	sig := packet.(*SignatureV3)
+	out := new(bytes.Buffer)
+	if err = sig.Serialize(out); err != nil {
+		t.Errorf("error reserializing: %s", err)
+		return
+	}
+	expected, err := ioutil.ReadAll(v3KeyReader(t))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	expected = expected[4+141+4+39:] // See pgpdump offsets below, this is where the sig starts
+	if !bytes.Equal(expected, out.Bytes()) {
+		t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected))
+	}
+}
+
+func v3KeyReader(t *testing.T) io.Reader {
+	armorBlock, err := armor.Decode(bytes.NewBufferString(keySigV3Armor))
+	if err != nil {
+		t.Fatalf("armor Decode failed: %v", err)
+	}
+	return armorBlock.Body
+}
+
+// keySigV3Armor is some V3 public key I found in an SKS dump.
+// Old: Public Key Packet(tag 6)(141 bytes)
+//      Ver 4 - new
+//      Public key creation time - Fri Sep 16 17:13:54 CDT 1994
+//      Pub alg - unknown(pub 0)
+//      Unknown public key(pub 0)
+// Old: User ID Packet(tag 13)(39 bytes)
+//      User ID - Armin M. Warda <warda@nephilim.ruhr.de>
+// Old: Signature Packet(tag 2)(149 bytes)
+//      Ver 4 - new
+//      Sig type - unknown(05)
+//      Pub alg - ElGamal Encrypt-Only(pub 16)
+//      Hash alg - unknown(hash 46)
+//      Hashed Sub: unknown(sub 81, critical)(1988 bytes)
+const keySigV3Armor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: SKS 1.0.10
+
+mI0CLnoYogAAAQQA1qwA2SuJwfQ5bCQ6u5t20ulnOtY0gykf7YjiK4LiVeRBwHjGq7v30tGV
+5Qti7qqRW4Ww7CDCJc4sZMFnystucR2vLkXaSoNWoFm4Fg47NiisDdhDezHwbVPW6OpCFNSi
+ZAamtj4QAUBu8j4LswafrJqZqR9336/V3g8Yil2l48kABRG0J0FybWluIE0uIFdhcmRhIDx3
+YXJkYUBuZXBoaWxpbS5ydWhyLmRlPoiVAgUQLok2xwXR6zmeWEiZAQE/DgP/WgxPQh40/Po4
+gSkWZCDAjNdph7zexvAb0CcUWahcwiBIgg3U5ErCx9I5CNVA9U+s8bNrDZwgSIeBzp3KhWUx
+524uhGgm6ZUTOAIKA6CbV6pfqoLpJnRYvXYQU5mIWsNa99wcu2qu18OeEDnztb7aLA6Ra9OF
+YFCbq4EjXRoOrYM=
+=LPjs
+-----END PGP PUBLIC KEY BLOCK-----`