Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic signature API #143

Merged
merged 20 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions pki/pki.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package pki

import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"strings"

"github.com/cloudflare/circl/sign"
"github.com/cloudflare/circl/sign/api"
)

var allSchemesByOID map[string]sign.Scheme
var allSchemesByTLS map[uint]sign.Scheme

type pkixPrivKey struct {
Version int
Algorithm pkix.AlgorithmIdentifier
PrivateKey []byte
}

func init() {
allSchemesByOID = make(map[string]sign.Scheme)
for _, scheme := range api.AllSchemes() {
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
if cert, ok := scheme.(CertificateScheme); ok {
allSchemesByOID[cert.Oid().String()] = scheme
}
}

allSchemesByTLS = make(map[uint]sign.Scheme)
for _, scheme := range api.AllSchemes() {
if tlsScheme, ok := scheme.(TLSScheme); ok {
allSchemesByTLS[tlsScheme.TLSIdentifier()] = scheme
}
}
}

func SchemeByOid(oid asn1.ObjectIdentifier) sign.Scheme { return allSchemesByOID[oid.String()] }

func SchemeByTLSID(id uint) sign.Scheme { return allSchemesByTLS[id] }

// Additional methods when the signature scheme is supported in X509.
type CertificateScheme interface {
// Return the appropriate OIDs for this instance. It is implicitly
// assumed that the encoding is simple: e.g. uses the same OID for
// signature and public key like Ed25519.
Oid() asn1.ObjectIdentifier
}

// Additional methods when the signature scheme is supported in TLS.
type TLSScheme interface {
TLSIdentifier() uint
}

func UnmarshalPEMPublicKey(data []byte) (sign.PublicKey, error) {
block, rest := pem.Decode(data)
if len(rest) != 0 {
return nil, errors.New("trailing data")
}
if !strings.HasSuffix(block.Type, "PUBLIC KEY") {
return nil, errors.New("pem block type is not public key")
}

return UnmarshalPKIXPublicKey(block.Bytes)
}

func UnmarshalPKIXPublicKey(data []byte) (sign.PublicKey, error) {
var pkix struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
if rest, err := asn1.Unmarshal(data, &pkix); err != nil {
return nil, err
} else if len(rest) != 0 {
return nil, errors.New("trailing data")
}
scheme := SchemeByOid(pkix.Algorithm.Algorithm)
if scheme == nil {
return nil, errors.New("unsupported public key algorithm")
}
return scheme.UnmarshalBinaryPublicKey(pkix.PublicKey.RightAlign())
}

func UnmarshalPEMPrivateKey(data []byte) (sign.PrivateKey, error) {
block, rest := pem.Decode(data)
if len(rest) != 0 {
return nil, errors.New("trailing")
}
if !strings.HasSuffix(block.Type, "PRIVATE KEY") {
return nil, errors.New("pem block type is not private key")
}

return UnmarshalPKIXPrivateKey(block.Bytes)
}

func UnmarshalPKIXPrivateKey(data []byte) (sign.PrivateKey, error) {
var pkix pkixPrivKey
if rest, err := asn1.Unmarshal(data, &pkix); err != nil {
return nil, err
} else if len(rest) != 0 {
return nil, errors.New("trailing data")
}
scheme := SchemeByOid(pkix.Algorithm.Algorithm)
if scheme == nil {
return nil, errors.New("unsupported public key algorithm")
}
var sk []byte
if rest, err := asn1.Unmarshal(pkix.PrivateKey, &sk); err != nil {
return nil, err
} else if len(rest) > 0 {
return nil, errors.New("trailing data")
}
return scheme.UnmarshalBinaryPrivateKey(sk)
}

func MarshalPEMPublicKey(pk sign.PublicKey) ([]byte, error) {
data, err := MarshalPKIXPublicKey(pk)
if err != nil {
return nil, err
}
str := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: data,
})
return str, nil
}

func MarshalPKIXPublicKey(pk sign.PublicKey) ([]byte, error) {
data, err := pk.MarshalBinary()
if err != nil {
return nil, err
}

scheme := pk.Scheme()
return asn1.Marshal(struct {
pkix.AlgorithmIdentifier
asn1.BitString
}{
pkix.AlgorithmIdentifier{
Algorithm: scheme.(CertificateScheme).Oid(),
},
asn1.BitString{
Bytes: data,
BitLength: len(data) * 8,
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
},
})
}

func MarshalPEMPrivateKey(sk sign.PrivateKey) ([]byte, error) {
data, err := MarshalPKIXPrivateKey(sk)
if err != nil {
return nil, err
}
str := pem.EncodeToMemory(&pem.Block{
Type: fmt.Sprintf("%s PRIVATE KEY", sk.Scheme().Name()),
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
Bytes: data,
},
)
return str, nil
}

func MarshalPKIXPrivateKey(sk sign.PrivateKey) ([]byte, error) {
data, err := sk.MarshalBinary()
if err != nil {
return nil, err
}

data, err = asn1.Marshal(data)
if err != nil {
return nil, err
}

scheme := sk.Scheme()
return asn1.Marshal(pkixPrivKey{
0,
pkix.AlgorithmIdentifier{
Algorithm: scheme.(CertificateScheme).Oid(),
},
data,
})
}
56 changes: 56 additions & 0 deletions pki/pki_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pki_test

import (
"testing"

"github.com/cloudflare/circl/pki"
"github.com/cloudflare/circl/sign/api"
)

func TestPEM(t *testing.T) {
for _, scheme := range api.AllSchemes() {
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
scheme := scheme
t.Run(scheme.Name(), func(t *testing.T) {
if scheme == nil {
t.FailNow()
}

_, ok := scheme.(pki.CertificateScheme)
if !ok {
return
}

pk, sk, err := scheme.GenerateKey()
if err != nil {
t.Fatal(err)
}

packedPk, err := pki.MarshalPEMPublicKey(pk)
if err != nil {
t.Fatal(err)
}

pk2, err := pki.UnmarshalPEMPublicKey(packedPk)
if err != nil {
t.Fatal(err)
}
if !pk.Equal(pk2) {
t.FailNow()
}

packedSk, err := pki.MarshalPEMPrivateKey(sk)
if err != nil {
t.Fatal(err)
}

sk2, err := pki.UnmarshalPEMPrivateKey(packedSk)
if err != nil {
t.Fatal(err)
}

if !sk.Equal(sk2) {
t.FailNow()
}
})
}
}
33 changes: 33 additions & 0 deletions sign/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Package api contains a register of signature algorithms.
package api

import (
"github.com/cloudflare/circl/sign"
"github.com/cloudflare/circl/sign/ed25519"
"github.com/cloudflare/circl/sign/ed448"
"github.com/cloudflare/circl/sign/eddilithium3"
"github.com/cloudflare/circl/sign/eddilithium4"
)

var allSchemes = [...]sign.Scheme{
ed25519.Scheme,
ed448.Scheme,
eddilithium3.Scheme,
eddilithium4.Scheme,
}

var allSchemeNames map[string]sign.Scheme

func init() {
allSchemeNames = make(map[string]sign.Scheme)
for _, scheme := range allSchemes {
allSchemeNames[scheme.Name()] = scheme
}
}

// SchemeByName returns the scheme with the given name and nil if it is not
// supported.
func SchemeByName(name string) sign.Scheme { return allSchemeNames[name] }

// AllSchemes returns all signature schemes supported.
func AllSchemes() []sign.Scheme { a := allSchemes; return a[:] }
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
103 changes: 103 additions & 0 deletions sign/api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package api_test

import (
"fmt"
"testing"

"github.com/cloudflare/circl/sign"
"github.com/cloudflare/circl/sign/api"
)

func TestApi(t *testing.T) {
allSchemes := api.AllSchemes()
for _, scheme := range allSchemes {
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
scheme := scheme
t.Run(scheme.Name(), func(t *testing.T) {
if scheme == nil {
t.FailNow()
}

pk, sk, err := scheme.GenerateKey()
if err != nil {
t.FailNow()
}

packedPk, err := pk.MarshalBinary()
if err != nil {
t.FailNow()
}

if len(packedPk) != scheme.PublicKeySize() {
t.FailNow()
}

packedSk, err := sk.MarshalBinary()
if err != nil {
t.Fatal(err)
}

if len(packedSk) != scheme.PrivateKeySize() {
t.FailNow()
}

pk2, err := scheme.UnmarshalBinaryPublicKey(packedPk)
if err != nil {
t.Fatal(err)
}

sk2, err := scheme.UnmarshalBinaryPrivateKey(packedSk)
if err != nil {
t.Fatal(err)
}

if !sk.Equal(sk2) {
t.FailNow()
}

if !pk.Equal(pk2) {
t.FailNow()
}

msg := []byte(fmt.Sprintf("Signing with %s", scheme.Name()))
opts := &sign.SignatureOpts{}
if scheme.SupportsContext() {
opts.Context = "A context"
}
sig := scheme.Sign(sk, msg, opts)

if scheme.SignatureSize() != len(sig) {
t.FailNow()
}

if !scheme.Verify(pk2, msg, sig, opts) {
t.FailNow()
}

if scheme.SupportsContext() {
opts2 := opts
opts2.Context = "Wrong context"
if scheme.Verify(pk2, msg, sig, opts2) {
t.FailNow()
}
}

sig[0]++
if scheme.Verify(pk2, msg, sig, opts) {
t.FailNow()
}

scheme2 := api.SchemeByName(scheme.Name())
if scheme2 == nil || scheme2 != scheme {
t.FailNow()
}

if pk.Scheme() != scheme {
t.FailNow()
}

if sk.Scheme() != scheme {
t.FailNow()
}
})
}
}
Loading