mirror of
https://github.com/octoleo/restic.git
synced 2024-11-19 19:45:18 +00:00
6e1a3987b7
This commit adds a command called `self-update` which downloads the latest released version of restic from GitHub and replacing the current binary with it. It does not rely on any external program (so it'll work everywhere), but still verifies the GPG signature using the embedded GPG public key. By default, the `self-update` command is hidden behind the `selfupdate` built tag, which is only set when restic is built using `build.go`. The reason for this is that downstream distributions will then not include the command by default, so users are encouraged to use the platform-specific distribution mechanism.
732 lines
21 KiB
Go
732 lines
21 KiB
Go
// 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 packet
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"encoding/asn1"
|
|
"encoding/binary"
|
|
"hash"
|
|
"io"
|
|
"math/big"
|
|
"strconv"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/openpgp/errors"
|
|
"golang.org/x/crypto/openpgp/s2k"
|
|
)
|
|
|
|
const (
|
|
// See RFC 4880, section 5.2.3.21 for details.
|
|
KeyFlagCertify = 1 << iota
|
|
KeyFlagSign
|
|
KeyFlagEncryptCommunications
|
|
KeyFlagEncryptStorage
|
|
)
|
|
|
|
// Signature represents a signature. See RFC 4880, section 5.2.
|
|
type Signature struct {
|
|
SigType SignatureType
|
|
PubKeyAlgo PublicKeyAlgorithm
|
|
Hash crypto.Hash
|
|
|
|
// HashSuffix is extra data that is hashed in after the signed data.
|
|
HashSuffix []byte
|
|
// HashTag contains the first two bytes of the hash for fast rejection
|
|
// of bad signed data.
|
|
HashTag [2]byte
|
|
CreationTime time.Time
|
|
|
|
RSASignature parsedMPI
|
|
DSASigR, DSASigS parsedMPI
|
|
ECDSASigR, ECDSASigS parsedMPI
|
|
|
|
// rawSubpackets contains the unparsed subpackets, in order.
|
|
rawSubpackets []outputSubpacket
|
|
|
|
// The following are optional so are nil when not included in the
|
|
// signature.
|
|
|
|
SigLifetimeSecs, KeyLifetimeSecs *uint32
|
|
PreferredSymmetric, PreferredHash, PreferredCompression []uint8
|
|
IssuerKeyId *uint64
|
|
IsPrimaryId *bool
|
|
|
|
// FlagsValid is set if any flags were given. See RFC 4880, section
|
|
// 5.2.3.21 for details.
|
|
FlagsValid bool
|
|
FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool
|
|
|
|
// RevocationReason is set if this signature has been revoked.
|
|
// See RFC 4880, section 5.2.3.23 for details.
|
|
RevocationReason *uint8
|
|
RevocationReasonText string
|
|
|
|
// MDC is set if this signature has a feature packet that indicates
|
|
// support for MDC subpackets.
|
|
MDC bool
|
|
|
|
// EmbeddedSignature, if non-nil, is a signature of the parent key, by
|
|
// this key. This prevents an attacker from claiming another's signing
|
|
// subkey as their own.
|
|
EmbeddedSignature *Signature
|
|
|
|
outSubpackets []outputSubpacket
|
|
}
|
|
|
|
func (sig *Signature) parse(r io.Reader) (err error) {
|
|
// RFC 4880, section 5.2.3
|
|
var buf [5]byte
|
|
_, err = readFull(r, buf[:1])
|
|
if err != nil {
|
|
return
|
|
}
|
|
if buf[0] != 4 {
|
|
err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
|
|
return
|
|
}
|
|
|
|
_, err = readFull(r, buf[:5])
|
|
if err != nil {
|
|
return
|
|
}
|
|
sig.SigType = SignatureType(buf[0])
|
|
sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
|
|
switch sig.PubKeyAlgo {
|
|
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
|
|
default:
|
|
err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
|
return
|
|
}
|
|
|
|
var ok bool
|
|
sig.Hash, ok = s2k.HashIdToHash(buf[2])
|
|
if !ok {
|
|
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
|
|
}
|
|
|
|
hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
|
|
l := 6 + hashedSubpacketsLength
|
|
sig.HashSuffix = make([]byte, l+6)
|
|
sig.HashSuffix[0] = 4
|
|
copy(sig.HashSuffix[1:], buf[:5])
|
|
hashedSubpackets := sig.HashSuffix[6:l]
|
|
_, err = readFull(r, hashedSubpackets)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// See RFC 4880, section 5.2.4
|
|
trailer := sig.HashSuffix[l:]
|
|
trailer[0] = 4
|
|
trailer[1] = 0xff
|
|
trailer[2] = uint8(l >> 24)
|
|
trailer[3] = uint8(l >> 16)
|
|
trailer[4] = uint8(l >> 8)
|
|
trailer[5] = uint8(l)
|
|
|
|
err = parseSignatureSubpackets(sig, hashedSubpackets, true)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = readFull(r, buf[:2])
|
|
if err != nil {
|
|
return
|
|
}
|
|
unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1])
|
|
unhashedSubpackets := make([]byte, unhashedSubpacketsLength)
|
|
_, err = readFull(r, unhashedSubpackets)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = parseSignatureSubpackets(sig, unhashedSubpackets, false)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = readFull(r, sig.HashTag[:2])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch sig.PubKeyAlgo {
|
|
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
|
|
case PubKeyAlgoDSA:
|
|
sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r)
|
|
if err == nil {
|
|
sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
|
|
}
|
|
case PubKeyAlgoECDSA:
|
|
sig.ECDSASigR.bytes, sig.ECDSASigR.bitLength, err = readMPI(r)
|
|
if err == nil {
|
|
sig.ECDSASigS.bytes, sig.ECDSASigS.bitLength, err = readMPI(r)
|
|
}
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
return
|
|
}
|
|
|
|
// parseSignatureSubpackets parses subpackets of the main signature packet. See
|
|
// RFC 4880, section 5.2.3.1.
|
|
func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) {
|
|
for len(subpackets) > 0 {
|
|
subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if sig.CreationTime.IsZero() {
|
|
err = errors.StructuralError("no creation time in signature")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type signatureSubpacketType uint8
|
|
|
|
const (
|
|
creationTimeSubpacket signatureSubpacketType = 2
|
|
signatureExpirationSubpacket signatureSubpacketType = 3
|
|
keyExpirationSubpacket signatureSubpacketType = 9
|
|
prefSymmetricAlgosSubpacket signatureSubpacketType = 11
|
|
issuerSubpacket signatureSubpacketType = 16
|
|
prefHashAlgosSubpacket signatureSubpacketType = 21
|
|
prefCompressionSubpacket signatureSubpacketType = 22
|
|
primaryUserIdSubpacket signatureSubpacketType = 25
|
|
keyFlagsSubpacket signatureSubpacketType = 27
|
|
reasonForRevocationSubpacket signatureSubpacketType = 29
|
|
featuresSubpacket signatureSubpacketType = 30
|
|
embeddedSignatureSubpacket signatureSubpacketType = 32
|
|
)
|
|
|
|
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
|
|
func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) {
|
|
// RFC 4880, section 5.2.3.1
|
|
var (
|
|
length uint32
|
|
packetType signatureSubpacketType
|
|
isCritical bool
|
|
)
|
|
switch {
|
|
case subpacket[0] < 192:
|
|
length = uint32(subpacket[0])
|
|
subpacket = subpacket[1:]
|
|
case subpacket[0] < 255:
|
|
if len(subpacket) < 2 {
|
|
goto Truncated
|
|
}
|
|
length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192
|
|
subpacket = subpacket[2:]
|
|
default:
|
|
if len(subpacket) < 5 {
|
|
goto Truncated
|
|
}
|
|
length = uint32(subpacket[1])<<24 |
|
|
uint32(subpacket[2])<<16 |
|
|
uint32(subpacket[3])<<8 |
|
|
uint32(subpacket[4])
|
|
subpacket = subpacket[5:]
|
|
}
|
|
if length > uint32(len(subpacket)) {
|
|
goto Truncated
|
|
}
|
|
rest = subpacket[length:]
|
|
subpacket = subpacket[:length]
|
|
if len(subpacket) == 0 {
|
|
err = errors.StructuralError("zero length signature subpacket")
|
|
return
|
|
}
|
|
packetType = signatureSubpacketType(subpacket[0] & 0x7f)
|
|
isCritical = subpacket[0]&0x80 == 0x80
|
|
subpacket = subpacket[1:]
|
|
sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
|
|
switch packetType {
|
|
case creationTimeSubpacket:
|
|
if !isHashed {
|
|
err = errors.StructuralError("signature creation time in non-hashed area")
|
|
return
|
|
}
|
|
if len(subpacket) != 4 {
|
|
err = errors.StructuralError("signature creation time not four bytes")
|
|
return
|
|
}
|
|
t := binary.BigEndian.Uint32(subpacket)
|
|
sig.CreationTime = time.Unix(int64(t), 0)
|
|
case signatureExpirationSubpacket:
|
|
// Signature expiration time, section 5.2.3.10
|
|
if !isHashed {
|
|
return
|
|
}
|
|
if len(subpacket) != 4 {
|
|
err = errors.StructuralError("expiration subpacket with bad length")
|
|
return
|
|
}
|
|
sig.SigLifetimeSecs = new(uint32)
|
|
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
|
case keyExpirationSubpacket:
|
|
// Key expiration time, section 5.2.3.6
|
|
if !isHashed {
|
|
return
|
|
}
|
|
if len(subpacket) != 4 {
|
|
err = errors.StructuralError("key expiration subpacket with bad length")
|
|
return
|
|
}
|
|
sig.KeyLifetimeSecs = new(uint32)
|
|
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
|
case prefSymmetricAlgosSubpacket:
|
|
// Preferred symmetric algorithms, section 5.2.3.7
|
|
if !isHashed {
|
|
return
|
|
}
|
|
sig.PreferredSymmetric = make([]byte, len(subpacket))
|
|
copy(sig.PreferredSymmetric, subpacket)
|
|
case issuerSubpacket:
|
|
// Issuer, section 5.2.3.5
|
|
if len(subpacket) != 8 {
|
|
err = errors.StructuralError("issuer subpacket with bad length")
|
|
return
|
|
}
|
|
sig.IssuerKeyId = new(uint64)
|
|
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
|
|
case prefHashAlgosSubpacket:
|
|
// Preferred hash algorithms, section 5.2.3.8
|
|
if !isHashed {
|
|
return
|
|
}
|
|
sig.PreferredHash = make([]byte, len(subpacket))
|
|
copy(sig.PreferredHash, subpacket)
|
|
case prefCompressionSubpacket:
|
|
// Preferred compression algorithms, section 5.2.3.9
|
|
if !isHashed {
|
|
return
|
|
}
|
|
sig.PreferredCompression = make([]byte, len(subpacket))
|
|
copy(sig.PreferredCompression, subpacket)
|
|
case primaryUserIdSubpacket:
|
|
// Primary User ID, section 5.2.3.19
|
|
if !isHashed {
|
|
return
|
|
}
|
|
if len(subpacket) != 1 {
|
|
err = errors.StructuralError("primary user id subpacket with bad length")
|
|
return
|
|
}
|
|
sig.IsPrimaryId = new(bool)
|
|
if subpacket[0] > 0 {
|
|
*sig.IsPrimaryId = true
|
|
}
|
|
case keyFlagsSubpacket:
|
|
// Key flags, section 5.2.3.21
|
|
if !isHashed {
|
|
return
|
|
}
|
|
if len(subpacket) == 0 {
|
|
err = errors.StructuralError("empty key flags subpacket")
|
|
return
|
|
}
|
|
sig.FlagsValid = true
|
|
if subpacket[0]&KeyFlagCertify != 0 {
|
|
sig.FlagCertify = true
|
|
}
|
|
if subpacket[0]&KeyFlagSign != 0 {
|
|
sig.FlagSign = true
|
|
}
|
|
if subpacket[0]&KeyFlagEncryptCommunications != 0 {
|
|
sig.FlagEncryptCommunications = true
|
|
}
|
|
if subpacket[0]&KeyFlagEncryptStorage != 0 {
|
|
sig.FlagEncryptStorage = true
|
|
}
|
|
case reasonForRevocationSubpacket:
|
|
// Reason For Revocation, section 5.2.3.23
|
|
if !isHashed {
|
|
return
|
|
}
|
|
if len(subpacket) == 0 {
|
|
err = errors.StructuralError("empty revocation reason subpacket")
|
|
return
|
|
}
|
|
sig.RevocationReason = new(uint8)
|
|
*sig.RevocationReason = subpacket[0]
|
|
sig.RevocationReasonText = string(subpacket[1:])
|
|
case featuresSubpacket:
|
|
// Features subpacket, section 5.2.3.24 specifies a very general
|
|
// mechanism for OpenPGP implementations to signal support for new
|
|
// features. In practice, the subpacket is used exclusively to
|
|
// indicate support for MDC-protected encryption.
|
|
sig.MDC = len(subpacket) >= 1 && subpacket[0]&1 == 1
|
|
case embeddedSignatureSubpacket:
|
|
// Only usage is in signatures that cross-certify
|
|
// signing subkeys. section 5.2.3.26 describes the
|
|
// format, with its usage described in section 11.1
|
|
if sig.EmbeddedSignature != nil {
|
|
err = errors.StructuralError("Cannot have multiple embedded signatures")
|
|
return
|
|
}
|
|
sig.EmbeddedSignature = new(Signature)
|
|
// Embedded signatures are required to be v4 signatures see
|
|
// section 12.1. However, we only parse v4 signatures in this
|
|
// file anyway.
|
|
if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil {
|
|
return nil, err
|
|
}
|
|
if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding {
|
|
return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType)))
|
|
}
|
|
default:
|
|
if isCritical {
|
|
err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
|
|
return
|
|
}
|
|
}
|
|
return
|
|
|
|
Truncated:
|
|
err = errors.StructuralError("signature subpacket truncated")
|
|
return
|
|
}
|
|
|
|
// subpacketLengthLength returns the length, in bytes, of an encoded length value.
|
|
func subpacketLengthLength(length int) int {
|
|
if length < 192 {
|
|
return 1
|
|
}
|
|
if length < 16320 {
|
|
return 2
|
|
}
|
|
return 5
|
|
}
|
|
|
|
// serializeSubpacketLength marshals the given length into to.
|
|
func serializeSubpacketLength(to []byte, length int) int {
|
|
// RFC 4880, Section 4.2.2.
|
|
if length < 192 {
|
|
to[0] = byte(length)
|
|
return 1
|
|
}
|
|
if length < 16320 {
|
|
length -= 192
|
|
to[0] = byte((length >> 8) + 192)
|
|
to[1] = byte(length)
|
|
return 2
|
|
}
|
|
to[0] = 255
|
|
to[1] = byte(length >> 24)
|
|
to[2] = byte(length >> 16)
|
|
to[3] = byte(length >> 8)
|
|
to[4] = byte(length)
|
|
return 5
|
|
}
|
|
|
|
// subpacketsLength returns the serialized length, in bytes, of the given
|
|
// subpackets.
|
|
func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) {
|
|
for _, subpacket := range subpackets {
|
|
if subpacket.hashed == hashed {
|
|
length += subpacketLengthLength(len(subpacket.contents) + 1)
|
|
length += 1 // type byte
|
|
length += len(subpacket.contents)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// serializeSubpackets marshals the given subpackets into to.
|
|
func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) {
|
|
for _, subpacket := range subpackets {
|
|
if subpacket.hashed == hashed {
|
|
n := serializeSubpacketLength(to, len(subpacket.contents)+1)
|
|
to[n] = byte(subpacket.subpacketType)
|
|
to = to[1+n:]
|
|
n = copy(to, subpacket.contents)
|
|
to = to[n:]
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// KeyExpired returns whether sig is a self-signature of a key that has
|
|
// expired.
|
|
func (sig *Signature) KeyExpired(currentTime time.Time) bool {
|
|
if sig.KeyLifetimeSecs == nil {
|
|
return false
|
|
}
|
|
expiry := sig.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second)
|
|
return currentTime.After(expiry)
|
|
}
|
|
|
|
// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
|
|
func (sig *Signature) buildHashSuffix() (err error) {
|
|
hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true)
|
|
|
|
var ok bool
|
|
l := 6 + hashedSubpacketsLen
|
|
sig.HashSuffix = make([]byte, l+6)
|
|
sig.HashSuffix[0] = 4
|
|
sig.HashSuffix[1] = uint8(sig.SigType)
|
|
sig.HashSuffix[2] = uint8(sig.PubKeyAlgo)
|
|
sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
|
|
if !ok {
|
|
sig.HashSuffix = nil
|
|
return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
|
|
}
|
|
sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
|
|
sig.HashSuffix[5] = byte(hashedSubpacketsLen)
|
|
serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true)
|
|
trailer := sig.HashSuffix[l:]
|
|
trailer[0] = 4
|
|
trailer[1] = 0xff
|
|
trailer[2] = byte(l >> 24)
|
|
trailer[3] = byte(l >> 16)
|
|
trailer[4] = byte(l >> 8)
|
|
trailer[5] = byte(l)
|
|
return
|
|
}
|
|
|
|
func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
|
|
err = sig.buildHashSuffix()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
h.Write(sig.HashSuffix)
|
|
digest = h.Sum(nil)
|
|
copy(sig.HashTag[:], digest)
|
|
return
|
|
}
|
|
|
|
// Sign signs a message with a private key. The hash, h, must contain
|
|
// the hash of the message to be signed and will be mutated by this function.
|
|
// On 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) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err error) {
|
|
sig.outSubpackets = sig.buildSubpackets()
|
|
digest, err := sig.signPrepareHash(h)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch priv.PubKeyAlgo {
|
|
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
// supports both *rsa.PrivateKey and crypto.Signer
|
|
sig.RSASignature.bytes, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
|
|
sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
|
|
case PubKeyAlgoDSA:
|
|
dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
|
|
|
|
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
|
subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
|
|
if len(digest) > subgroupSize {
|
|
digest = digest[:subgroupSize]
|
|
}
|
|
r, s, err := dsa.Sign(config.Random(), dsaPriv, digest)
|
|
if err == nil {
|
|
sig.DSASigR.bytes = r.Bytes()
|
|
sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
|
|
sig.DSASigS.bytes = s.Bytes()
|
|
sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
|
|
}
|
|
case PubKeyAlgoECDSA:
|
|
var r, s *big.Int
|
|
if pk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok {
|
|
// direct support, avoid asn1 wrapping/unwrapping
|
|
r, s, err = ecdsa.Sign(config.Random(), pk, digest)
|
|
} else {
|
|
var b []byte
|
|
b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, nil)
|
|
if err == nil {
|
|
r, s, err = unwrapECDSASig(b)
|
|
}
|
|
}
|
|
if err == nil {
|
|
sig.ECDSASigR = fromBig(r)
|
|
sig.ECDSASigS = fromBig(s)
|
|
}
|
|
default:
|
|
err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA
|
|
// signature.
|
|
func unwrapECDSASig(b []byte) (r, s *big.Int, err error) {
|
|
var ecsdaSig struct {
|
|
R, S *big.Int
|
|
}
|
|
_, err = asn1.Unmarshal(b, &ecsdaSig)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return ecsdaSig.R, ecsdaSig.S, nil
|
|
}
|
|
|
|
// SignUserId computes a signature from priv, asserting that pub is a valid
|
|
// key for the identity id. On 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) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error {
|
|
h, err := userIdSignatureHash(id, pub, sig.Hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sig.Sign(h, priv, config)
|
|
}
|
|
|
|
// SignKey computes a signature from priv, asserting that pub is a subkey. On
|
|
// 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.Hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sig.Sign(h, priv, config)
|
|
}
|
|
|
|
// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
|
|
// called first.
|
|
func (sig *Signature) Serialize(w io.Writer) (err error) {
|
|
if len(sig.outSubpackets) == 0 {
|
|
sig.outSubpackets = sig.rawSubpackets
|
|
}
|
|
if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil && sig.ECDSASigR.bytes == nil {
|
|
return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
|
|
}
|
|
|
|
sigLength := 0
|
|
switch sig.PubKeyAlgo {
|
|
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
sigLength = 2 + len(sig.RSASignature.bytes)
|
|
case PubKeyAlgoDSA:
|
|
sigLength = 2 + len(sig.DSASigR.bytes)
|
|
sigLength += 2 + len(sig.DSASigS.bytes)
|
|
case PubKeyAlgoECDSA:
|
|
sigLength = 2 + len(sig.ECDSASigR.bytes)
|
|
sigLength += 2 + len(sig.ECDSASigS.bytes)
|
|
default:
|
|
panic("impossible")
|
|
}
|
|
|
|
unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false)
|
|
length := len(sig.HashSuffix) - 6 /* trailer not included */ +
|
|
2 /* length of unhashed subpackets */ + unhashedSubpacketsLen +
|
|
2 /* hash tag */ + sigLength
|
|
err = serializeHeader(w, packetTypeSignature, length)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen)
|
|
unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8)
|
|
unhashedSubpackets[1] = byte(unhashedSubpacketsLen)
|
|
serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false)
|
|
|
|
_, err = w.Write(unhashedSubpackets)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = w.Write(sig.HashTag[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch sig.PubKeyAlgo {
|
|
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
err = writeMPIs(w, sig.RSASignature)
|
|
case PubKeyAlgoDSA:
|
|
err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
|
|
case PubKeyAlgoECDSA:
|
|
err = writeMPIs(w, sig.ECDSASigR, sig.ECDSASigS)
|
|
default:
|
|
panic("impossible")
|
|
}
|
|
return
|
|
}
|
|
|
|
// outputSubpacket represents a subpacket to be marshaled.
|
|
type outputSubpacket struct {
|
|
hashed bool // true if this subpacket is in the hashed area.
|
|
subpacketType signatureSubpacketType
|
|
isCritical bool
|
|
contents []byte
|
|
}
|
|
|
|
func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
|
|
creationTime := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
|
|
subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime})
|
|
|
|
if sig.IssuerKeyId != nil {
|
|
keyId := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
|
|
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
|
|
}
|
|
|
|
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
|
|
sigLifetime := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
|
|
subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
|
|
}
|
|
|
|
// Key flags may only appear in self-signatures or certification signatures.
|
|
|
|
if sig.FlagsValid {
|
|
var flags byte
|
|
if sig.FlagCertify {
|
|
flags |= KeyFlagCertify
|
|
}
|
|
if sig.FlagSign {
|
|
flags |= KeyFlagSign
|
|
}
|
|
if sig.FlagEncryptCommunications {
|
|
flags |= KeyFlagEncryptCommunications
|
|
}
|
|
if sig.FlagEncryptStorage {
|
|
flags |= KeyFlagEncryptStorage
|
|
}
|
|
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
|
|
}
|
|
|
|
// The following subpackets may only appear in self-signatures
|
|
|
|
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
|
|
keyLifetime := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
|
|
subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
|
|
}
|
|
|
|
if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
|
|
subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
|
|
}
|
|
|
|
if len(sig.PreferredSymmetric) > 0 {
|
|
subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
|
|
}
|
|
|
|
if len(sig.PreferredHash) > 0 {
|
|
subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
|
|
}
|
|
|
|
if len(sig.PreferredCompression) > 0 {
|
|
subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
|
|
}
|
|
|
|
return
|
|
}
|