Mig-Action-Generator: First shot at the action creator, with GPG signature handling using gpgme

This commit is contained in:
Julien Vehent 2014-01-06 16:01:51 -05:00
Родитель f7459e8d9d
Коммит 5de7bcd328
4 изменённых файлов: 379 добавлений и 0 удалений

Просмотреть файл

@ -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
}