openpgp: support creating signatures compatible with 'gpg --sign'.
This is neither a '--clearsign' nor a '--detach-sign' which are already supported. Verification of these signatures is already supported by ReadMessage. The code shares a lot with standard encrypt/sign, so mostly a refactoring of 'Encrypt' to allow use of the code path without actually doing a signing. Change-Id: I5bb7487134ffcf1189ed74e28dbbbe1c01b356d1 GitHub-Last-Rev: 01162222600a4a2a230a444387b33883faf596ec GitHub-Pull-Request: golang/crypto#50 Reviewed-on: https://go-review.googlesource.com/116017 Reviewed-by: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
committed by
Filippo Valsorda
parent
a49355c7e3
commit
a214413485
+106
-68
@@ -164,12 +164,12 @@ func hashToHashId(h crypto.Hash) uint8 {
|
||||
return v
|
||||
}
|
||||
|
||||
// Encrypt encrypts a message to a number of recipients and, optionally, signs
|
||||
// it. hints contains optional information, that is also encrypted, that aids
|
||||
// the recipients in processing the message. The resulting WriteCloser must
|
||||
// be closed after the contents of the file have been written.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
||||
// writeAndSign writes the data as a payload package and, optionally, signs
|
||||
// it. hints contains optional information, that is also encrypted,
|
||||
// that aids the recipients in processing the message. The resulting
|
||||
// WriteCloser must be closed after the contents of the file have been
|
||||
// written. If config is nil, sensible defaults will be used.
|
||||
func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
||||
var signer *packet.PrivateKey
|
||||
if signed != nil {
|
||||
signKey, ok := signed.signingKey(config.Now())
|
||||
@@ -185,6 +185,83 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
||||
}
|
||||
}
|
||||
|
||||
var hash crypto.Hash
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
|
||||
hash = h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the hash specified by config is a candidate, we'll use that.
|
||||
if configuredHash := config.Hash(); configuredHash.Available() {
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
|
||||
hash = h
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hash == 0 {
|
||||
hashId := candidateHashes[0]
|
||||
name, ok := s2k.HashIdToString(hashId)
|
||||
if !ok {
|
||||
name = "#" + strconv.Itoa(int(hashId))
|
||||
}
|
||||
return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
ops := &packet.OnePassSignature{
|
||||
SigType: packet.SigTypeBinary,
|
||||
Hash: hash,
|
||||
PubKeyAlgo: signer.PubKeyAlgo,
|
||||
KeyId: signer.KeyId,
|
||||
IsLast: true,
|
||||
}
|
||||
if err := ops.Serialize(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if hints == nil {
|
||||
hints = &FileHints{}
|
||||
}
|
||||
|
||||
w := payload
|
||||
if signer != nil {
|
||||
// If we need to write a signature packet after the literal
|
||||
// data then we need to stop literalData from closing
|
||||
// encryptedData.
|
||||
w = noOpCloser{w}
|
||||
|
||||
}
|
||||
var epochSeconds uint32
|
||||
if !hints.ModTime.IsZero() {
|
||||
epochSeconds = uint32(hints.ModTime.Unix())
|
||||
}
|
||||
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
return signatureWriter{payload, literalData, hash, hash.New(), signer, config}, nil
|
||||
}
|
||||
return literalData, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts a message to a number of recipients and, optionally, signs
|
||||
// it. hints contains optional information, that is also encrypted, that aids
|
||||
// the recipients in processing the message. The resulting WriteCloser must
|
||||
// be closed after the contents of the file have been written.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
||||
if len(to) == 0 {
|
||||
return nil, errors.InvalidArgumentError("no encryption recipient provided")
|
||||
}
|
||||
|
||||
// These are the possible ciphers that we'll use for the message.
|
||||
candidateCiphers := []uint8{
|
||||
uint8(packet.CipherAES128),
|
||||
@@ -241,33 +318,6 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
||||
}
|
||||
}
|
||||
|
||||
var hash crypto.Hash
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
|
||||
hash = h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the hash specified by config is a candidate, we'll use that.
|
||||
if configuredHash := config.Hash(); configuredHash.Available() {
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
|
||||
hash = h
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hash == 0 {
|
||||
hashId := candidateHashes[0]
|
||||
name, ok := s2k.HashIdToString(hashId)
|
||||
if !ok {
|
||||
name = "#" + strconv.Itoa(int(hashId))
|
||||
}
|
||||
return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
|
||||
}
|
||||
|
||||
symKey := make([]byte, cipher.KeySize())
|
||||
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
|
||||
return nil, err
|
||||
@@ -279,49 +329,37 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
||||
}
|
||||
}
|
||||
|
||||
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config)
|
||||
payload, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
ops := &packet.OnePassSignature{
|
||||
SigType: packet.SigTypeBinary,
|
||||
Hash: hash,
|
||||
PubKeyAlgo: signer.PubKeyAlgo,
|
||||
KeyId: signer.KeyId,
|
||||
IsLast: true,
|
||||
}
|
||||
if err := ops.Serialize(encryptedData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeAndSign(payload, candidateHashes, signed, hints, config)
|
||||
}
|
||||
|
||||
// Sign signs a message. The resulting WriteCloser must be closed after the
|
||||
// contents of the file have been written. hints contains optional information
|
||||
// that aids the recipients in processing the message.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
|
||||
if signed == nil {
|
||||
return nil, errors.InvalidArgumentError("no signer provided")
|
||||
}
|
||||
|
||||
if hints == nil {
|
||||
hints = &FileHints{}
|
||||
// These are the possible hash functions that we'll use for the signature.
|
||||
candidateHashes := []uint8{
|
||||
hashToHashId(crypto.SHA256),
|
||||
hashToHashId(crypto.SHA512),
|
||||
hashToHashId(crypto.SHA1),
|
||||
hashToHashId(crypto.RIPEMD160),
|
||||
}
|
||||
|
||||
w := encryptedData
|
||||
if signer != nil {
|
||||
// If we need to write a signature packet after the literal
|
||||
// data then we need to stop literalData from closing
|
||||
// encryptedData.
|
||||
w = noOpCloser{encryptedData}
|
||||
|
||||
defaultHashes := candidateHashes[len(candidateHashes)-1:]
|
||||
preferredHashes := signed.primaryIdentity().SelfSignature.PreferredHash
|
||||
if len(preferredHashes) == 0 {
|
||||
preferredHashes = defaultHashes
|
||||
}
|
||||
var epochSeconds uint32
|
||||
if !hints.ModTime.IsZero() {
|
||||
epochSeconds = uint32(hints.ModTime.Unix())
|
||||
}
|
||||
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil
|
||||
}
|
||||
return literalData, nil
|
||||
candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
|
||||
return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, config)
|
||||
}
|
||||
|
||||
// signatureWriter hashes the contents of a message while passing it along to
|
||||
|
||||
@@ -271,3 +271,92 @@ func TestEncryption(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testSigningTests = []struct {
|
||||
keyRingHex string
|
||||
}{
|
||||
{
|
||||
testKeys1And2PrivateHex,
|
||||
},
|
||||
{
|
||||
dsaElGamalTestKeysHex,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSigning(t *testing.T) {
|
||||
for i, test := range testSigningTests {
|
||||
kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
|
||||
|
||||
passphrase := []byte("passphrase")
|
||||
for _, entity := range kring {
|
||||
if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
|
||||
err := entity.PrivateKey.Decrypt(passphrase)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: failed to decrypt key", i)
|
||||
}
|
||||
}
|
||||
for _, subkey := range entity.Subkeys {
|
||||
if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
|
||||
err := subkey.PrivateKey.Decrypt(passphrase)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: failed to decrypt subkey", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signed := kring[0]
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
w, err := Sign(buf, signed, nil /* no hints */, nil)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: error in Sign: %s", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
const message = "testing"
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
t.Errorf("#%d: error writing plaintext: %s", i, err)
|
||||
continue
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
t.Errorf("#%d: error closing WriteCloser: %s", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
md, err := ReadMessage(buf, kring, nil /* no prompt */, nil)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: error reading message: %s", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
testTime, _ := time.Parse("2006-01-02", "2013-07-01")
|
||||
signKey, _ := kring[0].signingKey(testTime)
|
||||
expectedKeyId := signKey.PublicKey.KeyId
|
||||
if md.SignedByKeyId != expectedKeyId {
|
||||
t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignedBy, expectedKeyId)
|
||||
}
|
||||
if md.SignedBy == nil {
|
||||
t.Errorf("#%d: failed to find the signing Entity", i)
|
||||
}
|
||||
|
||||
plaintext, err := ioutil.ReadAll(md.UnverifiedBody)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: error reading contents: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(plaintext) != message {
|
||||
t.Errorf("#%d: got: %q, want: %q", i, plaintext, message)
|
||||
}
|
||||
|
||||
if md.SignatureError != nil {
|
||||
t.Errorf("#%d: signature error: %q", i, md.SignatureError)
|
||||
}
|
||||
if md.Signature == nil {
|
||||
t.Error("signature missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user