ncryptf

package module
v0.0.0-...-2fc41c6 Latest Latest
Warning

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

Go to latest
Published: Mar 20, 2019 License: BSD-3-Clause Imports: 15 Imported by: 0

README

ncryptf Go

TravisCI Go Report Card License

ncryptf logo

A library for facilitating hashed based KDF signature authentication, and end-to-end encrypted communication with compatible API's.

Installing

  1. Enable Go11 Modules by setting the GO111MODULE environment variable
GO111MODULE=on
  1. Install normally
go get github.com/ncryptf/ncryptf-go

Testing

go test ./...

Documentation

HMAC+HKDF Authentication

HMAC+HKDF Authentication is an Authentication method that allows ensures the request is not tampered with in transit. This provides resiliance not only against network layer manipulation, but also man-in-the-middle attacks.

At a high level, an HMAC signature is created based upon the raw request body, the HTTP method, the URI (with query parameters, if present), and the current date. In addition to ensuring the request cannot be manipulated in transit, it also ensures that the request is timeboxed, effectively preventing replay attacks.

The library itself is made available by importing the following struct:

Supporting API's will return the following payload containing at minimum the following information.

{
    "access_token": "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    "refresh_token": "MA2JX5FXWS57DHW4OIHHQDCJVGS3ZKKFCL7XM4GNOB567I6ER4LQ",
    "ikm": "bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM=",
    "signing": "7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw==",
    "expires_at": 1472678411
}

After extracting the elements, we can create signed request by doing the following:

import "github.com/ncryptf/ncryptf-go"
import "encoding/base64"

accessToken := "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA"
refreshToken := "MA2JX5FXWS57DHW4OIHHQDCJVGS3ZKKFCL7XM4GNOB567I6ER4LQ"
ikm := base64.StdEncoding.DecodeFromString("bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM=")
ikm := base64.StdEncoding.DecodeFromString("7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw==")
expiresAt := 1472678411

token, err := NewToken(accessToken, refreshToken, ikm, signing, expiresAt)
if err != nil {
    auth, err := NewAuthorization("POST", "/api/v1/test", token, time.Now(), "{\"foo\":\"bar\"}", nil, nil)

    if err != nil {
        header := auth.GetHeader()
    }
    // Handle authorization failure
}

// Handle token parsing failure

Note that the date property should be pre-offset when calling Authorization to prevent time skewing.

The payload parameter should be a JSON serializable string.

Version 2 HMAC Header

The Version 2 HMAC header, for API's that support it can be retrieved by calling:

header := auth.GetHeader()
Version 1 HMAC Header

For API's using version 1 of the HMAC header, call Authorization with the optional version parameter set to 1 for the 6th parameter.

 auth, err := NewAuthorization("POST", "/api/v1/test", token, time.Now(), "{\"foo\":\"bar\"}", 1, nil)

if err != nil {
    header := auth.GetHeader()
}

This string can be used in the Authorization Header

Date Header

The Version 1 HMAC header requires an additional X-Date header. The X-Date header can be retrieved by calling auth.GetDateString()

Encrypted Requests & Responses

This library enables clients to establish and trusted encrypted session on top of a TLS layer, while simultaniously (and independently) providing the ability authenticate and identify a client via HMAC+HKDF style authentication.

