mirror of
https://github.com/containers/skopeo.git
synced 2025-04-28 03:10:18 +00:00
This fixes CVE-2020-8945 by incorporating proglottis/gpgme#23 . Other changes included by the rebase: - Support for gpgme_off_t (~no-op on Linux) - Wrapping a few more GPGME functions (irrelevant if we don't call them) Given how invasive the CVE fix is (affecting basically all binding code), it seems safer to just update the package (and be verifiably equivalent with upstream) than to backport and try to back out the few other changes. Performed by updating vendor conf and $ vndr github.com/mtrmac/gpgme Signed-off-by: Miloslav Trmač <mitr@redhat.com>
953 lines
23 KiB
Go
953 lines
23 KiB
Go
// Package gpgme provides a Go wrapper for the GPGME library
|
|
package gpgme
|
|
|
|
// #cgo LDFLAGS: -lgpgme -lassuan -lgpg-error
|
|
// #cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
|
// #include <stdlib.h>
|
|
// #include <gpgme.h>
|
|
// #include "go_gpgme.h"
|
|
import "C"
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
var Version string
|
|
|
|
func init() {
|
|
Version = C.GoString(C.gpgme_check_version(nil))
|
|
}
|
|
|
|
// Callback is the function that is called when a passphrase is required
|
|
type Callback func(uidHint string, prevWasBad bool, f *os.File) error
|
|
|
|
//export gogpgme_passfunc
|
|
func gogpgme_passfunc(hook unsafe.Pointer, uid_hint, passphrase_info *C.char, prev_was_bad, fd C.int) C.gpgme_error_t {
|
|
c := callbackLookup(uintptr(hook)).(*Context)
|
|
go_uid_hint := C.GoString(uid_hint)
|
|
f := os.NewFile(uintptr(fd), go_uid_hint)
|
|
defer f.Close()
|
|
err := c.callback(go_uid_hint, prev_was_bad != 0, f)
|
|
if err != nil {
|
|
return C.GPG_ERR_CANCELED
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type Protocol int
|
|
|
|
const (
|
|
ProtocolOpenPGP Protocol = C.GPGME_PROTOCOL_OpenPGP
|
|
ProtocolCMS Protocol = C.GPGME_PROTOCOL_CMS
|
|
ProtocolGPGConf Protocol = C.GPGME_PROTOCOL_GPGCONF
|
|
ProtocolAssuan Protocol = C.GPGME_PROTOCOL_ASSUAN
|
|
ProtocolG13 Protocol = C.GPGME_PROTOCOL_G13
|
|
ProtocolUIServer Protocol = C.GPGME_PROTOCOL_UISERVER
|
|
ProtocolDefault Protocol = C.GPGME_PROTOCOL_DEFAULT
|
|
ProtocolUnknown Protocol = C.GPGME_PROTOCOL_UNKNOWN
|
|
)
|
|
|
|
type PinEntryMode int
|
|
|
|
// const ( // Unavailable in 1.3.2
|
|
// PinEntryDefault PinEntryMode = C.GPGME_PINENTRY_MODE_DEFAULT
|
|
// PinEntryAsk PinEntryMode = C.GPGME_PINENTRY_MODE_ASK
|
|
// PinEntryCancel PinEntryMode = C.GPGME_PINENTRY_MODE_CANCEL
|
|
// PinEntryError PinEntryMode = C.GPGME_PINENTRY_MODE_ERROR
|
|
// PinEntryLoopback PinEntryMode = C.GPGME_PINENTRY_MODE_LOOPBACK
|
|
// )
|
|
|
|
type EncryptFlag uint
|
|
|
|
const (
|
|
EncryptAlwaysTrust EncryptFlag = C.GPGME_ENCRYPT_ALWAYS_TRUST
|
|
EncryptNoEncryptTo EncryptFlag = C.GPGME_ENCRYPT_NO_ENCRYPT_TO
|
|
EncryptPrepare EncryptFlag = C.GPGME_ENCRYPT_PREPARE
|
|
EncryptExceptSign EncryptFlag = C.GPGME_ENCRYPT_EXPECT_SIGN
|
|
)
|
|
|
|
type HashAlgo int
|
|
|
|
// const values for HashAlgo values should be added when necessary.
|
|
|
|
type KeyListMode uint
|
|
|
|
const (
|
|
KeyListModeLocal KeyListMode = C.GPGME_KEYLIST_MODE_LOCAL
|
|
KeyListModeExtern KeyListMode = C.GPGME_KEYLIST_MODE_EXTERN
|
|
KeyListModeSigs KeyListMode = C.GPGME_KEYLIST_MODE_SIGS
|
|
KeyListModeSigNotations KeyListMode = C.GPGME_KEYLIST_MODE_SIG_NOTATIONS
|
|
KeyListModeEphemeral KeyListMode = C.GPGME_KEYLIST_MODE_EPHEMERAL
|
|
KeyListModeModeValidate KeyListMode = C.GPGME_KEYLIST_MODE_VALIDATE
|
|
)
|
|
|
|
type PubkeyAlgo int
|
|
|
|
// const values for PubkeyAlgo values should be added when necessary.
|
|
|
|
type SigMode int
|
|
|
|
const (
|
|
SigModeNormal SigMode = C.GPGME_SIG_MODE_NORMAL
|
|
SigModeDetach SigMode = C.GPGME_SIG_MODE_DETACH
|
|
SigModeClear SigMode = C.GPGME_SIG_MODE_CLEAR
|
|
)
|
|
|
|
type SigSum int
|
|
|
|
const (
|
|
SigSumValid SigSum = C.GPGME_SIGSUM_VALID
|
|
SigSumGreen SigSum = C.GPGME_SIGSUM_GREEN
|
|
SigSumRed SigSum = C.GPGME_SIGSUM_RED
|
|
SigSumKeyRevoked SigSum = C.GPGME_SIGSUM_KEY_REVOKED
|
|
SigSumKeyExpired SigSum = C.GPGME_SIGSUM_KEY_EXPIRED
|
|
SigSumSigExpired SigSum = C.GPGME_SIGSUM_SIG_EXPIRED
|
|
SigSumKeyMissing SigSum = C.GPGME_SIGSUM_KEY_MISSING
|
|
SigSumCRLMissing SigSum = C.GPGME_SIGSUM_CRL_MISSING
|
|
SigSumCRLTooOld SigSum = C.GPGME_SIGSUM_CRL_TOO_OLD
|
|
SigSumBadPolicy SigSum = C.GPGME_SIGSUM_BAD_POLICY
|
|
SigSumSysError SigSum = C.GPGME_SIGSUM_SYS_ERROR
|
|
)
|
|
|
|
type Validity int
|
|
|
|
const (
|
|
ValidityUnknown Validity = C.GPGME_VALIDITY_UNKNOWN
|
|
ValidityUndefined Validity = C.GPGME_VALIDITY_UNDEFINED
|
|
ValidityNever Validity = C.GPGME_VALIDITY_NEVER
|
|
ValidityMarginal Validity = C.GPGME_VALIDITY_MARGINAL
|
|
ValidityFull Validity = C.GPGME_VALIDITY_FULL
|
|
ValidityUltimate Validity = C.GPGME_VALIDITY_ULTIMATE
|
|
)
|
|
|
|
type ErrorCode int
|
|
|
|
const (
|
|
ErrorNoError ErrorCode = C.GPG_ERR_NO_ERROR
|
|
ErrorEOF ErrorCode = C.GPG_ERR_EOF
|
|
)
|
|
|
|
// Error is a wrapper for GPGME errors
|
|
type Error struct {
|
|
err C.gpgme_error_t
|
|
}
|
|
|
|
func (e Error) Code() ErrorCode {
|
|
return ErrorCode(C.gpgme_err_code(e.err))
|
|
}
|
|
|
|
func (e Error) Error() string {
|
|
return C.GoString(C.gpgme_strerror(e.err))
|
|
}
|
|
|
|
func handleError(err C.gpgme_error_t) error {
|
|
e := Error{err: err}
|
|
if e.Code() == ErrorNoError {
|
|
return nil
|
|
}
|
|
return e
|
|
}
|
|
|
|
func cbool(b bool) C.int {
|
|
if b {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func EngineCheckVersion(p Protocol) error {
|
|
return handleError(C.gpgme_engine_check_version(C.gpgme_protocol_t(p)))
|
|
}
|
|
|
|
type EngineInfo struct {
|
|
next *EngineInfo
|
|
protocol Protocol
|
|
fileName string
|
|
homeDir string
|
|
version string
|
|
requiredVersion string
|
|
}
|
|
|
|
func copyEngineInfo(info C.gpgme_engine_info_t) *EngineInfo {
|
|
res := &EngineInfo{
|
|
next: nil,
|
|
protocol: Protocol(info.protocol),
|
|
fileName: C.GoString(info.file_name),
|
|
homeDir: C.GoString(info.home_dir),
|
|
version: C.GoString(info.version),
|
|
requiredVersion: C.GoString(info.req_version),
|
|
}
|
|
if info.next != nil {
|
|
res.next = copyEngineInfo(info.next)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (e *EngineInfo) Next() *EngineInfo {
|
|
return e.next
|
|
}
|
|
|
|
func (e *EngineInfo) Protocol() Protocol {
|
|
return e.protocol
|
|
}
|
|
|
|
func (e *EngineInfo) FileName() string {
|
|
return e.fileName
|
|
}
|
|
|
|
func (e *EngineInfo) Version() string {
|
|
return e.version
|
|
}
|
|
|
|
func (e *EngineInfo) RequiredVersion() string {
|
|
return e.requiredVersion
|
|
}
|
|
|
|
func (e *EngineInfo) HomeDir() string {
|
|
return e.homeDir
|
|
}
|
|
|
|
func GetEngineInfo() (*EngineInfo, error) {
|
|
var cInfo C.gpgme_engine_info_t
|
|
err := handleError(C.gpgme_get_engine_info(&cInfo))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return copyEngineInfo(cInfo), nil // It is up to the caller not to invalidate cInfo concurrently until this is done.
|
|
}
|
|
|
|
func SetEngineInfo(proto Protocol, fileName, homeDir string) error {
|
|
var cfn, chome *C.char
|
|
if fileName != "" {
|
|
cfn = C.CString(fileName)
|
|
defer C.free(unsafe.Pointer(cfn))
|
|
}
|
|
if homeDir != "" {
|
|
chome = C.CString(homeDir)
|
|
defer C.free(unsafe.Pointer(chome))
|
|
}
|
|
return handleError(C.gpgme_set_engine_info(C.gpgme_protocol_t(proto), cfn, chome))
|
|
}
|
|
|
|
func FindKeys(pattern string, secretOnly bool) ([]*Key, error) {
|
|
var keys []*Key
|
|
ctx, err := New()
|
|
if err != nil {
|
|
return keys, err
|
|
}
|
|
defer ctx.Release()
|
|
if err := ctx.KeyListStart(pattern, secretOnly); err != nil {
|
|
return keys, err
|
|
}
|
|
defer ctx.KeyListEnd()
|
|
for ctx.KeyListNext() {
|
|
keys = append(keys, ctx.Key)
|
|
}
|
|
if ctx.KeyError != nil {
|
|
return keys, ctx.KeyError
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
func Decrypt(r io.Reader) (*Data, error) {
|
|
ctx, err := New()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer ctx.Release()
|
|
cipher, err := NewDataReader(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cipher.Close()
|
|
plain, err := NewData()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = ctx.Decrypt(cipher, plain)
|
|
plain.Seek(0, SeekSet)
|
|
return plain, err
|
|
}
|
|
|
|
type Context struct {
|
|
Key *Key
|
|
KeyError error
|
|
|
|
callback Callback
|
|
cbc uintptr // WARNING: Call runtime.KeepAlive(c) after ANY use of c.cbc in C (typically via c.ctx)
|
|
|
|
ctx C.gpgme_ctx_t // WARNING: Call runtime.KeepAlive(c) after ANY passing of c.ctx to C
|
|
}
|
|
|
|
func New() (*Context, error) {
|
|
c := &Context{}
|
|
err := C.gpgme_new(&c.ctx)
|
|
runtime.SetFinalizer(c, (*Context).Release)
|
|
return c, handleError(err)
|
|
}
|
|
|
|
func (c *Context) Release() {
|
|
if c.ctx == nil {
|
|
return
|
|
}
|
|
if c.cbc > 0 {
|
|
callbackDelete(c.cbc)
|
|
}
|
|
C.gpgme_release(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
c.ctx = nil
|
|
}
|
|
|
|
func (c *Context) SetArmor(yes bool) {
|
|
C.gpgme_set_armor(c.ctx, cbool(yes))
|
|
runtime.KeepAlive(c)
|
|
}
|
|
|
|
func (c *Context) Armor() bool {
|
|
res := C.gpgme_get_armor(c.ctx) != 0
|
|
runtime.KeepAlive(c)
|
|
return res
|
|
}
|
|
|
|
func (c *Context) SetTextMode(yes bool) {
|
|
C.gpgme_set_textmode(c.ctx, cbool(yes))
|
|
runtime.KeepAlive(c)
|
|
}
|
|
|
|
func (c *Context) TextMode() bool {
|
|
res := C.gpgme_get_textmode(c.ctx) != 0
|
|
runtime.KeepAlive(c)
|
|
return res
|
|
}
|
|
|
|
func (c *Context) SetProtocol(p Protocol) error {
|
|
err := handleError(C.gpgme_set_protocol(c.ctx, C.gpgme_protocol_t(p)))
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) Protocol() Protocol {
|
|
res := Protocol(C.gpgme_get_protocol(c.ctx))
|
|
runtime.KeepAlive(c)
|
|
return res
|
|
}
|
|
|
|
func (c *Context) SetKeyListMode(m KeyListMode) error {
|
|
err := handleError(C.gpgme_set_keylist_mode(c.ctx, C.gpgme_keylist_mode_t(m)))
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) KeyListMode() KeyListMode {
|
|
res := KeyListMode(C.gpgme_get_keylist_mode(c.ctx))
|
|
runtime.KeepAlive(c)
|
|
return res
|
|
}
|
|
|
|
// Unavailable in 1.3.2:
|
|
// func (c *Context) SetPinEntryMode(m PinEntryMode) error {
|
|
// err := handleError(C.gpgme_set_pinentry_mode(c.ctx, C.gpgme_pinentry_mode_t(m)))
|
|
// runtime.KeepAlive(c)
|
|
// return err
|
|
// }
|
|
|
|
// Unavailable in 1.3.2:
|
|
// func (c *Context) PinEntryMode() PinEntryMode {
|
|
// res := PinEntryMode(C.gpgme_get_pinentry_mode(c.ctx))
|
|
// runtime.KeepAlive(c)
|
|
// return res
|
|
// }
|
|
|
|
func (c *Context) SetCallback(callback Callback) error {
|
|
var err error
|
|
c.callback = callback
|
|
if c.cbc > 0 {
|
|
callbackDelete(c.cbc)
|
|
}
|
|
if callback != nil {
|
|
cbc := callbackAdd(c)
|
|
c.cbc = cbc
|
|
_, err = C.gogpgme_set_passphrase_cb(c.ctx, C.gpgme_passphrase_cb_t(C.gogpgme_passfunc), C.uintptr_t(cbc))
|
|
} else {
|
|
c.cbc = 0
|
|
_, err = C.gogpgme_set_passphrase_cb(c.ctx, nil, 0)
|
|
}
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) EngineInfo() *EngineInfo {
|
|
cInfo := C.gpgme_ctx_get_engine_info(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
// NOTE: c must be live as long as we are accessing cInfo.
|
|
res := copyEngineInfo(cInfo)
|
|
runtime.KeepAlive(c) // for accesses to cInfo
|
|
return res
|
|
}
|
|
|
|
func (c *Context) SetEngineInfo(proto Protocol, fileName, homeDir string) error {
|
|
var cfn, chome *C.char
|
|
if fileName != "" {
|
|
cfn = C.CString(fileName)
|
|
defer C.free(unsafe.Pointer(cfn))
|
|
}
|
|
if homeDir != "" {
|
|
chome = C.CString(homeDir)
|
|
defer C.free(unsafe.Pointer(chome))
|
|
}
|
|
err := handleError(C.gpgme_ctx_set_engine_info(c.ctx, C.gpgme_protocol_t(proto), cfn, chome))
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) KeyListStart(pattern string, secretOnly bool) error {
|
|
cpattern := C.CString(pattern)
|
|
defer C.free(unsafe.Pointer(cpattern))
|
|
err := handleError(C.gpgme_op_keylist_start(c.ctx, cpattern, cbool(secretOnly)))
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) KeyListNext() bool {
|
|
c.Key = newKey()
|
|
err := handleError(C.gpgme_op_keylist_next(c.ctx, &c.Key.k))
|
|
runtime.KeepAlive(c) // implies runtime.KeepAlive(c.Key)
|
|
if err != nil {
|
|
if e, ok := err.(Error); ok && e.Code() == ErrorEOF {
|
|
c.KeyError = nil
|
|
} else {
|
|
c.KeyError = err
|
|
}
|
|
return false
|
|
}
|
|
c.KeyError = nil
|
|
return true
|
|
}
|
|
|
|
func (c *Context) KeyListEnd() error {
|
|
err := handleError(C.gpgme_op_keylist_end(c.ctx))
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) GetKey(fingerprint string, secret bool) (*Key, error) {
|
|
key := newKey()
|
|
cfpr := C.CString(fingerprint)
|
|
defer C.free(unsafe.Pointer(cfpr))
|
|
err := handleError(C.gpgme_get_key(c.ctx, cfpr, &key.k, cbool(secret)))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(key)
|
|
keyKIsNil := key.k == nil
|
|
runtime.KeepAlive(key)
|
|
if e, ok := err.(Error); keyKIsNil && ok && e.Code() == ErrorEOF {
|
|
return nil, fmt.Errorf("key %q not found", fingerprint)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (c *Context) Decrypt(ciphertext, plaintext *Data) error {
|
|
err := handleError(C.gpgme_op_decrypt(c.ctx, ciphertext.dh, plaintext.dh))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(ciphertext)
|
|
runtime.KeepAlive(plaintext)
|
|
return err
|
|
}
|
|
|
|
func (c *Context) DecryptVerify(ciphertext, plaintext *Data) error {
|
|
err := handleError(C.gpgme_op_decrypt_verify(c.ctx, ciphertext.dh, plaintext.dh))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(ciphertext)
|
|
runtime.KeepAlive(plaintext)
|
|
return err
|
|
}
|
|
|
|
type Signature struct {
|
|
Summary SigSum
|
|
Fingerprint string
|
|
Status error
|
|
Timestamp time.Time
|
|
ExpTimestamp time.Time
|
|
WrongKeyUsage bool
|
|
PKATrust uint
|
|
ChainModel bool
|
|
Validity Validity
|
|
ValidityReason error
|
|
PubkeyAlgo PubkeyAlgo
|
|
HashAlgo HashAlgo
|
|
}
|
|
|
|
func (c *Context) Verify(sig, signedText, plain *Data) (string, []Signature, error) {
|
|
var signedTextPtr, plainPtr C.gpgme_data_t = nil, nil
|
|
if signedText != nil {
|
|
signedTextPtr = signedText.dh
|
|
}
|
|
if plain != nil {
|
|
plainPtr = plain.dh
|
|
}
|
|
err := handleError(C.gpgme_op_verify(c.ctx, sig.dh, signedTextPtr, plainPtr))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(sig)
|
|
if signedText != nil {
|
|
runtime.KeepAlive(signedText)
|
|
}
|
|
if plain != nil {
|
|
runtime.KeepAlive(plain)
|
|
}
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
res := C.gpgme_op_verify_result(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
// NOTE: c must be live as long as we are accessing res.
|
|
sigs := []Signature{}
|
|
for s := res.signatures; s != nil; s = s.next {
|
|
sig := Signature{
|
|
Summary: SigSum(s.summary),
|
|
Fingerprint: C.GoString(s.fpr),
|
|
Status: handleError(s.status),
|
|
// s.notations not implemented
|
|
Timestamp: time.Unix(int64(s.timestamp), 0),
|
|
ExpTimestamp: time.Unix(int64(s.exp_timestamp), 0),
|
|
WrongKeyUsage: C.signature_wrong_key_usage(s) != 0,
|
|
PKATrust: uint(C.signature_pka_trust(s)),
|
|
ChainModel: C.signature_chain_model(s) != 0,
|
|
Validity: Validity(s.validity),
|
|
ValidityReason: handleError(s.validity_reason),
|
|
PubkeyAlgo: PubkeyAlgo(s.pubkey_algo),
|
|
HashAlgo: HashAlgo(s.hash_algo),
|
|
}
|
|
sigs = append(sigs, sig)
|
|
}
|
|
fileName := C.GoString(res.file_name)
|
|
runtime.KeepAlive(c) // for all accesses to res above
|
|
return fileName, sigs, nil
|
|
}
|
|
|
|
func (c *Context) Encrypt(recipients []*Key, flags EncryptFlag, plaintext, ciphertext *Data) error {
|
|
size := unsafe.Sizeof(new(C.gpgme_key_t))
|
|
recp := C.calloc(C.size_t(len(recipients)+1), C.size_t(size))
|
|
defer C.free(recp)
|
|
for i := range recipients {
|
|
ptr := (*C.gpgme_key_t)(unsafe.Pointer(uintptr(recp) + size*uintptr(i)))
|
|
*ptr = recipients[i].k
|
|
}
|
|
err := C.gpgme_op_encrypt(c.ctx, (*C.gpgme_key_t)(recp), C.gpgme_encrypt_flags_t(flags), plaintext.dh, ciphertext.dh)
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(recipients)
|
|
runtime.KeepAlive(plaintext)
|
|
runtime.KeepAlive(ciphertext)
|
|
return handleError(err)
|
|
}
|
|
|
|
func (c *Context) Sign(signers []*Key, plain, sig *Data, mode SigMode) error {
|
|
C.gpgme_signers_clear(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
for _, k := range signers {
|
|
err := handleError(C.gpgme_signers_add(c.ctx, k.k))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(k)
|
|
if err != nil {
|
|
C.gpgme_signers_clear(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
return err
|
|
}
|
|
}
|
|
err := handleError(C.gpgme_op_sign(c.ctx, plain.dh, sig.dh, C.gpgme_sig_mode_t(mode)))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(plain)
|
|
runtime.KeepAlive(sig)
|
|
return err
|
|
}
|
|
|
|
type AssuanDataCallback func(data []byte) error
|
|
type AssuanInquireCallback func(name, args string) error
|
|
type AssuanStatusCallback func(status, args string) error
|
|
|
|
// AssuanSend sends a raw Assuan command to gpg-agent
|
|
func (c *Context) AssuanSend(
|
|
cmd string,
|
|
data AssuanDataCallback,
|
|
inquiry AssuanInquireCallback,
|
|
status AssuanStatusCallback,
|
|
) error {
|
|
var operr C.gpgme_error_t
|
|
|
|
dataPtr := callbackAdd(&data)
|
|
inquiryPtr := callbackAdd(&inquiry)
|
|
statusPtr := callbackAdd(&status)
|
|
cmdCStr := C.CString(cmd)
|
|
defer C.free(unsafe.Pointer(cmdCStr))
|
|
err := C.gogpgme_op_assuan_transact_ext(
|
|
c.ctx,
|
|
cmdCStr,
|
|
C.uintptr_t(dataPtr),
|
|
C.uintptr_t(inquiryPtr),
|
|
C.uintptr_t(statusPtr),
|
|
&operr,
|
|
)
|
|
runtime.KeepAlive(c)
|
|
|
|
if handleError(operr) != nil {
|
|
return handleError(operr)
|
|
}
|
|
return handleError(err)
|
|
}
|
|
|
|
//export gogpgme_assuan_data_callback
|
|
func gogpgme_assuan_data_callback(handle unsafe.Pointer, data unsafe.Pointer, datalen C.size_t) C.gpgme_error_t {
|
|
c := callbackLookup(uintptr(handle)).(*AssuanDataCallback)
|
|
if *c == nil {
|
|
return 0
|
|
}
|
|
(*c)(C.GoBytes(data, C.int(datalen)))
|
|
return 0
|
|
}
|
|
|
|
//export gogpgme_assuan_inquiry_callback
|
|
func gogpgme_assuan_inquiry_callback(handle unsafe.Pointer, cName *C.char, cArgs *C.char) C.gpgme_error_t {
|
|
name := C.GoString(cName)
|
|
args := C.GoString(cArgs)
|
|
c := callbackLookup(uintptr(handle)).(*AssuanInquireCallback)
|
|
if *c == nil {
|
|
return 0
|
|
}
|
|
(*c)(name, args)
|
|
return 0
|
|
}
|
|
|
|
//export gogpgme_assuan_status_callback
|
|
func gogpgme_assuan_status_callback(handle unsafe.Pointer, cStatus *C.char, cArgs *C.char) C.gpgme_error_t {
|
|
status := C.GoString(cStatus)
|
|
args := C.GoString(cArgs)
|
|
c := callbackLookup(uintptr(handle)).(*AssuanStatusCallback)
|
|
if *c == nil {
|
|
return 0
|
|
}
|
|
(*c)(status, args)
|
|
return 0
|
|
}
|
|
|
|
// ExportModeFlags defines how keys are exported from Export
|
|
type ExportModeFlags uint
|
|
|
|
const (
|
|
ExportModeExtern ExportModeFlags = C.GPGME_EXPORT_MODE_EXTERN
|
|
ExportModeMinimal ExportModeFlags = C.GPGME_EXPORT_MODE_MINIMAL
|
|
)
|
|
|
|
func (c *Context) Export(pattern string, mode ExportModeFlags, data *Data) error {
|
|
pat := C.CString(pattern)
|
|
defer C.free(unsafe.Pointer(pat))
|
|
err := handleError(C.gpgme_op_export(c.ctx, pat, C.gpgme_export_mode_t(mode), data.dh))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(data)
|
|
return err
|
|
}
|
|
|
|
// ImportStatusFlags describes the type of ImportStatus.Status. The C API in gpgme.h simply uses "unsigned".
|
|
type ImportStatusFlags uint
|
|
|
|
const (
|
|
ImportNew ImportStatusFlags = C.GPGME_IMPORT_NEW
|
|
ImportUID ImportStatusFlags = C.GPGME_IMPORT_UID
|
|
ImportSIG ImportStatusFlags = C.GPGME_IMPORT_SIG
|
|
ImportSubKey ImportStatusFlags = C.GPGME_IMPORT_SUBKEY
|
|
ImportSecret ImportStatusFlags = C.GPGME_IMPORT_SECRET
|
|
)
|
|
|
|
type ImportStatus struct {
|
|
Fingerprint string
|
|
Result error
|
|
Status ImportStatusFlags
|
|
}
|
|
|
|
type ImportResult struct {
|
|
Considered int
|
|
NoUserID int
|
|
Imported int
|
|
ImportedRSA int
|
|
Unchanged int
|
|
NewUserIDs int
|
|
NewSubKeys int
|
|
NewSignatures int
|
|
NewRevocations int
|
|
SecretRead int
|
|
SecretImported int
|
|
SecretUnchanged int
|
|
NotImported int
|
|
Imports []ImportStatus
|
|
}
|
|
|
|
func (c *Context) Import(keyData *Data) (*ImportResult, error) {
|
|
err := handleError(C.gpgme_op_import(c.ctx, keyData.dh))
|
|
runtime.KeepAlive(c)
|
|
runtime.KeepAlive(keyData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res := C.gpgme_op_import_result(c.ctx)
|
|
runtime.KeepAlive(c)
|
|
// NOTE: c must be live as long as we are accessing res.
|
|
imports := []ImportStatus{}
|
|
for s := res.imports; s != nil; s = s.next {
|
|
imports = append(imports, ImportStatus{
|
|
Fingerprint: C.GoString(s.fpr),
|
|
Result: handleError(s.result),
|
|
Status: ImportStatusFlags(s.status),
|
|
})
|
|
}
|
|
importResult := &ImportResult{
|
|
Considered: int(res.considered),
|
|
NoUserID: int(res.no_user_id),
|
|
Imported: int(res.imported),
|
|
ImportedRSA: int(res.imported_rsa),
|
|
Unchanged: int(res.unchanged),
|
|
NewUserIDs: int(res.new_user_ids),
|
|
NewSubKeys: int(res.new_sub_keys),
|
|
NewSignatures: int(res.new_signatures),
|
|
NewRevocations: int(res.new_revocations),
|
|
SecretRead: int(res.secret_read),
|
|
SecretImported: int(res.secret_imported),
|
|
SecretUnchanged: int(res.secret_unchanged),
|
|
NotImported: int(res.not_imported),
|
|
Imports: imports,
|
|
}
|
|
runtime.KeepAlive(c) // for all accesses to res above
|
|
return importResult, nil
|
|
}
|
|
|
|
type Key struct {
|
|
k C.gpgme_key_t // WARNING: Call Runtime.KeepAlive(k) after ANY passing of k.k to C
|
|
}
|
|
|
|
func newKey() *Key {
|
|
k := &Key{}
|
|
runtime.SetFinalizer(k, (*Key).Release)
|
|
return k
|
|
}
|
|
|
|
func (k *Key) Release() {
|
|
C.gpgme_key_release(k.k)
|
|
runtime.KeepAlive(k)
|
|
k.k = nil
|
|
}
|
|
|
|
func (k *Key) Revoked() bool {
|
|
res := C.key_revoked(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) Expired() bool {
|
|
res := C.key_expired(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) Disabled() bool {
|
|
res := C.key_disabled(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) Invalid() bool {
|
|
res := C.key_invalid(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) CanEncrypt() bool {
|
|
res := C.key_can_encrypt(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) CanSign() bool {
|
|
res := C.key_can_sign(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) CanCertify() bool {
|
|
res := C.key_can_certify(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) Secret() bool {
|
|
res := C.key_secret(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) CanAuthenticate() bool {
|
|
res := C.key_can_authenticate(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) IsQualified() bool {
|
|
res := C.key_is_qualified(k.k) != 0
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) Protocol() Protocol {
|
|
res := Protocol(k.k.protocol)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) IssuerSerial() string {
|
|
res := C.GoString(k.k.issuer_serial)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) IssuerName() string {
|
|
res := C.GoString(k.k.issuer_name)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) ChainID() string {
|
|
res := C.GoString(k.k.chain_id)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) OwnerTrust() Validity {
|
|
res := Validity(k.k.owner_trust)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
func (k *Key) SubKeys() *SubKey {
|
|
subKeys := k.k.subkeys
|
|
runtime.KeepAlive(k)
|
|
if subKeys == nil {
|
|
return nil
|
|
}
|
|
return &SubKey{k: subKeys, parent: k} // The parent: k reference ensures subKeys remains valid
|
|
}
|
|
|
|
func (k *Key) UserIDs() *UserID {
|
|
uids := k.k.uids
|
|
runtime.KeepAlive(k)
|
|
if uids == nil {
|
|
return nil
|
|
}
|
|
return &UserID{u: uids, parent: k} // The parent: k reference ensures uids remains valid
|
|
}
|
|
|
|
func (k *Key) KeyListMode() KeyListMode {
|
|
res := KeyListMode(k.k.keylist_mode)
|
|
runtime.KeepAlive(k)
|
|
return res
|
|
}
|
|
|
|
type SubKey struct {
|
|
k C.gpgme_subkey_t
|
|
parent *Key // make sure the key is not released when we have a reference to a subkey
|
|
}
|
|
|
|
func (k *SubKey) Next() *SubKey {
|
|
if k.k.next == nil {
|
|
return nil
|
|
}
|
|
return &SubKey{k: k.k.next, parent: k.parent}
|
|
}
|
|
|
|
func (k *SubKey) Revoked() bool {
|
|
return C.subkey_revoked(k.k) != 0
|
|
}
|
|
|
|
func (k *SubKey) Expired() bool {
|
|
return C.subkey_expired(k.k) != 0
|
|
}
|
|
|
|
func (k *SubKey) Disabled() bool {
|
|
return C.subkey_disabled(k.k) != 0
|
|
}
|
|
|
|
func (k *SubKey) Invalid() bool {
|
|
return C.subkey_invalid(k.k) != 0
|
|
}
|
|
|
|
func (k *SubKey) Secret() bool {
|
|
return C.subkey_secret(k.k) != 0
|
|
}
|
|
|
|
func (k *SubKey) KeyID() string {
|
|
return C.GoString(k.k.keyid)
|
|
}
|
|
|
|
func (k *SubKey) Fingerprint() string {
|
|
return C.GoString(k.k.fpr)
|
|
}
|
|
|
|
func (k *SubKey) Created() time.Time {
|
|
if k.k.timestamp <= 0 {
|
|
return time.Time{}
|
|
}
|
|
return time.Unix(int64(k.k.timestamp), 0)
|
|
}
|
|
|
|
func (k *SubKey) Expires() time.Time {
|
|
if k.k.expires <= 0 {
|
|
return time.Time{}
|
|
}
|
|
return time.Unix(int64(k.k.expires), 0)
|
|
}
|
|
|
|
func (k *SubKey) CardNumber() string {
|
|
return C.GoString(k.k.card_number)
|
|
}
|
|
|
|
type UserID struct {
|
|
u C.gpgme_user_id_t
|
|
parent *Key // make sure the key is not released when we have a reference to a user ID
|
|
}
|
|
|
|
func (u *UserID) Next() *UserID {
|
|
if u.u.next == nil {
|
|
return nil
|
|
}
|
|
return &UserID{u: u.u.next, parent: u.parent}
|
|
}
|
|
|
|
func (u *UserID) Revoked() bool {
|
|
return C.uid_revoked(u.u) != 0
|
|
}
|
|
|
|
func (u *UserID) Invalid() bool {
|
|
return C.uid_invalid(u.u) != 0
|
|
}
|
|
|
|
func (u *UserID) Validity() Validity {
|
|
return Validity(u.u.validity)
|
|
}
|
|
|
|
func (u *UserID) UID() string {
|
|
return C.GoString(u.u.uid)
|
|
}
|
|
|
|
func (u *UserID) Name() string {
|
|
return C.GoString(u.u.name)
|
|
}
|
|
|
|
func (u *UserID) Comment() string {
|
|
return C.GoString(u.u.comment)
|
|
}
|
|
|
|
func (u *UserID) Email() string {
|
|
return C.GoString(u.u.email)
|
|
}
|