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 5 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
179 changes: 179 additions & 0 deletions pki/pki.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package pki

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

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

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

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")
}

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")
}

return UnmarshalPKIXPrivateKey(block.Bytes)
}

func UnmarshalPKIXPrivateKey(data []byte) (sign.PrivateKey, error) {
var pkix struct {
Version int
Algorithm pkix.AlgorithmIdentifier
PrivateKey []byte
}
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: fmt.Sprintf("%s PUBLIC KEY", pk.Scheme().Name()),
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(struct {
Version int
Algorithm pkix.AlgorithmIdentifier
PrivateKey []byte
}{
0,
pkix.AlgorithmIdentifier{
Algorithm: scheme.(CertificateScheme).Oid(),
},
data,
})
}
54 changes: 54 additions & 0 deletions pki/pki_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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
t.Logf("%v: %v\n", scheme.ID(), scheme.Name())
if scheme == nil {
t.Fatal()
}

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

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.Fatal()
}

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.Fatal()
}
}
}
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.SchemeCount]sign.Scheme
var allSchemeNames map[string]sign.Scheme

func init() {
allSchemeNames = make(map[string]sign.Scheme)
register(ed25519.Scheme)
register(ed448.Scheme)
register(eddilithium3.Scheme)
register(eddilithium4.Scheme)
bwesterb marked this conversation as resolved.
Show resolved Hide resolved
}

func register(s sign.Scheme) {
allSchemes[s.ID()] = s
allSchemeNames[s.Name()] = s
}

// 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
94 changes: 94 additions & 0 deletions sign/api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package api_test

import (
"crypto"
"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
t.Logf("%v: %v\n", scheme.ID(), scheme.Name())
if scheme == nil {
t.Fatal()
}

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

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

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

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

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

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.Fatal()
}

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

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

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

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

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

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

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

if sk.Scheme() != scheme {
t.Fatal()
}
}
}
18 changes: 18 additions & 0 deletions sign/dilithium/mode1/dilithium.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading