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
This commit is contained in:
committed by
Adam Langley
parent
5a4ea2fde3
commit
964142eb52
@@ -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`
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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-----`
|
||||
Reference in New Issue
Block a user