codechallenge

package module
v0.0.0-...-edbd301 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2019 License: MIT Imports: 15 Imported by: 0

README

Short Message Signer

An employment coding challenge for Smart Edge of Irvine, CA

Go Report

Purpose:

While this is based on a real world problem, the goal of this project is to demonstrate my aptitude and potential value to Smart Edge as a future employee.

What it does:

Given a short message (250 characters or less) from standard input it will:

  • If no key pair is found (for the requested algorithm) on the filesystem:
    • Generate and save, a new public+private key pair for the specified cryptography algorithm, to the filesystem
  • Load the correct public+private key pair
  • Sign the message with the private key
  • Verify that the signature that was generated matches the public key
  • Emit the signed massage, with the signature and public key, in JSON format, to standard output.

For more details on how this tool is supposed to work, the specification document can be found here.

Getting started:

For sake of simplicity, I will assume you are working with this package from a Linux system. While Go and Docker support other sytems, for clarity, building and developing this project is most straight forward on a current Linux machine with Docker installed.

Checking out the repository

Thanks to Docker, the project should build and run in any directory, but since Go is rather pickey about where source code should live, it is strongly recommended to check this project out, at the proper place, within the $GOPATH. To checkout this project into a new $GOPATH, and build it there try this:

$  export GOPATH="$(pwd)/gopath"
$  mkdir -p gopath/src/github.com/smartedge
$  git clone https://github.com/loren-osborn/smartEdge_codeChallenge.git gopath/src/github.com/smartedge/codechallenge
$  cd gopath/src/github.com/smartedge/codechallenge
$  make

The Makefile will then (by default):

Building outside a container

To build the executable outside the container (assuming that your $GOPATH is set correctly) type:

$  make build_local

Then you can execute it with:

$  ./codechallenge
Command line options

The tool recognizes the following options:

Usage of ./codechallenge.bin:
  -help
      display this help message.
  Input format options:
      -ascii
        	This specifies that the message is ASCII content
      -binary
        	This specifies that the message is raw binary content
      -utf8
        	This specifies that the message is UTF-8 content [default]
  Algorithm options:
      -ecdsa
        	Causes the mesage to be signed with an ECDSA key-pair [default]
      -rsa
        	Causes the mesage to be signed with an RSA key-pair
      -bits uint
        	Bit length of the RSA key [default=2048]
  -private string
    	filepath of the private key file. Defaults to ~/.smartEdge/id_rsa.priv for RSA and ~/.smartEdge/id_ecdsa.priv for ECDSA.
  -public string
    	filepath of the private key file. Defaults to ~/.smartEdge/id_rsa.pub for RSA and ~/.smartEdge/id_ecdsa.pub for ECDSA.
Guided Tour:

This should help you find your way around the files in the repository:

Source Code:
  • In addition to all files ending in _test.go, all code in the testtools/ directory is used exclusively for unit testing.
  • Except for a stub entrypoint in codechallenge/main.go, all production source files are in the root directory of the project.
  • Code in the testtools/mocks/ directory help build a reusable mocked environment for unit testing.
  • Code in the testtools/ directory provide comparison and utility functions for unit testing.
Other directories in repository:
  • A bash script in the buildtools/ directory helps scrape generated godoc documentation into HTML files.
  • The testdata/ directory contains test data used for comparison. (Currently the unused valid_output_schema.json from the specification document lives here.)
Generated files:
  • The HTML godoc documentation files are stored in the godoc/ directory
  • The files coverage.out and coverage.html contain the code coverage report from go test in text and HTML format respectively.
Design Considerations

Dependency Injection: I started this project with testability as a top priority. To achieve this, I made dependency injection a core component to how the parts of this project fit together. All connections with externalities, including the random entropy source, go through the *Dependencies struct created at program invocation. This allows any of them to be easily mocked for testing purposes.

TDD: While I had to change gears due to other demands on my time, I started this project doing exclusively Test Driven Development. This allowed me to build out very solid reusable testing infrustructure to build the project in an fully testable manner. While I had to alter my development style midstream, I remain confident this code base can be brought up to 100% code coverage without modification, and modest effort.

BDD: I had intended to use this project to learn and use Ginkgo and Gomega for the production features of the codechallenge, and completed implementing a Gomega matcher for JSON schema validation for this purpose. While I'm excited to use Ginkgo with other projects at Smart Edge, I was unable to include any Ginkgo BDD tests in this project.

Stub Entrypoint: I put the actual main() entrypoint in a stub subpackage for two reasons:

  • Implementing the majority of the code outside the main package allowed go tests to easily test the distinction between public and private package members. (I did not put much effort into identifying package members that should be kept private yet.)
  • godoc refuses to produce API documentation for functions in the main package, so moving all substantive code out of the main package allowed for generation of very readable automated documentation.
Future Development

