Refactor api (#6)
* Refactor library, remove duplicates * Rebuild structure to use Messages and Signature models * Use PGPSplitMessage * Remove signature model * Various fixes * Add helpers with tests * Fixes, add some docs, add tests * Add attachment helpers * Add helpers Symmetric encryption * Edit docs + examples * Rename kr to keyRing * Various fixes for documentation * Edit JSON handling functions, add decrypt keyring via token * Add proposal changes doc * Fix CI * Drop *Message functions, join CleartextMessage and BinaryMessage * Change canonicalization and trimming only to text signatures * Add cleartextsignature, detach signature from message model, move helpers * Documentation, remove optional parameters * Move verification to separate model * Don't return message in VerifyDetached * Update table of contents in readme * Appease golint * Run go fmt * Rename Encrypt/DecryptMessageWithPassword to ..WithToken These functions shouldn't be used with user-provided passwords, as they don't do any key-stretching. * Change key generation usernames
This commit is contained in:
parent
82d49bf235
commit
e65ed17b41
34 changed files with 2573 additions and 1478 deletions
255
crypto/keyring_message.go
Normal file
255
crypto/keyring_message.go
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
pgpErrors "golang.org/x/crypto/openpgp/errors"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/constants"
|
||||
"github.com/ProtonMail/gopenpgp/internal"
|
||||
)
|
||||
|
||||
// Encrypt encrypts a PlainMessage, outputs a PGPMessage.
|
||||
// If an unlocked private key is also provided it will also sign the message.
|
||||
// message : The plaintext input as a PlainMessage
|
||||
// privateKey : (optional) an unlocked private keyring to include signature in the message
|
||||
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
||||
encrypted, err := asymmetricEncrypt(message.GetBinary(), keyRing, privateKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPGPMessage(encrypted), nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage
|
||||
// message : The encrypted input as a PGPMessage
|
||||
// verifyKey : Public key for signature verification (optional)
|
||||
// verifyTime : Time at verification (necessary only if verifyKey is not nil)
|
||||
func (keyRing *KeyRing) Decrypt(
|
||||
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
|
||||
) (*PlainMessage, *Verification, error) {
|
||||
decrypted, verifyStatus, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return NewPlainMessage(decrypted), newVerification(verifyStatus), nil
|
||||
}
|
||||
|
||||
// SignDetached generates and returns a PGPSignature for a given PlainMessage
|
||||
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
|
||||
signEntity, err := keyRing.GetSigningEntity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &packet.Config{DefaultHash: crypto.SHA512, Time: pgp.getTimeGenerator()}
|
||||
var outBuf bytes.Buffer
|
||||
//sign bin
|
||||
if err := openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPGPSignature(outBuf.Bytes()), nil
|
||||
}
|
||||
|
||||
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
|
||||
// and returns a Verification with the filled Verified field.
|
||||
func (keyRing *KeyRing) VerifyDetached(
|
||||
message *PlainMessage, signature *PGPSignature, verifyTime int64,
|
||||
) (*Verification, error) {
|
||||
var err error
|
||||
verifyVal, err := verifySignature(
|
||||
keyRing.GetEntities(),
|
||||
message.NewReader(),
|
||||
signature.GetBinary(),
|
||||
verifyTime,
|
||||
)
|
||||
return newVerification(verifyVal), err
|
||||
}
|
||||
|
||||
// ------ INTERNAL FUNCTIONS -------
|
||||
|
||||
// Core for encryption+signature functions
|
||||
func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isBinary bool) ([]byte, error) {
|
||||
var outBuf bytes.Buffer
|
||||
var encryptWriter io.WriteCloser
|
||||
var signEntity *openpgp.Entity
|
||||
var err error
|
||||
|
||||
if privateKey != nil && len(privateKey.entities) > 0 {
|
||||
var err error
|
||||
signEntity, err = privateKey.GetSigningEntity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
|
||||
|
||||
hints := &openpgp.FileHints{
|
||||
IsBinary: isBinary,
|
||||
FileName: "",
|
||||
}
|
||||
|
||||
if isBinary {
|
||||
encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
|
||||
} else {
|
||||
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = encryptWriter.Write(data)
|
||||
encryptWriter.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return outBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Core for decryption+verification functions
|
||||
func asymmetricDecrypt(
|
||||
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
|
||||
) (plaintext []byte, verified int, err error) {
|
||||
privKeyEntries := privateKey.GetEntities()
|
||||
var additionalEntries openpgp.EntityList
|
||||
|
||||
if verifyKey != nil {
|
||||
additionalEntries = verifyKey.GetEntities()
|
||||
}
|
||||
|
||||
if additionalEntries != nil {
|
||||
privKeyEntries = append(privKeyEntries, additionalEntries...)
|
||||
}
|
||||
|
||||
config := &packet.Config{Time: pgp.getTimeGenerator()}
|
||||
|
||||
messageDetails, err := openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
|
||||
if err != nil {
|
||||
return nil, constants.SIGNATURE_NOT_SIGNED, err
|
||||
}
|
||||
|
||||
if verifyKey != nil {
|
||||
processSignatureExpiration(messageDetails, verifyTime)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
|
||||
if err != nil {
|
||||
return nil, constants.SIGNATURE_NOT_SIGNED, err
|
||||
}
|
||||
|
||||
if verifyKey != nil {
|
||||
verifyStatus, verifyError := verifyDetailsSignature(messageDetails, verifyKey)
|
||||
|
||||
if verifyStatus == constants.SIGNATURE_FAILED {
|
||||
return nil, verifyStatus, errors.New(verifyError)
|
||||
}
|
||||
|
||||
return body, verifyStatus, nil
|
||||
}
|
||||
|
||||
return body, constants.SIGNATURE_NOT_SIGNED, nil
|
||||
}
|
||||
|
||||
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
|
||||
// creationTime check.
|
||||
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
|
||||
if md.SignatureError == pgpErrors.ErrSignatureExpired {
|
||||
if verifyTime > 0 {
|
||||
created := md.Signature.CreationTime.Unix()
|
||||
expires := int64(math.MaxInt64)
|
||||
if md.Signature.SigLifetimeSecs != nil {
|
||||
expires = int64(*md.Signature.SigLifetimeSecs) + created
|
||||
}
|
||||
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
|
||||
md.SignatureError = nil
|
||||
}
|
||||
} else {
|
||||
// verifyTime = 0: time check disabled, everything is okay
|
||||
md.SignatureError = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify signature from message details
|
||||
func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) (int, string) {
|
||||
if md.IsSigned {
|
||||
if md.SignedBy != nil {
|
||||
if len(verifierKey.entities) > 0 {
|
||||
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
|
||||
if len(matches) > 0 {
|
||||
if md.SignatureError == nil {
|
||||
return constants.SIGNATURE_OK, ""
|
||||
}
|
||||
return constants.SIGNATURE_FAILED, md.SignatureError.Error()
|
||||
}
|
||||
} else {
|
||||
return constants.SIGNATURE_NO_VERIFIER, ""
|
||||
}
|
||||
} else {
|
||||
return constants.SIGNATURE_NO_VERIFIER, ""
|
||||
}
|
||||
}
|
||||
|
||||
return constants.SIGNATURE_NOT_SIGNED, ""
|
||||
}
|
||||
|
||||
// verifySignature verifies if a signature is valid with the entity list
|
||||
func verifySignature(
|
||||
pubKeyEntries openpgp.EntityList, origText io.Reader, signature []byte, verifyTime int64) (int, error) {
|
||||
config := &packet.Config{}
|
||||
if verifyTime == 0 {
|
||||
config.Time = func() time.Time {
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
} else {
|
||||
config.Time = func() time.Time {
|
||||
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
|
||||
}
|
||||
}
|
||||
signatureReader := bytes.NewReader(signature)
|
||||
|
||||
signer, err := openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
|
||||
|
||||
if err == pgpErrors.ErrSignatureExpired && signer != nil {
|
||||
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
|
||||
// Maybe the creation time offset pushed it over the edge
|
||||
// Retry with the actual verification time
|
||||
config.Time = func() time.Time {
|
||||
return time.Unix(verifyTime, 0)
|
||||
}
|
||||
|
||||
_, err = signatureReader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return constants.SIGNATURE_FAILED, err
|
||||
}
|
||||
|
||||
signer, err = openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
|
||||
if err != nil {
|
||||
return constants.SIGNATURE_FAILED, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if signer == nil {
|
||||
return constants.SIGNATURE_FAILED, errors.New("gopenpgp: signer is empty")
|
||||
}
|
||||
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
|
||||
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
|
||||
// return false, errors.New("signer is nil")
|
||||
// }
|
||||
return constants.SIGNATURE_OK, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue