mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-14 08:52:44 +00:00
Knut Ahlers
a1df72edc5
commitf0db1ff1f8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:19:56 2017 +0100 Mark option as deprecated Signed-off-by: Knut Ahlers <knut@ahlers.me> commit9891df2a16
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:11:56 2017 +0100 Fix: Typo Signed-off-by: Knut Ahlers <knut@ahlers.me> commit836006de64
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:04:20 2017 +0100 Add new dependencies Signed-off-by: Knut Ahlers <knut@ahlers.me> commitd64fee60c8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 11:55:52 2017 +0100 Replace insecure password hashing Prior this commit passwords were hashed with a static salt and using the SHA1 hashing function. This could lead to passwords being attackable in case someone gets access to the raw data stored inside the database. This commit introduces password hashing using bcrypt hashing function which addresses this issue. Old passwords are not automatically re-hashed as they are unknown. Replacing the old password scheme is not that easy and needs #10 to be solved. Therefore the old hashing scheme is kept for compatibility reason. Signed-off-by: Knut Ahlers <knut@ahlers.me> Signed-off-by: Knut Ahlers <knut@ahlers.me> closes #14 closes #15
442 lines
13 KiB
Go
442 lines
13 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 openpgp implements high level operations on OpenPGP messages.
|
|
package openpgp // import "golang.org/x/crypto/openpgp"
|
|
|
|
import (
|
|
"crypto"
|
|
_ "crypto/sha256"
|
|
"hash"
|
|
"io"
|
|
"strconv"
|
|
|
|
"golang.org/x/crypto/openpgp/armor"
|
|
"golang.org/x/crypto/openpgp/errors"
|
|
"golang.org/x/crypto/openpgp/packet"
|
|
)
|
|
|
|
// 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 error) {
|
|
block, err := armor.Decode(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if block.Type != expectedType {
|
|
return nil, errors.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 available.
|
|
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 be 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 error // nil if the signature is good.
|
|
Signature *packet.Signature // the signature packet itself, if v4 (default)
|
|
SignatureV3 *packet.SignatureV3 // the signature packet if it is a v2 or v3 signature
|
|
|
|
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, 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.
|
|
// If config is nil, sensible defaults will be used.
|
|
func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err 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)
|
|
switch p.Algo {
|
|
case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal:
|
|
break
|
|
default:
|
|
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, errors.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 available
|
|
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.Decrypt(pk.key.PrivateKey, config)
|
|
}
|
|
if len(pk.encryptedKey.Key) == 0 {
|
|
continue
|
|
}
|
|
decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
|
|
if err != nil && err != errors.ErrKeyIncorrect {
|
|
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, errors.ErrKeyIncorrect
|
|
}
|
|
|
|
if prompt == nil {
|
|
return nil, errors.ErrKeyIncorrect
|
|
}
|
|
|
|
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 {
|
|
key, cipherFunc, err := s.Decrypt(passphrase)
|
|
if err == nil {
|
|
decrypted, err = se.Decrypt(cipherFunc, key)
|
|
if err != nil && err != errors.ErrKeyIncorrect {
|
|
return nil, err
|
|
}
|
|
if decrypted != nil {
|
|
break FindKey
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
md.decrypted = decrypted
|
|
if err := packets.Push(decrypted); err != nil {
|
|
return nil, err
|
|
}
|
|
return readSignedMessage(packets, md, keyring)
|
|
}
|
|
|
|
// readSignedMessage reads a possibly 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 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:
|
|
if err := packets.Push(p.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
case *packet.OnePassSignature:
|
|
if !p.IsLast {
|
|
return nil, errors.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.KeysByIdUsage(p.KeyId, packet.KeyFlagSign)
|
|
if len(keys) > 0 {
|
|
md.SignedBy = &keys[0]
|
|
}
|
|
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 normalize 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, error) {
|
|
if !hashId.Available() {
|
|
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
|
|
}
|
|
h := hashId.New()
|
|
|
|
switch sigType {
|
|
case packet.SigTypeBinary:
|
|
return h, h, nil
|
|
case packet.SigTypeText:
|
|
return h, NewCanonicalTextHash(h), nil
|
|
}
|
|
|
|
return nil, nil, errors.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 error) {
|
|
n, err = cr.md.LiteralData.Body.Read(buf)
|
|
if err == io.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 error) {
|
|
n, err = scr.md.LiteralData.Body.Read(buf)
|
|
scr.wrappedHash.Write(buf[:n])
|
|
if err == io.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 = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature)
|
|
} else if scr.md.SignatureV3, ok = p.(*packet.SignatureV3); ok {
|
|
scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignatureV3(scr.h, scr.md.SignatureV3)
|
|
} else {
|
|
scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature")
|
|
return
|
|
}
|
|
|
|
// 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 known,
|
|
// ErrUnknownIssuer is returned.
|
|
func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
|
|
var issuerKeyId uint64
|
|
var hashFunc crypto.Hash
|
|
var sigType packet.SignatureType
|
|
var keys []Key
|
|
var p packet.Packet
|
|
|
|
packets := packet.NewReader(signature)
|
|
for {
|
|
p, err = packets.Next()
|
|
if err == io.EOF {
|
|
return nil, errors.ErrUnknownIssuer
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch sig := p.(type) {
|
|
case *packet.Signature:
|
|
if sig.IssuerKeyId == nil {
|
|
return nil, errors.StructuralError("signature doesn't have an issuer")
|
|
}
|
|
issuerKeyId = *sig.IssuerKeyId
|
|
hashFunc = sig.Hash
|
|
sigType = sig.SigType
|
|
case *packet.SignatureV3:
|
|
issuerKeyId = sig.IssuerKeyId
|
|
hashFunc = sig.Hash
|
|
sigType = sig.SigType
|
|
default:
|
|
return nil, errors.StructuralError("non signature packet found")
|
|
}
|
|
|
|
keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign)
|
|
if len(keys) > 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
panic("unreachable")
|
|
}
|
|
|
|
h, wrappedHash, err := hashForSignature(hashFunc, sigType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF {
|
|
return nil, err
|
|
}
|
|
|
|
for _, key := range keys {
|
|
switch sig := p.(type) {
|
|
case *packet.Signature:
|
|
err = key.PublicKey.VerifySignature(h, sig)
|
|
case *packet.SignatureV3:
|
|
err = key.PublicKey.VerifySignatureV3(h, sig)
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
|
|
if err == nil {
|
|
return key.Entity, nil
|
|
}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
// 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 error) {
|
|
body, err := readArmored(signature, SignatureType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return CheckDetachedSignature(keyring, signed, body)
|
|
}
|