Skip to content

Commit

Permalink
HMAC implementation
Browse files Browse the repository at this point in the history
re #7
  • Loading branch information
Richard Kettlewell authored and Richard Kettlewell committed Aug 3, 2018
1 parent cfa51e0 commit 77286f7
Show file tree
Hide file tree
Showing 4 changed files with 470 additions and 29 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ The configuration looks like this:
"Pin" : "password"
}

(At time of writing) OAEP is only partial, so expect test skips.
(At time of writing) OAEP is only partial and HMAC is unsupported, so expect test skips.

Limitations
===========
Expand Down
174 changes: 174 additions & 0 deletions hmac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2018 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 crypto11

import (
"context"
"fmt"
"github.com/miekg/pkcs11"
"github.com/youtube/vitess/go/pools"
"hash"
)

const (
NFCK_VENDOR_NCIPHER = 0xde436972
CKM_NCIPHER = (pkcs11.CKM_VENDOR_DEFINED | NFCK_VENDOR_NCIPHER)

// CKM_NC_MD5_HMAC_KEY_GEN is the nShield-specific HMACMD5 key-generation mechanism
CKM_NC_MD5_HMAC_KEY_GEN = (CKM_NCIPHER + 0x6)

// CKM_NC_SHA_1_HMAC_KEY_GEN is the nShield-specific HMACSHA1 key-generation mechanism
CKM_NC_SHA_1_HMAC_KEY_GEN = (CKM_NCIPHER + 0x3)

// CKM_NC_SHA224_HMAC_KEY_GEN is the nShield-specific HMACSHA224 key-generation mechanism
CKM_NC_SHA224_HMAC_KEY_GEN = (CKM_NCIPHER + 0x24)

// CKM_NC_SHA256_HMAC_KEY_GEN is the nShield-specific HMACSHA256 key-generation mechanism
CKM_NC_SHA256_HMAC_KEY_GEN = (CKM_NCIPHER + 0x25)

// CKM_NC_SHA384_HMAC_KEY_GEN is the nShield-specific HMACSHA384 key-generation mechanism
CKM_NC_SHA384_HMAC_KEY_GEN = (CKM_NCIPHER + 0x26)

// CKM_NC_SHA512_HMAC_KEY_GEN is the nShield-specific HMACSHA512 key-generation mechanism
CKM_NC_SHA512_HMAC_KEY_GEN = (CKM_NCIPHER + 0x27)
)

type hmacImplementation struct {
// PKCS#11 session to use
session *PKCS11Session

size int

blockSize int

// Cleanup function
cleanup func()
}

type hmacInfo struct {
size int
blockSize int
general bool
}

