Skip to content

Commit

Permalink
Initial commit of gose code
Browse files Browse the repository at this point in the history
gose contains implementations of various JSON Web cryptographic
specifications including JSON Web Token (JWT), JSON Web Key (JWK)
and JSON Web Encryption (JWE).

This package is designed to be correct, simple and extensible.
Extensions can be made by implementing the rich interfaces
exposed to allow simple signing and encryption using Hardware Security
Modules (HSM).

Signed-off-by: Nick A. Smith <[email protected]>
  • Loading branch information
Nick A. Smith committed Nov 7, 2019
0 parents commit 5f0d00d
Show file tree
Hide file tree
Showing 50 changed files with 8,540 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.idea/
22 changes: 22 additions & 0 deletions LICENCE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License.

Copyright 2019 Thales e-Security, Inc

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.PHONY: all clean lint vet coverage
GOCMD:=go
GOCLEAN:=$(GOCMD) clean
GOTEST:=$(GOCMD) test
GOGET:=$(GOCMD) get
GOVET:=$(GOCMD) vet
GOLINT:=golint
SRCS:=$(wildcard *.go) $(wildcard jose/*.go)

all: clean lint vet coverage

clean:
$(GOCLEAN)
rm -f coverage.out

lint:
$(GOLINT) -set_exit_status ./...

vet:
$(GOVET) ./...

test:
$(GOTEST) -gcflags=-l -short -race ./...

coverage: coverage.out

coverage.out: $(SRCS)
$(GOTEST) -gcflags=-l -coverprofile coverage.out ./...
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# GOSE - JOSE and friends for the Go developer

## Overview

GOSE is JOSE/JWT/JWK/JWS/JWKS implemented in Go with Helpers, and examples.

It contains implementations of the JOSE suite of types and helpers for many different use cases.


## Mission

- Simple
- Compliant
- Safe
- Efficient
- Extensible

## Examples

Examples are provided under the `/examples` folder to illustrate correct use of this package.




131 changes: 131 additions & 0 deletions aes_gcm_cryptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2019 Thales e-Security, Inc
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package gose

import (
"crypto/cipher"
"crypto/rand"
"io"

"github.com/thalesignite/gose/jose"
)

var validEncryptionOpts = []jose.KeyOps{jose.KeyOpsEncrypt}
var validDecryptionOpts = []jose.KeyOps{jose.KeyOpsDecrypt}
var validCryptorOpts = []jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt}

// AesGcmCryptor provides AES GCM encryption and decryption functions.
type AesGcmCryptor struct {
kid string
alg jose.Alg
aead cipher.AEAD
opts []jose.KeyOps
rng io.Reader
}

// Kid the key identity
func (cryptor *AesGcmCryptor) Kid() string {
return cryptor.kid
}

// Algorithm the supported algorithm
func (cryptor *AesGcmCryptor) Algorithm() jose.Alg {
return cryptor.alg
}

// GenerateNonce generate a nonce of the correct size for use with GCM encryption/decryption from a random source.
func (cryptor *AesGcmCryptor) GenerateNonce() ([]byte, error) {
nonce := make([]byte, cryptor.aead.NonceSize())
if _, err := cryptor.rng.Read(nonce); err != nil {
return nil, err
}
return nonce, nil
}

// Open decrypt a previously encrypted ciphertext.
func (cryptor *AesGcmCryptor) Open(operation jose.KeyOps, nonce, ciphertext, aad, tag []byte) (plaintext []byte, err error) {
ops := intersection(validDecryptionOpts, cryptor.opts)
if !isSubset(ops, []jose.KeyOps{operation}) {
err = ErrInvalidOperations
return
}
dst := make([]byte, 0, len(ciphertext))
ciphertextAndTag := make([]byte, len(ciphertext)+len(tag))
_ = copy(ciphertextAndTag, ciphertext)
_ = copy(ciphertextAndTag[len(ciphertext):], tag)
if dst, err = cryptor.aead.Open(dst, nonce, ciphertextAndTag, aad); err != nil {
return
}
plaintext = dst
return
}

// Seal encrypt a supplied plaintext and AAD.
func (cryptor *AesGcmCryptor) Seal(operation jose.KeyOps, nonce, plaintext, aad []byte) (ciphertext, tag []byte, err error) {
ops := intersection(validEncryptionOpts, cryptor.opts)
if !isSubset(ops, []jose.KeyOps{operation}) {
err = ErrInvalidOperations
return
}
if len(nonce) != cryptor.aead.NonceSize() {
err = ErrInvalidNonce
return
}
sz := cryptor.aead.Overhead() + len(plaintext)
dst := make([]byte, 0, sz)
dst = cryptor.aead.Seal(dst, nonce, plaintext, aad)
ciphertext = dst[:len(plaintext)]
tag = dst[len(plaintext):]
return
}

// NewAesGcmCryptorFromJwk create a new instance of an AesGCmCryptor from a JWK.
func NewAesGcmCryptorFromJwk(jwk jose.Jwk, required []jose.KeyOps) (AuthenticatedEncryptionKey, error) {
/* Check jwk can be used to encrypt or decrypt */
ops := intersection(validCryptorOpts, jwk.Ops())
if len(ops) == 0 {
return nil, ErrInvalidOperations
}
/* Load the jwk */
aead, err := LoadSymmetricAEAD(jwk, required)
if err != nil {
return nil, err
}
return &AesGcmCryptor{
kid: jwk.Kid(),
alg: jwk.Alg(),
aead: aead,
rng: rand.Reader,
opts: jwk.Ops(),
}, nil
}

// NewAesGcmCryptor create a new instance of an AesGCmCryptor from the supplied parameters.
func NewAesGcmCryptor(aead cipher.AEAD, rng io.Reader, kid string, alg jose.Alg, opeartions []jose.KeyOps) (AuthenticatedEncryptionKey, error) {
return &AesGcmCryptor{
kid: kid,
alg: alg,
aead: aead,
rng: rng,
opts: opeartions,
}, nil
}
183 changes: 183 additions & 0 deletions aes_gcm_cryptor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2019 Thales e-Security, Inc
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package gose

import (
"crypto/rand"
"testing"

"github.com/thalesignite/gose/jose"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
fakeKeyMaterial = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
fakeNonce = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
fakeTag = []byte{0x70, 0x7d, 0xcf, 0x80, 0xa1, 0xb9, 0xa6, 0x17, 0x03, 0xe7, 0x95, 0xd4, 0xd1, 0x09, 0xf0, 0xfd}
fakePlaintext = []byte{0x01}
fakeCiphertext = []byte{0xcf}
)

func TestNewAesGcmCryptor_InvalidOps(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt})
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
assert.Nil(t, cryptor)
assert.Equal(t, err, ErrInvalidOperations)
}

func TestNewAesGcmCryptor_InvalidKey(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgES256)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt})
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
assert.Nil(t, cryptor)
assert.Equal(t, err, ErrInvalidKeyType)
}

func TestNewAesGcmCryptor(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
assert.NotNil(t, cryptor)
assert.NoError(t, err)
}

func TestAesGcmCryptor_GenerateNonce(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

nonce, err := cryptor.GenerateNonce()
require.NoError(t, err)
assert.Len(t, nonce, 12)
}

func TestAesGcmCryptor_Getters(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

assert.Equal(t, cryptor.Kid(), "something-unique")
assert.Equal(t, cryptor.Algorithm(), jose.AlgA256GCM)
}

func TestAesGcmCryptor_Seal_InvalidOps(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

ciphertext, tag, err := cryptor.Seal(jose.KeyOpsEncrypt, fakeNonce, fakePlaintext, nil)
assert.Nil(t, ciphertext)
assert.Nil(t, tag)
assert.Equal(t, err, ErrInvalidOperations)
}

func TestAesGcmCryptor_Seal(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsEncrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

ciphertext, tag, err := cryptor.Seal(jose.KeyOpsEncrypt, fakeNonce, fakePlaintext, nil)
assert.Len(t, ciphertext, 1)
assert.Len(t, tag, 16)
assert.NoError(t, err)
}

func TestAesGcmCryptor_Open_InvalidOps(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsEncrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

plaintext, err := cryptor.Open(jose.KeyOpsDecrypt, fakeNonce, fakeCiphertext, nil, fakeTag)
assert.Nil(t, plaintext)
assert.Equal(t, err, ErrInvalidOperations)
}

func TestAesGcmCryptor_Open(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

plaintext, err := cryptor.Open(jose.KeyOpsDecrypt, fakeNonce, fakeCiphertext, nil, fakeTag)
assert.Equal(t, plaintext, fakePlaintext)
assert.NoError(t, err)
}

func TestAesGcmCryptor_RoundTrip(t *testing.T) {
key := &jose.OctSecretKey{}
key.SetAlg(jose.AlgA256GCM)
key.SetOps([]jose.KeyOps{jose.KeyOpsEncrypt, jose.KeyOpsDecrypt})
key.SetKid("something-unique")
key.K.SetBytes(fakeKeyMaterial)
cryptor, err := NewAesGcmCryptorFromJwk(key, []jose.KeyOps{jose.KeyOpsDecrypt, jose.KeyOpsEncrypt})
require.NotNil(t, cryptor)
require.NoError(t, err)

for i := 0; i < 50; i++ {
toSeal := make([]byte, 374)
_, err = rand.Read(toSeal)
require.Nil(t, err)
nonce, err := cryptor.GenerateNonce()
require.NoError(t, err)
ciphertext, tag, err := cryptor.Seal(jose.KeyOpsEncrypt, nonce, toSeal, nil)
require.Nil(t, err)
plaintext, err := cryptor.Open(jose.KeyOpsDecrypt, nonce, ciphertext, nil, tag)
assert.NoError(t, err)
assert.Equal(t, plaintext, toSeal)
}
}
Loading

0 comments on commit 5f0d00d

Please sign in to comment.