The rationale for this functionality includes but is not limited to:

  1. Necessity for extra layer of security
  2. Lack of trust in the network or TLS itself (see https://blog.cloudflare.com/incident-report-on-memory-leak-caused-by-cloudflare-parser-bug/)
  3. Need to ensure confidentiality of the Initial Key Material (IKM) provided by the server for HMAC+HKDF authentication
  4. Need to ensure confidentiality of user submitted credentials to the API for authentication

The primary reason you may want to establish an encrypted session with the API itself is to ensure confidentiality of the IKM to prevent data leakages over untrusted networks to avoid information being exposed in a Cloudflare like incident (or any man-in-the-middle attack). Encrypted sessions enable you to utilize a service like Cloudflare should a memory leak occur again with confidence that the IKM and other secure data would not be exposed.

Encryption Keys

Encryption uses a sodium crypto box. A keypair can be generated as follows when using lazy-sodium.

import "ncryptf"

func GetKeypair() *Keypair {
    return ncryptf.GenerateKeypair()
}
Signing Keys

Encryption uses a sodium signature. A keypair for signing can be generated as follows using lazy-sodium:

import "ncryptf"

func GenerateSigningKeypair() *Keypair {
    return ncryptf.GenerateKeypair()
}
Encrypted Request Body

Payloads can be encrypted as follows:

import "github.com/ncryptf/ncryptf-go"
import "encoding/base64"

payload := "{\"foo\":\"bar\"}"

// Generate your signing keypair or use your own
kp := GenerateKeypair()
sk := GenerateSigningKeypair()

// Mock for a remote public key
rk := GenerateKeypair()

req, err := NewRequest(kp.GetSecretKey(), sk.GetSecretKey())
if err != nil {
    // Error when generating the request struct
}

cipher, err := request.Encrypt(payload, rk.GetPublicKey())
if err != nil {
    // Error encrypting the message
}

message := base64.StdEncoding.EncodeToString(cipher)

// Do your http request with message here

Note that you need to have a pre-bootstrapped public key to encrypt data. For the v1 API, this is typically this is returned by /api/v1/server/otk.

Decrypting Responses

Responses from the server can be decrypted as follows:

import "github.com/ncryptf/ncryptf-go"
import "encoding/base64"

// Assuming http.Client is doing your http request
resp, err := client.Do(req)

defer resp.Body.Close()

bodyBytes, err := ioutil.ReadAll(resp.Body)
bodyString := string(bodyBytes)
responseBody, err := base64.StdEncoding.DecodeString(bodyString)

response, err := NewResponse(kp.GetSecretKey())
message, err := response.Decrypt(responseBody)

// Message contains your decrypted string
V2 Encrypted Payload

Verison 2 works identical to the version 1 payload, with the exception that all components needed to decrypt the message are bundled within the payload itself, rather than broken out into separate headers. This alleviates developer concerns with needing to manage multiple headers.

The version 2 payload is described as follows. Each component is concatanated together.

Segment Length
4 byte header DE259002 in binary format 4 BYTES
Nonce 24 BYTES
The public key associated to the private key 32 BYTES
Encrypted Body X BYTES
Signature Public Key 32 BYTES
Signature or raw request body 64 BYTES
Checksum of prior elements concatonated together 64 BYTES

Documentation

Index

Constants

View Source
const AuthInfo = "HMAC|AuthenticationKey"

AuthInfo INFO parameter for HMAC

Variables

View Source
var (
	// ErrKeypairSecretKeySize an error thrown when the secret key size is invalid
	ErrKeypairSecretKeySize = errors.New("ncryptf: Secret key should be a multiple of 16 bytes")

	// ErrKeypairPublicKeySize an error thrown when the public key size is invalid
	ErrKeypairPublicKeySize = errors.New("ncryptf: Public key should be a multiple of 4 bytes")
)
View Source
var (
	// ErrRequestSign an error for when signing fails
	ErrRequestSign = errors.New("Unable to sign request")

	// ErrRequestSecretKeyLength an error for when the secret key length is not correct
	ErrRequestSecretKeyLength = fmt.Errorf("Secret key should be %d bytes", C.crypto_box_SECRETKEYBYTES)

	// ErrRequestSignatureKeyLength an error for when the signature key length is not correct
	ErrRequestSignatureKeyLength = fmt.Errorf("Signature key should be %d bytes", C.crypto_sign_SECRETKEYBYTES)

	// ErrRequestPublicKeyLength an error for when the public key length is not correct
	ErrRequestPublicKeyLength = fmt.Errorf("Public key should be %d bytes", C.crypto_box_PUBLICKEYBYTES)

	// ErrRequestNonceLength an error when the nonce isn't the correct length
	ErrRequestNonceLength = fmt.Errorf("Nonce should be %d bytes", C.crypto_box_NONCEBYTES)

	// ErrRequestEncyptionFailed an error when encryption fails
	ErrRequestEncyptionFailed = errors.New("An error occured when encrypting the data")
)
View Source
var (
	// ErrResponseSecretKeyLength an error for when the secret key length is invalid
	ErrResponseSecretKeyLength = fmt.Errorf("Secret key should be %d bytes", C.crypto_box_SECRETKEYBYTES)

	// ErrResponseMACLength an error when the message length is invalid
	ErrResponseMACLength = fmt.Errorf("Message should be longer than %d bytes", C.crypto_box_MACBYTES)

	// ErrResponseNotSuitableForPublicKeyExtraction an error for when the public key cannot be extracted from the response
	ErrResponseNotSuitableForPublicKeyExtraction = errors.New("The response provided is not suitable for public key extraction")

	// ErrResponseMessageLength an error when the response message length is invalid
	ErrResponseMessageLength = errors.New("The response message is too short")

	// ErrRresponseSignatureLength an error when the signature length is invalid
	ErrRresponseSignatureLength = fmt.Errorf("Signature should be %d bytes", 64)

	// ErrResponsePublicKeyLength an error when the public key length is invalid
	ErrResponsePublicKeyLength = fmt.Errorf("Public key should be %d bytes", C.crypto_sign_PUBLICKEYBYTES)

	// ErrResponseSignatureVerification an error when signature verification fails
	ErrResponseSignatureVerification = errors.New("Signature verification failed")

	// ErrResponseNonceLength an error when the nonce length is invalid
	ErrResponseNonceLength = fmt.Errorf("Nonce should be %d bytes", C.crypto_box_NONCEBYTES)

	// ErrResponseDecryptionFailed an error when decryption failed
	ErrResponseDecryptionFailed = errors.New("Unable to decrypt message")

	// ErrResponseInvalidChecksum an error when the checksum associated with a message is invalid
	ErrResponseInvalidChecksum = errors.New("The checksum associated with the message is not valid")
)
View Source
var (
	// ErrTokenIKMSize an error when the IKM size not 32 bytes
	ErrTokenIKMSize = errors.New("Initial key material should be 32 bytes")

	// ErrTokenSignatureSize an error when the signature secret key is not 64 bytes
	ErrTokenSignatureSize = errors.New("Signature secret key should be 64 bytes")
)
View Source
var (
	// ErrAuthorizationKeySize an error when the key cannot be extracted
	ErrAuthorizationKeySize = errors.New("Unable to extract key material")
)

Functions

func Derive

func Derive(httpMethod string, uri string, salt []byte, date time.Time, payload string, version int) string

Derive derives the signature for a given version

func GetPublicKeyFromResponse

func GetPublicKeyFromResponse(response []byte) ([]byte, error)

GetPublicKeyFromResponse Returns the public key from a v2 response

func GetSigningPublicKeyFromResponse

func GetSigningPublicKeyFromResponse(response []byte) ([]byte, error)

GetSigningPublicKeyFromResponse Extracts the siging public key from a v3 response

func GetVersion

func GetVersion(response []byte) (int, error)

GetVersion returns the version associated with a given message

func IsSignatureValid

func IsSignatureValid(response string, signature []byte, publicKey []byte) (bool, error)

IsSignatureValid returns true if the detached signature associated to the message is valid or not

func Zero

func Zero(data sodium.Bytes) bool

Zero zeroes data on a given byte array

Types

type Authorization

type Authorization struct {
	// contains filtered or unexported fields
}

Authorization struct

func NewAuthorization

func NewAuthorization(httpMethod string, uri string, token Token, date time.Time, payload string, version int, salt []byte) (*Authorization, error)

NewAuthorization generates a new Authorization struct from the provided data

func (*Authorization) GetDate

func (a *Authorization) GetDate() time.Time

GetDate returns the authorization date

func (*Authorization) GetDateString

func (a *Authorization) GetDateString() string

GetDateString returns the formatted date string

func (*Authorization) GetEncodedHMAC

func (a *Authorization) GetEncodedHMAC() string

GetEncodedHMAC returns the base64 encoded HMAC

func (*Authorization) GetEncodedSalt

func (a *Authorization) GetEncodedSalt() string

GetEncodedSalt returns the base64 encoded salt

func (*Authorization) GetHMAC

func (a *Authorization) GetHMAC() []byte

GetHMAC returns the HMAC byte array

func (*Authorization) GetHeader

func (a *Authorization) GetHeader() string

GetHeader returns the formatted header

func (*Authorization) GetSignatureString

func (a *Authorization) GetSignatureString() string

GetSignatureString returns the generated signature string

func (*Authorization) Verify

func (a *Authorization) Verify(hmac []byte, auth Authorization, driftAllowance int) bool

Verify returns true if the provided hmac, authorixzation, and drift allowance is acceptable

type Keypair

type Keypair struct {
	// contains filtered or unexported fields
}

Keypair structure

func GenerateKeypair

func GenerateKeypair() *Keypair

GenerateKeypair generates a crypto box keypair (32 byte secret, 32 byte public)

func GenerateSigningKeypair

func GenerateSigningKeypair() *Keypair

GenerateSigningKeypair generates a crypto sign keypair (64 byte secret, 32 byte public)

func NewKeypair

func NewKeypair(secretKey []byte, publicKey []byte) (*Keypair, error)

NewKeypair function to create a new Keypair

func (*Keypair) GetPublicKey

func (k *Keypair) GetPublicKey() []byte

GetPublicKey returns the public component of the keypair

func (*Keypair) GetSecretKey

func (k *Keypair) GetSecretKey() []byte

GetSecretKey returns the secret component of the keypair

type Request

type Request struct {
	// contains filtered or unexported fields
}

Request struct

func NewRequest

func NewRequest(secretKey []byte, signatureSecretKey []byte) (*Request, error)

NewRequest returns a new request instance

func (*Request) Encrypt

func (r *Request) Encrypt(data string, publicKey []byte) ([]byte, error)

Encrypt a data string with a given public key using v2 and a generated nonce

func (*Request) EncryptWithNonce

func (r *Request) EncryptWithNonce(data string, publicKey []byte, version int, nonce []byte) ([]byte, error)

EncryptWithNonce encrypts a data string with a given public key, and a specified nonce and version

func (*Request) EncryptWithVersion

func (r *Request) EncryptWithVersion(data string, publicKey []byte, version int) ([]byte, error)

EncryptWithVersion encrypts a data string with a given public key, a generated nonce, and a specified version

func (*Request) GetNonce

func (r *Request) GetNonce() []byte

GetNonce returns a 24 byte nonce

func (*Request) Sign

func (r *Request) Sign(data string) ([]byte, error)

Sign signs the data

type Response

type Response struct {
	// contains filtered or unexported fields
}

Response structure for response instance

func NewResponse

func NewResponse(secretKey []byte) (*Response, error)

NewResponse returns a new response object or error

func (*Response) Decrypt

func (r *Response) Decrypt(response []byte) (string, error)

Decrypt decrypts a v2 message with an embedded public key

func (*Response) DecryptWithPublicKey

func (r *Response) DecryptWithPublicKey(response []byte, publicKey []byte) (string, error)

DecryptWithPublicKey decrypts a response with a given public key. Used for v1 signatures

func (*Response) DecryptWithPublicKeyAndNonce

func (r *Response) DecryptWithPublicKeyAndNonce(response []byte, publicKey []byte, nonce []byte) (string, error)

DecryptWithPublicKeyAndNonce decrypts a message with a public key and nonce

type Token

type Token struct {
	AccessToken  string
	RefreshToken string
	IKM          []byte
	Signature    []byte
	ExpiresAt    int64
}

Token structure

func NewToken

func NewToken(accessToken string, refreshToken string, ikm []byte, signature []byte, expiresAt int64) (*Token, error)

NewToken creates a token struct

func (*Token) GetSignaturePublicKey

func (t *Token) GetSignaturePublicKey() ([]byte, error)

GetSignaturePublicKey retrieves the signature public key from the private componentz

func (*Token) IsExpired

func (t *Token) IsExpired() bool

IsExpired returns true if the token is expired, and false otherwise

Jump to

Keyboard shortcuts

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