var hmacInfos = map[int]*hmacInfo{
pkcs11.CKM_MD5_HMAC: {20, 64, false},

This comment has been minimized.

Copy link
@nickrmc83

nickrmc83 Aug 3, 2018

Contributor

I don't think we should encourage or support the use of MD5-based HMAC and so I'd be very tempted to remove it as a mechanism.

This comment has been minimized.

Copy link
@optnfast

optnfast Aug 3, 2018

Contributor

I wondered about that. I feel it's the HSM's job to enforce policy rather than the shim layer's. But we could be stricter, yes.

(NB that HMAC is not dependent on collision resistance, so the well-known problem with MD5 isn't a direct issue here. But I do sympathise with the desire to bury it anyway.)

pkcs11.CKM_MD5_HMAC_GENERAL: {20, 64, true},
pkcs11.CKM_SHA_1_HMAC: {20, 64, false},
pkcs11.CKM_SHA_1_HMAC_GENERAL: {20, 64, true},
pkcs11.CKM_SHA224_HMAC: {28, 64, false},
pkcs11.CKM_SHA224_HMAC_GENERAL: {28, 64, true},
pkcs11.CKM_SHA256_HMAC: {32, 64, false},
pkcs11.CKM_SHA256_HMAC_GENERAL: {32, 64, true},
pkcs11.CKM_SHA384_HMAC: {48, 64, false},
pkcs11.CKM_SHA384_HMAC_GENERAL: {48, 64, true},
pkcs11.CKM_SHA512_HMAC: {64, 128, false},
pkcs11.CKM_SHA512_HMAC_GENERAL: {64, 128, true},
pkcs11.CKM_SHA512_224_HMAC: {28, 128, false},
pkcs11.CKM_SHA512_224_HMAC_GENERAL: {28, 128, true},
pkcs11.CKM_SHA512_256_HMAC: {32, 128, false},
pkcs11.CKM_SHA512_256_HMAC_GENERAL: {32, 128, true},
pkcs11.CKM_RIPEMD160_HMAC: {20, 64, false},
pkcs11.CKM_RIPEMD160_HMAC_GENERAL: {20, 64, true},
}

// NewHMAC returns a new HMAC hash using the given PKCS#11 mechanism
// and key.
// length specifies the output size, for _GENERAL mechanisms.
//
// If the mechanism is not in the built-in list of known mechanisms then the
// Size() function will return whatever length was, even if it is wrong.
// BlockSize() will always return 0 in this case.
//
// The Reset() method is not implemented, and Sum() may only be called once.
// The former limitation may be lifted in future but the latter is fundamental
// and will not change.
func (key *PKCS11SecretKey) NewHMAC(mech int, length int) (h hash.Hash, err error) {
// TODO refactor with newBlockModeCloser
sessionPool := pool.Get(key.Slot)
if sessionPool == nil {
err = fmt.Errorf("crypto11: no session for slot %d", key.Slot)
return
}
ctx, cancel := context.WithTimeout(context.Background(), newSessionTimeout)
defer cancel()
var session pools.Resource
if session, err = sessionPool.Get(ctx); err != nil {
return
}
var hi hmacImplementation
hi.session = session.(*PKCS11Session)
hi.cleanup = func() {
sessionPool.Put(session)
hi.session = nil
}
var params []byte
if info, ok := hmacInfos[mech]; ok {
hi.blockSize = info.blockSize
if info.general {
hi.size = length
params = ulongToBytes(uint(length))
} else {
hi.size = info.size
}
} else {
hi.size = length
}
mechDescription := []*pkcs11.Mechanism{pkcs11.NewMechanism(uint(mech), params)}
if err = hi.session.Ctx.SignInit(hi.session.Handle, mechDescription, key.Handle); err != nil {
hi.cleanup()
return
}
h = &hi
return
}

func (hi *hmacImplementation) Write(p []byte) (n int, err error) {
if err = hi.session.Ctx.SignUpdate(hi.session.Handle, p); err != nil {
return
}
n = len(p)
return
}

func (hi *hmacImplementation) Sum(b []byte) []byte {
var result []byte
var err error
result, err = hi.session.Ctx.SignFinal(hi.session.Handle)

This comment has been minimized.

Copy link
@nickrmc83

nickrmc83 Aug 3, 2018

Contributor

Why don't we cache the result of Sum so future callers can call it again with the caveat that Write returns an error once Sum has been called.

This comment has been minimized.

Copy link
@optnfast

optnfast Aug 3, 2018

Contributor

Good idea.
Another thing we should do is support Reset() (i.e. and start from the top - new session, new C_SignInit). That'll probably be next week.

hi.cleanup()
if err != nil {
panic(err)
}
return append(b, result...)
}

func (hi *hmacImplementation) Reset() {
panic("Reset not implemented")
}

func (hi *hmacImplementation) Size() int {
return hi.size
}

func (hi *hmacImplementation) BlockSize() int {
return hi.blockSize
}
105 changes: 105 additions & 0 deletions hmac_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2018 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 crypto11

import (
"bytes"
"github.com/miekg/pkcs11"
"hash"
"testing"
)

func TestHmac(t *testing.T) {
ConfigureFromFile("config")
var info pkcs11.Info
var err error
if info, err = libHandle.GetInfo(); err != nil {
t.Errorf("GetInfo: %v", err)
return
}
if info.ManufacturerID == "SoftHSM" {
t.Skipf("HMAC not implemented on SoftHSM")
}
t.Run("HMACSHA1", func(t *testing.T) {
testHmac(t, pkcs11.CKK_SHA_1_HMAC, pkcs11.CKM_SHA_1_HMAC, 0, 20)
})
t.Run("HMACSHA1General", func(t *testing.T) {
testHmac(t, pkcs11.CKK_SHA_1_HMAC, pkcs11.CKM_SHA_1_HMAC_GENERAL, 10, 10)
})
t.Run("HMACSHA256", func(t *testing.T) {
testHmac(t, pkcs11.CKK_SHA256_HMAC, pkcs11.CKM_SHA256_HMAC, 0, 32)
})
Close()
}

func testHmac(t *testing.T, keytype int, mech int, length int, xlength int) {
var err error
var key *PKCS11SecretKey
var id []byte
t.Run("Generate", func(t *testing.T) {
if key, err = GenerateSecretKey(256, Ciphers[keytype]); err != nil {
t.Errorf("crypto11.GenerateSecretKey: %v", err)
return
}
if key == nil {
t.Errorf("crypto11.GenerateSecretKey: returned nil but no error")
return
}
if id, _, err = key.Identify(); err != nil {
t.Errorf("crypto11.PKCS11SecretKey.Identify: %v", err)
return
}
})
if key == nil {
return
}
t.Run("Short", func(t *testing.T) {
input := []byte("a short string")
var h1, h2 hash.Hash
if h1, err = key.NewHMAC(mech, length); err != nil {
t.Errorf("key.NewHMAC: %v", err)
return
}
if n, err := h1.Write(input); err != nil || n != len(input) {
t.Errorf("h1.Write: %v/%d", err, n)
return
}
r1 := h1.Sum([]byte{})
if h2, err = key.NewHMAC(mech, length); err != nil {
t.Errorf("key.NewHMAC: %v", err)
return
}
if n, err := h2.Write(input); err != nil || n != len(input) {
t.Errorf("h2.Write: %v/%d", err, n)
return
}
r2 := h2.Sum([]byte{})
if bytes.Compare(r1, r2) != 0 {
t.Errorf("h1/h2 inconsistent")
return
}
if len(r1) != xlength {
t.Errorf("r1 wrong length (want %v got %v)", xlength, len(r1))
return
}
})
}
Loading

0 comments on commit 77286f7

Please sign in to comment.