зеркало из https://github.com/mozilla/mig.git
Mig-Action-Generator: First shot at the action creator, with GPG signature handling using gpgme
This commit is contained in:
Родитель
f7459e8d9d
Коммит
5de7bcd328
11
build.sh
11
build.sh
|
@ -30,6 +30,17 @@ do
|
|||
done
|
||||
done
|
||||
|
||||
# build the C code for action generator
|
||||
opwd=$(pwd)
|
||||
cd "src/mig/client"
|
||||
[ -e libmig_gpgme.o ] && rm libmig_gpgme.o
|
||||
[ -e libmig_gpgme.a ] && rm libmig_gpgme.a
|
||||
gcc -Wall -c libmig_gpgme.c -o libmig_gpgme.o
|
||||
ar -cvq libmig_gpgme.a libmig_gpgme.o
|
||||
go build -o $opwd/bin/$LINUX64/mig-action-generator mig-action-generator.go
|
||||
rm libmig_gpgme.o libmig_gpgme.a
|
||||
cd $opwd
|
||||
|
||||
# basic test
|
||||
# (note to self: stop being lazy and write unit tests!)
|
||||
echo -n Testing...
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/* Build instructions
|
||||
* $ gcc -Wall -c libmig_gpgme.c
|
||||
* $ ar -cvq libmig_gpgme.a libmig_gpgme.o
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <gpgme.h>
|
||||
#include <inttypes.h>
|
||||
#include <locale.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libmig_gpgme.h"
|
||||
|
||||
#define fail_if_err(err) \
|
||||
do { \
|
||||
if(err) { \
|
||||
fprintf(stderr, "%s:%d: %s: %s\n", \
|
||||
__FILE__, __LINE__, gpgme_strsource(err), \
|
||||
gpgme_strerror(err)); \
|
||||
exit(1); \
|
||||
} \
|
||||
} \
|
||||
while(0)
|
||||
|
||||
const char * MIGSign(char *signKeyID, char *stringToBeSigned) {
|
||||
gpgme_ctx_t ctx;
|
||||
gpgme_error_t err;
|
||||
gpgme_data_t in, out;
|
||||
gpgme_key_t signer[1] = {NULL};
|
||||
//gpgme_sign_result_t result;
|
||||
//gpgme_new_signature_t sig;
|
||||
// Set the GPGME signature mode
|
||||
// GPGME_SIG_MODE_NORMAL : Signature with data
|
||||
// GPGME_SIG_MODE_CLEAR : Clear signed text
|
||||
// GPGME_SIG_MODE_DETACH : Detached signature
|
||||
gpgme_sig_mode_t sigMode = GPGME_SIG_MODE_DETACH;
|
||||
|
||||
// GPG signatures are hashes encrypted with the private RSA key
|
||||
// Thus, signatures are the same size as the key itself. often
|
||||
// it's 2048 bits, but can be more. The resulting ASCII signature
|
||||
// will be smaller: for a 4096 bits key, the ascii sig is 836 bytes
|
||||
// The value of 2048 chars below is enough even for 8192 bits keys
|
||||
// http://tools.ietf.org/search/rfc4880#section-5.2.4
|
||||
static char signature[2048];
|
||||
int ret;
|
||||
|
||||
// Begin setup of GPGME
|
||||
gpgme_check_version(NULL);
|
||||
setlocale(LC_ALL, "");
|
||||
gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
|
||||
#ifndef HAVE_W32_SYSTEM
|
||||
gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
|
||||
#endif
|
||||
// Create the GPGME Context
|
||||
err = gpgme_new(&ctx);
|
||||
// Error handling
|
||||
fail_if_err(err);
|
||||
|
||||
// Unset the context to textmode
|
||||
gpgme_set_textmode(ctx, 1);
|
||||
// Disable ASCII armor on the context
|
||||
gpgme_set_armor(ctx, 1);
|
||||
|
||||
// Find the signing key
|
||||
err = gpgme_op_keylist_start(ctx, signKeyID, 1);
|
||||
fail_if_err(err);
|
||||
err = gpgme_op_keylist_next(ctx, &signer[0]);
|
||||
fail_if_err(err);
|
||||
err = gpgme_op_keylist_end(ctx);
|
||||
fail_if_err(err);
|
||||
|
||||
// Clear signers and add the key we want
|
||||
gpgme_signers_clear(ctx);
|
||||
fail_if_err(err);
|
||||
err = gpgme_signers_add(ctx, signer[0]);
|
||||
fail_if_err(err);
|
||||
|
||||
// Create a data object pointing to the memory segment
|
||||
err = gpgme_data_new_from_mem(&in, stringToBeSigned, strlen(stringToBeSigned), 1);
|
||||
fail_if_err(err);
|
||||
|
||||
// Create a data object pointing to the out buffer
|
||||
err = gpgme_data_new(&out);
|
||||
fail_if_err(err);
|
||||
|
||||
// set output encoding to base64
|
||||
|
||||
// Sign the contents of "in" using the defined mode and place it into "out"
|
||||
err = gpgme_op_sign(ctx, in, out, sigMode);
|
||||
fail_if_err(err);
|
||||
|
||||
// Rewind the "out" data object
|
||||
ret = gpgme_data_seek(out, 0, SEEK_SET);
|
||||
if(ret)
|
||||
fail_if_err(gpgme_err_code_from_errno(errno));
|
||||
|
||||
// Read the contents of "out" into the signature
|
||||
ret = gpgme_data_read(out, signature, 2048);
|
||||
if(ret < 0)
|
||||
fail_if_err(gpgme_err_code_from_errno(errno));
|
||||
|
||||
gpgme_data_release(in);
|
||||
gpgme_data_release(out);
|
||||
gpgme_release(ctx);
|
||||
|
||||
return signature;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <gpgme.h>
|
||||
|
||||
const char * MIGSign(char *signKeyID, char *stringToBeSigned);
|
|
@ -0,0 +1,254 @@
|
|||
package main
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/go.crypto/openpgp"
|
||||
"code.google.com/p/go.crypto/openpgp/armor"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mig"
|
||||
"mig/modules/filechecker"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I.
|
||||
#cgo LDFLAGS: -lgpgme libmig_gpgme.a
|
||||
#include <libmig_gpgme.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func main() {
|
||||
|
||||
var Usage = func() {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"Mozilla InvestiGator Action Generator\n" +
|
||||
"usage: %s -k=<key id> (-i <input file)\n\n" +
|
||||
"Command line to generate and sign MIG Actions.\n" +
|
||||
"The resulting actions are display on stdout.\n\n" +
|
||||
"Options:\n",
|
||||
os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// command line options
|
||||
var key = flag.String("k", "key identifier", "Key identifier used to sign the action (ex: B75C2346)")
|
||||
var file = flag.String("i", "/path/to/file", "Load action from file")
|
||||
flag.Parse()
|
||||
|
||||
// We need a key, if none is set on the command line, fail
|
||||
if *key == "key identifier" {
|
||||
Usage()
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
var ea mig.ExtendedAction
|
||||
var err error
|
||||
if *file != "/path/to/file" {
|
||||
// get action from local json file
|
||||
ea, err = getActionFromFile(*file)
|
||||
} else {
|
||||
//interactive mode
|
||||
ea, err = getActionFromTerminal()
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Compute a GPG signature of the action
|
||||
sig, err := signAction(ea, *key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// transform sig into json array
|
||||
asig := strings.Split(sig, "\n")
|
||||
for _, sigcomp := range asig {
|
||||
ea.Signature = append(ea.Signature, sigcomp)
|
||||
}
|
||||
ea.SignatureDate = time.Now().UTC()
|
||||
jsonAction, err := json.Marshal(ea)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", jsonAction)
|
||||
|
||||
// Verify the GPG signature
|
||||
err = verifySignature(ea)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getActionFromFile(path string) (ea mig.ExtendedAction, err error) {
|
||||
fd, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = json.Unmarshal(fd, &ea)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getActionFromTerminal() (ea mig.ExtendedAction, err error) {
|
||||
err = nil
|
||||
fmt.Print("Action name> ")
|
||||
_, err = fmt.Scanln(&ea.Action.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("Action Target> ")
|
||||
_, err = fmt.Scanln(&ea.Action.Target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("Action Check> ")
|
||||
_, err = fmt.Scanln(&ea.Action.Check)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("Action Expiration> ")
|
||||
var expiration string
|
||||
_, err = fmt.Scanln(&expiration)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ea.Action.ScheduledDate = time.Now().UTC()
|
||||
period, err := time.ParseDuration(expiration)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ea.Action.ExpirationDate = time.Now().UTC().Add(period)
|
||||
|
||||
var checkArgs string
|
||||
switch ea.Action.Check {
|
||||
default:
|
||||
fmt.Print("Unknown check type, supply JSON arguments> ")
|
||||
_, err := fmt.Scanln(&checkArgs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(checkArgs), ea.Action.Arguments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "filechecker":
|
||||
fmt.Println("Filechecker module parameters")
|
||||
var name string
|
||||
var fcargs filechecker.FileCheck
|
||||
fmt.Print("Check Name> ")
|
||||
_, err := fmt.Scanln(&name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("Filechecker Type> ")
|
||||
_, err = fmt.Scanln(&fcargs.Type)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("File Path> ")
|
||||
_, err = fmt.Scanln(&fcargs.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print("Check Value> ")
|
||||
_, err = fmt.Scanln(&fcargs.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fc := make(map[string]filechecker.FileCheck)
|
||||
fc[name] = fcargs
|
||||
ea.Action.Arguments = fc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// signAction signs a string with a key. The function uses a C library that
|
||||
// calls gpgme, for compatibility with gpg-agent.
|
||||
func signAction(ea mig.ExtendedAction, key string) (sig string, err error) {
|
||||
// prepare string for signature
|
||||
srcStr := prepDataToSign(ea)
|
||||
|
||||
// convert to C variable
|
||||
ckey := C.CString(key)
|
||||
cstr := C.CString(srcStr)
|
||||
|
||||
// calculate signature
|
||||
csig := C.MIGSign(ckey, cstr)
|
||||
|
||||
// convert signature back to Go string
|
||||
sig = C.GoString(csig)
|
||||
|
||||
C.free(unsafe.Pointer(ckey))
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// verifySignature checks the validity of an armored signature
|
||||
func verifySignature(ea mig.ExtendedAction) error {
|
||||
var signature string
|
||||
|
||||
// extract armored signature to string
|
||||
for _, s := range ea.Signature {
|
||||
signature = fmt.Sprintf("%s\n%s", signature, s)
|
||||
}
|
||||
|
||||
// transform string into io.Reader
|
||||
sigReader := bytes.NewBufferString(signature)
|
||||
|
||||
// decode armor
|
||||
block, err := armor.Decode(sigReader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if block.Type != "PGP SIGNATURE" {
|
||||
log.Fatal("Wrong signature type", block.Type)
|
||||
}
|
||||
|
||||
// get the source data
|
||||
srcStr := prepDataToSign(ea)
|
||||
|
||||
// convert to io.Reader
|
||||
srcReader := bytes.NewBufferString(srcStr)
|
||||
|
||||
// verify the signature and get the signer back
|
||||
pubringFile, err := os.Open("/home/ulfr/.gnupg/pubring.gpg")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer pubringFile.Close()
|
||||
pubring, err := openpgp.ReadKeyRing(pubringFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signer, err := openpgp.CheckArmoredDetachedSignature(pubring, srcReader, sigReader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Signature verified. Signer=")
|
||||
for _, ident := range signer.Identities {
|
||||
fmt.Printf("'%s', ", ident.UserId.Email)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepDataToSign concatenates Action components into a string
|
||||
func prepDataToSign(ea mig.ExtendedAction) (str string) {
|
||||
str = ea.Action.Name
|
||||
str += ea.Action.Target
|
||||
str += ea.Action.Check
|
||||
str += ea.Action.ScheduledDate.String()
|
||||
str += ea.Action.ExpirationDate.String()
|
||||
return
|
||||
}
|
Загрузка…
Ссылка в новой задаче