Example #1
0
func ManifestFromBytes(bytes []byte) (*Manifest, error) {
	manifest := &Manifest{}

	// Preserve the raw manifest so that manifest.Bytes() returns bytes in
	// the same order that they were passed to this function
	manifest.raw = make([]byte, len(bytes))
	copy(manifest.raw, bytes)

	signed, _ := clearsign.Decode(bytes)
	if signed != nil {
		signature, err := ioutil.ReadAll(signed.ArmoredSignature.Body)
		if err != nil {
			return nil, fmt.Errorf("Could not read signature from pod manifest: %s", err)
		}
		manifest.signature = signature

		// the original plaintext is in signed.Plaintext, but the signature
		// corresponds to signed.Bytes, so that's what we need to save
		manifest.plaintext = signed.Bytes

		// parse YAML from the message's plaintext instead
		bytes = signed.Plaintext
	}

	if err := yaml.Unmarshal(bytes, manifest); err != nil {
		return nil, fmt.Errorf("Could not read pod manifest: %s", err)
	}
	return manifest, nil
}
Example #2
0
func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	block, _ := clearsign.Decode(data)
	if block == nil {
		// There was no sig in the file.
		return nil, errors.New("signature block not found")
	}

	return block, nil
}
Example #3
0
func ParseOpenPGPParagraph(reader *bufio.Reader) (ret *Paragraph, ohshit error) {
	els := ""
	for {
		line, err := reader.ReadString('\n')
		if err == io.EOF {
			break
		}
		els = els + line
	}
	block, _ := clearsign.Decode([]byte(els))
	/**
	 * XXX: With the block, we need to validate everything.
	 *
	 * We need to hit openpgp.CheckDetachedSignature with block and
	 * a keyring. For now, it'll ignore all signature checking entirely.
	 */
	return ParseParagraph(bufio.NewReader(strings.NewReader(string(block.Bytes))))
}
Example #4
0
// Internal method to read an OpenPGP Clearsigned document, store related
// OpenPGP information onto the shell Struct, and return any errors that
// we encounter along the way, such as an invalid signature, unknown
// signer, or incomplete document. If `keyring` is `nil`, checking of the
// signed data is *not* preformed.
func (p *ParagraphReader) decodeClearsig(keyring *openpgp.EntityList) error {
	// One *massive* downside here is that the OpenPGP module in Go operates
	// on byte arrays in memory, and *not* on Readers and Writers. This is a
	// huge PITA because it does't need to be that way, and this forces
	// clearsigned documents into memory. Which f*****g sucks. But here
	// we are. It's likely worth a bug or two on this.

	signedData, err := ioutil.ReadAll(p.reader)
	if err != nil {
		return err
	}

	block, _ := clearsign.Decode(signedData)
	/* We're only interested in the first block. This may change in the
	 * future, in which case, we should likely set reader back to
	 * the remainder, and return that out to put through another
	 * ParagraphReader, since it may have a different signer. */

	if keyring == nil {
		/* As a special case, if the keyring is nil, we can go ahead
		 * and assume this data isn't intended to be checked against the
		 * keyring. So, we'll just pass on through. */
		p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes))
		return nil
	}

	/* Now, we have to go ahead and check that the signature is valid and
	 * relates to an entity we have in our keyring */
	signer, err := openpgp.CheckDetachedSignature(
		keyring,
		bytes.NewReader(block.Bytes),
		block.ArmoredSignature.Body,
	)

	if err != nil {
		return err
	}

	p.signer = signer
	p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes))

	return nil
}
Example #5
0
func decodeCheckSignature(r io.Reader, publicKey string) ([]byte, error) {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	b, _ := clearsign.Decode(data)
	if b == nil {
		return nil, errors.New("no PGP signature embedded in plain text data")
	}
	keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKey))
	if err != nil {
		return nil, fmt.Errorf("failed to parse public key: %v", err)
	}

	_, err = openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body)
	if err != nil {
		return nil, err
	}
	return b.Plaintext, nil
}
Example #6
0
func checkGPG(file *File) (state SigState, err error) {
	var signer *openpgp.Entity
	var cs *clearsign.Block
	keypath := path.Join(os.Getenv("HOME"), "/.gnupg/pubring.gpg")
	keys, err := os.Open(keypath)
	if err != nil {
		fmt.Printf("Could not open public keyring at %s\n", keypath)
		os.Exit(2)
	}

	keyring, err := openpgp.ReadKeyRing(keys)

	if err != nil {
		fmt.Printf("Error reading public keyring: %s\n", err)
		os.Exit(2)
	}

	if *flagClear {
		cs, _ = clearsign.Decode(file.content)
		if cs == nil {
			fmt.Printf("Problem decoding clearsign signature from file %s\n", file.name)
			os.Exit(2)
		}

		lsig, err := ioutil.ReadAll(cs.ArmoredSignature.Body)

		if err != nil {
			fmt.Printf("Problem reading signature from %s.  Are you sure this file is clearsigned?: %s\n", file.name, err)
			os.Exit(2)
		}
		if len(lsig) > 0 {
			file.signature = lsig
			file.content = cs.Bytes
			*flagBin = true
		}

	}

	if *flagBin {
		signer, err = openpgp.CheckDetachedSignature(keyring, bytes.NewReader(file.content), bytes.NewReader(file.signature))
	} else {
		signer, err = openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(file.content), bytes.NewReader(file.signature))
	}
	if err != nil {
		fmt.Printf("Invalid signature or public key not present: %s\n", err)
		os.Exit(2)
	}

	state.sig = signer.PrimaryKey.KeyIdString()

	l := len(*flagKeyid)
	if l > 0 {
		var rid string

		// Force the local id to be all uppercase
		lid := strings.ToUpper(*flagKeyid)

		// check the number of chars on the remote id to see if it's a
		// short or long id. If it's not 8 or 16, it's not valid.
		switch l {
		case 8:
			fmt.Println("WARNING: The use of short ids is NOT secure.  See https://evil32.com for more info.")
			fmt.Println("Refusing to use insecure key mechanism. Exiting.")
			os.Exit(1)
		case 16:
			fmt.Println("WARNING: The use of long ids is NOT considered to be secure.  See https://evil32.com for more info.")
			rid = signer.PrimaryKey.KeyIdString()
		}
		if len(rid) == 0 {
			fmt.Printf("You did not specify a valid GPG keyid length. Must be 8 or 16 characters.")
			os.Exit(2)
		}

		if lid != rid {
			fmt.Printf("The remote file was not signed by the expected GPG Public key. Expected %s and got %s\n", lid, rid)
			os.Exit(2)
		}
	}

	// Due to how clearsign works, the detached signature has to be
	// processed using the Bytes field, but the stripped content is located
	// in the Plaintext field. As we've verified the signature was valid
	// we can now fix the content

	if *flagClear {
		file.content = cs.Plaintext
	}

	state.success = true
	return state, nil
}
func main() {
	usage := `
 
Usage:
    create-coreos-vdi [-V VERSION] [-p PATH]
    
Options:
    -d DEST     Create CoreOS VDI image to the given path.
    -V VERSION  Version to install (e.g. alpha) [default: stable]
    -h          This help

This tool creates a CoreOS VDI image to be used with VirtualBox.
`

	arguments, _ := docopt.Parse(usage, nil, true, "Coreos create-coreos-vdi 0.1", false)

	_, _ = get_vboxmanage()

	RAW_IMAGE_NAME := "coreos_production_image.bin"
	IMAGE_NAME := RAW_IMAGE_NAME + ".bz2"
	DIGESTS_NAME := IMAGE_NAME + ".DIGESTS.asc"

	VERSION_ID, _ := arguments["-V"].(string)

	var BASE_URL string

	switch VERSION_ID {
	case "stable":
		BASE_URL = "http://stable.release.core-os.net/amd64-usr/current"
	case "alpha":
		BASE_URL = "http://alpha.release.core-os.net/amd64-usr/current"
	case "beta":
		BASE_URL = "http://beta.release.core-os.net/amd64-usr/current"
	default:
		BASE_URL = fmt.Sprintf("http://storage.core-os.net/coreos/amd64-usr/%s", VERSION_ID)
	}

	dest, ok := arguments["-p"].(string)
	if ok == false {
		dest, _ = os.Getwd()
	}

	workdir, _ := ioutil.TempDir(dest, "coreos")
	defer os.RemoveAll(workdir)

	IMAGE_URL := fmt.Sprintf("%s/%s", BASE_URL, IMAGE_NAME)
	DIGESTS_URL := fmt.Sprintf("%s/%s", BASE_URL, DIGESTS_NAME)
	DOWN_IMAGE := filepath.Join(workdir, RAW_IMAGE_NAME)

	var err error

	_, err = http.Head(IMAGE_URL)
	if err != nil {
		log.Fatal("Image URL unavailable:" + IMAGE_URL)
	}

	digests_get_result, err := http.Get(DIGESTS_URL)
	if err != nil {
		log.Fatal("Image signature unavailable:" + DIGESTS_URL)
	}

	digests_raw_message, err := ioutil.ReadAll(digests_get_result.Body)
	digests_get_result.Body.Close()

	// Gets CoreOS verion from version.txt file
	VERSION_NAME := "version.txt"
	VERSION_URL := fmt.Sprintf("%s/%s", BASE_URL, VERSION_NAME)

	version_result, err := http.Get(VERSION_URL)
	vars, _ := ReadVars(version_result.Body)
	VDI_IMAGE_NAME := fmt.Sprintf("coreos_production_%s.%s.%s.vdi", vars["COREOS_BUILD"], vars["COREOS_BRANCH"], vars["COREOS_PATCH"])
	VDI_IMAGE := filepath.Join(dest, VDI_IMAGE_NAME)

	decoded_long_id, err := hex.DecodeString(GPG_LONG_ID)
	decoded_long_id_int := binary.BigEndian.Uint64(decoded_long_id)

	fmt.Printf("Trusted hex key id %s is decimal %d\n", GPG_LONG_ID, decoded_long_id_int)

	pubkey_get_result, err := http.Get(GPG_KEY_URL)
	if err != nil {
		log.Fatal(err)
	}

	pubkey, _ := ioutil.ReadAll(pubkey_get_result.Body)
	pubkey_get_result.Body.Close()

	pubkey_reader := bytes.NewReader(pubkey)
	keyring, err := openpgp.ReadArmoredKeyRing(pubkey_reader)
	if err != nil {
		log.Fatal(err)
	}

	decoded_message, _ := clearsign.Decode(digests_raw_message)
	digests_text := string(decoded_message.Bytes)
	decoded_message_reader := bytes.NewReader(decoded_message.Bytes)

	res, err := openpgp.CheckDetachedSignature(keyring, decoded_message_reader, decoded_message.ArmoredSignature.Body)
	if err != nil {
		fmt.Println("Signature check for DIGESTS failed.")
	} else {
		if res.PrimaryKey.KeyId == decoded_long_id_int {
			fmt.Printf("Trusted key id %d matches keyid %d\n", decoded_long_id_int, decoded_long_id_int)
		}
		fmt.Printf("DIGESTS signature is valid: ")
	}

	var re = regexp.MustCompile(`(?m)(?P<method>(SHA1|SHA512)) HASH(?:\r?)\n(?P<hash>.[^\s]*)\s*(?P<file>[\w\d_\.]*)`)

	var keymap map[string]int = make(map[string]int)
	for index, name := range re.SubexpNames() {
		keymap[name] = index
	}

	matches := re.FindAllStringSubmatch(digests_text, -1)

	var bz_hash_sha1 string
	var bz_hash_sha512 string

	for _, match := range matches {
		if match[keymap["file"]] == IMAGE_NAME {
			if match[keymap["method"]] == "SHA1" {
				bz_hash_sha1 = match[keymap["hash"]]
			}
			if match[keymap["method"]] == "SHA512" {
				bz_hash_sha512 = match[keymap["hash"]]
			}
		}
	}

	sha1h := sha1.New()
	sha512h := sha512.New()

	fmt.Printf("downloading %s\n", IMAGE_NAME)

	response, err := http.Get(IMAGE_URL)
	defer response.Body.Close()

	bar := pb.New(int(response.ContentLength)).SetUnits(pb.U_BYTES)
	bar.Start()

	bzipped, _ := os.Create(filepath.Join(workdir, IMAGE_NAME))

	// create multi writer
	writer := io.MultiWriter(sha1h, sha512h, bar, bzipped)

	// and copy
	io.Copy(writer, response.Body)

	bar.FinishPrint("")

	bzipped.Close()

	if hex.EncodeToString(sha1h.Sum([]byte{})) == bz_hash_sha1 {
		fmt.Printf("SHA1 hash for %s match the one from DIGESTS\n", IMAGE_NAME)
	}
	if hex.EncodeToString(sha512h.Sum([]byte{})) == bz_hash_sha512 {
		fmt.Printf("SHA512 hash for %s match the one from DIGESTS\n", IMAGE_NAME)
	}

	fmt.Printf("Writing %s to %s...\n", IMAGE_NAME, RAW_IMAGE_NAME)

	bzip2_file, err := os.Open(filepath.Join(workdir, IMAGE_NAME))
	if err != nil {
		log.Fatal(err)
	}
	bzip2_reader := bzip2.NewReader(bzip2_file)

	bin_file, err := os.Create(DOWN_IMAGE)
	if err != nil {
		log.Fatal(err)
	}

	io.Copy(bin_file, bzip2_reader)
	bin_file.Close()

	cmd := exec.Command("vboxmanage", "convertdd", DOWN_IMAGE, VDI_IMAGE, "--format", "VDI")

	if runtime.GOOS == "windows" {
		// Use cmd /C on windows because LookPath is being difficult
		cmd = exec.Command("cmd", "/C", "vboxmanage", "convertdd", DOWN_IMAGE, VDI_IMAGE, "--format", "VDI")
	}
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	fmt.Printf("Converting %s to VirtualBox format...\n", RAW_IMAGE_NAME)
	err = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Success! CoreOS %s VDI image was created on %s.", VERSION_ID, VDI_IMAGE_NAME)
}
func TestUpload(t *testing.T) {

	setup(t)

	expires := tcclient.Time(time.Now().Add(time.Minute * 30))

	payload := GenericWorkerPayload{
		Command:    helloGoodbye(),
		MaxRunTime: 7200,
		Artifacts: []struct {
			Expires tcclient.Time `json:"expires"`
			Path    string        `json:"path"`
			Type    string        `json:"type"`
		}{
			{
				Path:    "SampleArtifacts/_/X.txt",
				Expires: expires,
				Type:    "file",
			},
		},
		Features: struct {
			ChainOfTrust bool `json:"chainOfTrust,omitempty"`
		}{
			ChainOfTrust: true,
		},
	}

	td := testTask()

	taskID, myQueue := submitTask(t, td, payload)
	runWorker()

	// some required substrings - not all, just a selection
	expectedArtifacts := map[string]struct {
		extracts        []string
		contentEncoding string
		expires         tcclient.Time
	}{
		"public/logs/live_backing.log": {
			extracts: []string{
				"hello world!",
				"goodbye world!",
				`"instance-type": "p3.enormous"`,
			},
			contentEncoding: "gzip",
			expires:         td.Expires,
		},
		"public/logs/live.log": {
			extracts: []string{
				"hello world!",
				"goodbye world!",
				"=== Task Finished ===",
				"Exit Code: 0",
			},
			contentEncoding: "gzip",
			expires:         td.Expires,
		},
		"public/logs/certified.log": {
			extracts: []string{
				"hello world!",
				"goodbye world!",
				"=== Task Finished ===",
				"Exit Code: 0",
			},
			contentEncoding: "gzip",
			expires:         td.Expires,
		},
		"public/logs/chainOfTrust.json.asc": {
			// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855  ./%%%/v/X
			// 8308d593eb56527137532595a60255a3fcfbe4b6b068e29b22d99742bad80f6f  ./_/X.txt
			// a0ed21ab50992121f08da55365da0336062205fd6e7953dbff781a7de0d625b7  ./b/c/d.jpg
			extracts: []string{
				"8308d593eb56527137532595a60255a3fcfbe4b6b068e29b22d99742bad80f6f",
			},
			contentEncoding: "gzip",
			expires:         td.Expires,
		},
		"SampleArtifacts/_/X.txt": {
			extracts: []string{
				"test artifact",
			},
			contentEncoding: "",
			expires:         payload.Artifacts[0].Expires,
		},
	}

	artifacts, err := myQueue.ListArtifacts(taskID, "0", "", "")

	if err != nil {
		t.Fatalf("Error listing artifacts: %v", err)
	}

	actualArtifacts := make(map[string]struct {
		ContentType string        `json:"contentType"`
		Expires     tcclient.Time `json:"expires"`
		Name        string        `json:"name"`
		StorageType string        `json:"storageType"`
	}, len(artifacts.Artifacts))

	for _, actualArtifact := range artifacts.Artifacts {
		actualArtifacts[actualArtifact.Name] = actualArtifact
	}

	for artifact := range expectedArtifacts {
		if a, ok := actualArtifacts[artifact]; ok {
			if a.ContentType != "text/plain; charset=utf-8" {
				t.Errorf("Artifact %s should have mime type 'text/plain; charset=utf-8' but has '%s'", artifact, a.ContentType)
			}
			if a.Expires.String() != expectedArtifacts[artifact].expires.String() {
				t.Errorf("Artifact %s should have expiry '%s' but has '%s'", artifact, expires, a.Expires)
			}
		} else {
			t.Errorf("Artifact '%s' not created", artifact)
		}
	}

	// now check content was uploaded to Amazon, and is correct

	// signer of public/logs/chainOfTrust.json.asc
	signer := &openpgp.Entity{}
	cotCert := &ChainOfTrustData{}

	for artifact, content := range expectedArtifacts {
		url, err := myQueue.GetLatestArtifact_SignedURL(taskID, artifact, 10*time.Minute)
		if err != nil {
			t.Fatalf("Error trying to fetch artifacts from Amazon...\n%s", err)
		}
		// need to do this so Content-Encoding header isn't swallowed by Go for test later on
		tr := &http.Transport{
			DisableCompression: true,
		}
		client := &http.Client{Transport: tr}
		rawResp, _, err := httpbackoff.ClientGet(client, url.String())
		if err != nil {
			t.Fatalf("Error trying to fetch decompressed artifact from signed URL %s ...\n%s", url.String(), err)
		}
		resp, _, err := httpbackoff.Get(url.String())
		if err != nil {
			t.Fatalf("Error trying to fetch artifact from signed URL %s ...\n%s", url.String(), err)
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("Error trying to read response body of artifact from signed URL %s ...\n%s", url.String(), err)
		}
		for _, requiredSubstring := range content.extracts {
			if strings.Index(string(b), requiredSubstring) < 0 {
				t.Errorf("Artifact '%s': Could not find substring %q in '%s'", artifact, requiredSubstring, string(b))
			}
		}
		if actualContentEncoding := rawResp.Header.Get("Content-Encoding"); actualContentEncoding != content.contentEncoding {
			t.Fatalf("Expected Content-Encoding %q but got Content-Encoding %q for artifact %q from url %v", content.contentEncoding, actualContentEncoding, artifact, url)
		}
		if actualContentType := resp.Header.Get("Content-Type"); actualContentType != "text/plain; charset=utf-8" {
			t.Fatalf("Content-Type in Signed URL response does not match Content-Type of artifact")
		}
		// check openpgp signature is valid
		if artifact == "public/logs/chainOfTrust.json.asc" {
			pubKey, err := os.Open(filepath.Join("testdata", "public-openpgp-key"))
			if err != nil {
				t.Fatalf("Error opening public key file")
			}
			defer pubKey.Close()
			entityList, err := openpgp.ReadArmoredKeyRing(pubKey)
			if err != nil {
				t.Fatalf("Error decoding public key file")
			}
			block, _ := clearsign.Decode(b)
			signer, err = openpgp.CheckDetachedSignature(entityList, bytes.NewBuffer(block.Bytes), block.ArmoredSignature.Body)
			if err != nil {
				t.Fatalf("Not able to validate openpgp signature of public/logs/chainOfTrust.json.asc")
			}
			err = json.Unmarshal(block.Plaintext, cotCert)
			if err != nil {
				t.Fatalf("Could not interpret public/logs/chainOfTrust.json as json")
			}
		}
	}
	if signer == nil {
		t.Fatalf("Signer of public/logs/chainOfTrust.json.asc could not be established (is nil)")
	}
	if signer.Identities["Generic-Worker <*****@*****.**>"] == nil {
		t.Fatalf("Did not get correct signer identity in public/logs/chainOfTrust.json.asc - %#v", signer.Identities)
	}

	// This trickery is to convert a TaskDefinitionResponse into a
	// TaskDefinitionRequest in order that we can compare. We cannot cast, so
	// need to transform to json as an intermediary step.
	b, err := json.Marshal(cotCert.Task)
	if err != nil {
		t.Fatalf("Cannot marshal task into json - %#v\n%v", cotCert.Task, err)
	}
	cotCertTaskRequest := &queue.TaskDefinitionRequest{}
	err = json.Unmarshal(b, cotCertTaskRequest)
	if err != nil {
		t.Fatalf("Cannot unmarshal json into task request - %#v\n%v", string(b), err)
	}

	// The Payload, Tags and Extra fields are raw bytes, so differences may not
	// be valid. Since we are comparing the rest, let's skip these two fields,
	// as the rest should give us good enough coverage already
	cotCertTaskRequest.Payload = nil
	cotCertTaskRequest.Tags = nil
	cotCertTaskRequest.Extra = nil
	td.Payload = nil
	td.Tags = nil
	td.Extra = nil
	if !reflect.DeepEqual(cotCertTaskRequest, td) {
		t.Fatalf("Did not get back expected task definition in chain of trust certificate:\n%#v\n ** vs **\n%#v", cotCertTaskRequest, td)
	}
	if len(cotCert.Artifacts) != 2 {
		t.Fatalf("Expected 2 artifact hashes to be listed")
	}
	if cotCert.TaskID != taskID {
		t.Fatalf("Expected taskId to be %q but was %q", taskID, cotCert.TaskID)
	}
	if cotCert.RunID != 0 {
		t.Fatalf("Expected runId to be 0 but was %v", cotCert.RunID)
	}
	if cotCert.WorkerGroup != "test-worker-group" {
		t.Fatalf("Expected workerGroup to be \"test-worker-group\" but was %q", cotCert.WorkerGroup)
	}
	if cotCert.WorkerID != "test-worker-id" {
		t.Fatalf("Expected workerGroup to be \"test-worker-id\" but was %q", cotCert.WorkerID)
	}
	if cotCert.Environment.PublicIPAddress != "12.34.56.78" {
		t.Fatalf("Expected publicIpAddress to be 12.34.56.78 but was %v", cotCert.Environment.PublicIPAddress)
	}
	if cotCert.Environment.PrivateIPAddress != "87.65.43.21" {
		t.Fatalf("Expected privateIpAddress to be 87.65.43.21 but was %v", cotCert.Environment.PrivateIPAddress)
	}
	if cotCert.Environment.InstanceID != "test-instance-id" {
		t.Fatalf("Expected instanceId to be \"test-instance-id\" but was %v", cotCert.Environment.InstanceID)
	}
	if cotCert.Environment.InstanceType != "p3.enormous" {
		t.Fatalf("Expected instanceType to be \"p3.enormous\" but was %v", cotCert.Environment.InstanceType)
	}
	if cotCert.Environment.Region != "outer-space" {
		t.Fatalf("Expected region to be \"outer-space\" but was %v", cotCert.Environment.Region)
	}
}