These are areas where I would improve this project given more time:

  • Easy wins (minimal effort changes)
    • Move entrypoint stub into cmd/codechallenge to avoid silly Windows-esque codechallenge.exe filename.
    • Make the "Usage" output match the output above.
    • Add a exit-status 0 -help option
    • Move dependencies.go and keys.go into deps and crypt sub-packes to aid organizational clarity of documentation. keys.go should be broken into 4 files: 1 for each of the 2 plugins, 1 for buffer types, and one for everything else.
  • Automate godoc link translation.
  • Finish filesystem mocking.
  • Testing:
    • Bring the project to 100% test coverage: Both for production code and testtools code.
    • Add BDD tests for "bullet point" features (and feature details) for feature tracability.
    • Ensure that all edge cases, and resulting behaviors are properly tested, and verified, rather than simply making sure that all code paths executed.
  • Forward Compatibility: Ensure that the project works properly in Go 1.11, with go modules, outside the $GOPATH
  • Add a mode where the signed messages could be validated. The tool already does this internally, but it would be beneficial to expose this to the user.
  • Look for more docker-friendly storage options for public and private keys than on the filesystem.
  • Refactoring directions:
    • The flag provides a fairly robust way add custom option types that would be cleaner and less confusing than the current implementation. I would suggest adding the following flag types:
      • A one of many, "mutually exclusive" flag option type (for content type and algorithm selection)
      • A conditionally allowed option (for rsa bit-length)
      • A string flag where the default can be computed dynamically, or set after flag.Parse()
    • Reorganize what functions are in which files to make them easier to find.
    • Determine what package members should be made private, and ensure they are still fully testable
    • Investigate if the RSA PSS padding needs its own sha256 hashing function. (If not, these should be consolidated.)
  • Build process: Seperate godoc generation from go test so the documentation doesn't need to be generated every time the tests don't fail.

Documentation

Overview

Package codechallenge implements a tool to sign a short text message, creating a key-pair if necessary

Index

Constants

View Source
const (
	ReplaceAll = -1
)

ReplaceAll tells strings.Replace() to replace all

View Source
const (
	UsageMessage = "  -help\n" +
		"    \tdisplay this help message.\n" +
		"  Input format options:\n" +
		"      -ascii\n" +
		"        \tThis specifies that the message is ASCII content\n" +
		"      -binary\n" +
		"        \tThis specifies that the message is raw binary content\n" +
		"      -utf8\n" +
		"        \tThis specifies that the message is UTF-8 content [default]\n" +
		"  Algorithm options:\n" +
		"      -ecdsa\n" +
		"        \tCauses the mesage to be signed with an ECDSA key-pair [default]\n" +
		"      -rsa\n" +
		"        \tCauses the mesage to be signed with an RSA key-pair\n" +
		"      -bits uint\n" +
		"        \tBit length of the RSA key [default=2048]\n" +
		"  -private string\n" +
		"    \tfilepath of the private key file. Defaults to ~/.smartEdge/id_rsa.priv for RSA and ~/.smartEdge/id_ecdsa.priv for ECDSA.\n" +
		"  -public string\n" +
		"    \tfilepath of the private key file. Defaults to ~/.smartEdge/id_rsa.pub for RSA and ~/.smartEdge/id_ecdsa.pub for ECDSA.\n"
)

UsageMessage is the message displayed when there is an error. The current implementation documents each flag twice. This should be consolidated later.

Variables

This section is empty.

Functions

func GenerateResponse

func GenerateResponse(d *deps.Dependencies, message string, sig crypt.BinarySignature, pubKey crypt.PEMEncoded) error

GenerateResponse takes the message, signature and public key and writes them in JSON format to d.Os.Stdout

func HandleError

func HandleError(d *deps.Dependencies, err error, exitStatus int)

HandleError displays an error message with Usage information to Stderr, and exits with an error code.

func InjestMessage

func InjestMessage(dataSource io.Reader, format ContentFormat) (string, error)

InjestMessage reads all data from dataSource, removing any trailing whitespace. Returns an error if the content is longer than 250 characters. Input is allowed to be ASCII, Binary or UTF-8: ASCII and Binary data have a 250 byte limit, while UTF-8 has a 250 character limit with up to 4 bytes per character. ASCII and UTF-8 inputs are both trimmed of trailing whitespace.

func RealMain

func RealMain(d *deps.Dependencies)

RealMain is the program entry-point with all dependencies injected. This allows us to test respecting public vs private methods by moving it outside the "main" package.

Types

type ContentFormat

type ContentFormat int

ContentFormat the data format of the message to be signed

const (
	UTF8 ContentFormat = iota
	ASCII
	Binary
)

Content character encodings

type RunConfig

type RunConfig struct {
	HelpMode       bool
	Format         ContentFormat
	PubKeySettings crypt.PkiSettings
}

RunConfig program's running config as specified on the command line.

func ParseArgs

func ParseArgs(d *deps.Dependencies) (*RunConfig, error)

ParseArgs parses the runtime configuration from the command line arguments.

type SignedMessage

type SignedMessage struct {
	Message   string `json:"message"`
	Signature string `json:"signature"`
	Pubkey    string `json:"pubkey"`
}

SignedMessage the final response to be rendered to JSON.

Directories

Path Synopsis
cmd
codechallenge
Package codechallenge is a stub to call into the main body of the program.
Package codechallenge is a stub to call into the main body of the program.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL