Skip to content

Commit

Permalink
Merge pull request #53 from ThalesIgnite/bernard-wagner-certificates
Browse files Browse the repository at this point in the history
Support importing and finding certificates
  • Loading branch information
Nick Smith authored Aug 9, 2019
2 parents 7dcb9d9 + 57f46c7 commit 8367a3e
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This is an implementation of the standard Golang crypto interfaces that
uses [PKCS#11](http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/pkcs11-base-v2.40-errata01-os-complete.html) as a backend. The supported features are:

* Generation and retrieval of RSA, DSA and ECDSA keys.
* Importing and retrieval of x509 certificates
* PKCS#1 v1.5 signing.
* PKCS#1 PSS signing.
* PKCS#1 v1.5 decryption
Expand Down
172 changes: 172 additions & 0 deletions certificates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// 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 crypto11

import (
"crypto/x509"
"encoding/asn1"
"math/big"

"github.com/miekg/pkcs11"
"github.com/pkg/errors"
)

// FindCertificate retrieves a previously imported certificate. Any combination of id, label
// and serial can be provided. An error is return if all are nil.
func (c *Context) FindCertificate(id []byte, label []byte, serial *big.Int) (*x509.Certificate, error) {

if c.closed.Get() {
return nil, errClosed
}

var cert *x509.Certificate
err := c.withSession(func(session *pkcs11Session) (err error) {
if id == nil && label == nil && serial == nil {
return errors.New("id, label and serial cannot all be nil")
}

var template []*pkcs11.Attribute

if id != nil {
template = append(template, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
}
if label != nil {
template = append(template, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
}
if serial != nil {
derSerial, err := asn1.Marshal(serial)
if err != nil {
return errors.WithMessage(err, "failed to encode serial")
}

template = append(template, pkcs11.NewAttribute(pkcs11.CKA_SERIAL_NUMBER, derSerial))
}

template = append(template, pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE))

if err = session.ctx.FindObjectsInit(session.handle, template); err != nil {
return err
}
defer func() {
finalErr := session.ctx.FindObjectsFinal(session.handle)
if err == nil {
err = finalErr
}
}()

handles, _, err := session.ctx.FindObjects(session.handle, 1)
if err != nil {
return err
}
if len(handles) == 0 {
return nil
}

attributes := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_VALUE, 0),
}

if attributes, err = session.ctx.GetAttributeValue(session.handle, handles[0], attributes); err != nil {
return err
}

cert, err = x509.ParseCertificate(attributes[0].Value)
return err
})

return cert, err
}

// ImportCertificate imports a certificate onto the token. The id parameter is used to
// set CKA_ID and must be non-nil.
func (c *Context) ImportCertificate(id []byte, certificate *x509.Certificate) error {
if c.closed.Get() {
return errClosed
}

if err := notNilBytes(id, "id"); err != nil {
return err
}

template, err := NewAttributeSetWithID(id)
if err != nil {
return err
}
return c.ImportCertificateWithAttributes(template, certificate)
}

// ImportCertificateWithLabel imports a certificate onto the token. The id and label parameters are used to
// set CKA_ID and CKA_LABEL respectively and must be non-nil.
func (c *Context) ImportCertificateWithLabel(id []byte, label []byte, certificate *x509.Certificate) error {
if c.closed.Get() {
return errClosed
}

if err := notNilBytes(id, "id"); err != nil {
return err
}
if err := notNilBytes(label, "label"); err != nil {
return err
}

template, err := NewAttributeSetWithIDAndLabel(id, label)
if err != nil {
return err
}
return c.ImportCertificateWithAttributes(template, certificate)
}

// ImportCertificateWithAttributes imports a certificate onto the token. After this function returns, template
// will contain the attributes applied to the certificate. If required attributes are missing, they will be set to a
// default value.
func (c *Context) ImportCertificateWithAttributes(template AttributeSet, certificate *x509.Certificate) error {
if c.closed.Get() {
return errClosed
}

if certificate == nil {
return errors.New("certificate cannot be nil")
}

serial, err := asn1.Marshal(certificate.SerialNumber)
if err != nil {
return err
}

template.AddIfNotPresent([]*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
pkcs11.NewAttribute(pkcs11.CKA_CERTIFICATE_TYPE, pkcs11.CKC_X_509),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
pkcs11.NewAttribute(pkcs11.CKA_SUBJECT, certificate.RawSubject),
pkcs11.NewAttribute(pkcs11.CKA_ISSUER, certificate.RawIssuer),
pkcs11.NewAttribute(pkcs11.CKA_SERIAL_NUMBER, serial),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, certificate.Raw),
})

err = c.withSession(func(session *pkcs11Session) error {
_, err = session.ctx.CreateObject(session.handle, template.ToSlice())
return err
})

return err
}
154 changes: 154 additions & 0 deletions certificates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"math/big"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCertificate(t *testing.T) {
ctx, err := ConfigureFromFile("config")
require.NoError(t, err)

defer func() {
require.NoError(t, ctx.Close())
}()

id := randomBytes()
label := randomBytes()

cert := generateRandomCert(t)

err = ctx.ImportCertificateWithLabel(id, label, cert)
require.NoError(t, err)

cert2, err := ctx.FindCertificate(nil, label, nil)
require.NoError(t, err)
require.NotNil(t, cert2)

assert.Equal(t, cert.Signature, cert2.Signature)

cert2, err = ctx.FindCertificate(nil, []byte("test2"), nil)
require.NoError(t, err)
assert.Nil(t, cert2)

cert2, err = ctx.FindCertificate(nil, nil, cert.SerialNumber)
require.NoError(t, err)
require.NotNil(t, cert2)

assert.Equal(t, cert.Signature, cert2.Signature)
}

// Test that provided attributes override default values
func TestCertificateAttributes(t *testing.T) {
ctx, err := ConfigureFromFile("config")
require.NoError(t, err)

defer func() {
require.NoError(t, ctx.Close())
}()

cert := generateRandomCert(t)

// We import this with a different serial number, to test this is obeyed
ourSerial := new(big.Int)
ourSerial.Add(cert.SerialNumber, big.NewInt(1))

derSerial, err := asn1.Marshal(ourSerial)
require.NoError(t, err)

template := NewAttributeSet()
err = template.Set(CkaSerialNumber, derSerial)
require.NoError(t, err)

err = ctx.ImportCertificateWithAttributes(template, cert)
require.NoError(t, err)

// Try to find with old serial
c, err := ctx.FindCertificate(nil, nil, cert.SerialNumber)
assert.Nil(t, c)

// Find with new serial
c, err = ctx.FindCertificate(nil, nil, ourSerial)
assert.NotNil(t, c)
}

func TestCertificateRequiredArgs(t *testing.T) {
ctx, err := ConfigureFromFile("config")
require.NoError(t, err)

defer func() {
require.NoError(t, ctx.Close())
}()

cert := generateRandomCert(t)

val := randomBytes()

err = ctx.ImportCertificateWithLabel(nil, val, cert)
require.Error(t, err)

err = ctx.ImportCertificateWithLabel(val, nil, cert)
require.Error(t, err)

err = ctx.ImportCertificateWithLabel(val, val, nil)
require.Error(t, err)
}

func generateRandomCert(t *testing.T) *x509.Certificate {
serial, err := rand.Int(rand.Reader, big.NewInt(20000))
require.NoError(t, err)

ca := &x509.Certificate{
Subject: pkix.Name{
CommonName: "Foo",
},
SerialNumber: serial,
NotAfter: time.Now().Add(365 * 24 * time.Hour),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

key, err := rsa.GenerateKey(rand.Reader, 4096)
require.NoError(t, err)

csr := &key.PublicKey
certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, csr, key)
require.NoError(t, err)

cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err)

return cert
}
Loading

0 comments on commit 8367a3e

Please sign in to comment.