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

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

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

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)
allSchemesByTLS = make(map[uint]sign.Scheme)
for _, scheme := range schemes.All() {
if cert, ok := scheme.(CertificateScheme); ok {
allSchemesByOID[cert.Oid().String()] = scheme
}
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: sk.Scheme().Name() + " PRIVATE KEY",
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/schemes"
)

func TestPEM(t *testing.T) {
for _, scheme := range schemes.All() {
scheme := scheme
t.Run(scheme.Name(), func(t *testing.T) {
if scheme == nil {
t.Fatal()
}

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

18 changes: 18 additions & 0 deletions sign/dilithium/mode1aes/dilithium.go

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

18 changes: 18 additions & 0 deletions sign/dilithium/mode2/dilithium.go

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

18 changes: 18 additions & 0 deletions sign/dilithium/mode2aes/dilithium.go

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

18 changes: 18 additions & 0 deletions sign/dilithium/mode3/dilithium.go

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

27 changes: 27 additions & 0 deletions sign/dilithium/mode3/internal/dilithium.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
cryptoRand "crypto/rand"
"crypto/subtle"
"io"

"github.com/cloudflare/circl/internal/shake"
Expand Down Expand Up @@ -415,3 +416,29 @@ func (sk *PrivateKey) Public() *PublicKey {
pk.t1.PackT1(pk.t1p[:])
return pk
}

// Equal returns whether the two public keys are equal
func (pk *PublicKey) Equal(other *PublicKey) bool {
return pk.rho == other.rho && pk.t1 == other.t1
}

// Equal returns whether the two private keys are equal
func (sk *PrivateKey) Equal(other *PrivateKey) bool {
ret := (subtle.ConstantTimeCompare(sk.rho[:], other.rho[:]) &
subtle.ConstantTimeCompare(sk.key[:], other.key[:]) &
subtle.ConstantTimeCompare(sk.tr[:], other.tr[:]))

acc := uint32(0)
for i := 0; i < L; i++ {
for j := 0; j < common.N; j++ {
acc |= sk.s1[i][j] ^ other.s1[i][j]
}
}
for i := 0; i < K; i++ {
for j := 0; j < common.N; j++ {
acc |= sk.s2[i][j] ^ other.s2[i][j]
acc |= sk.t0[i][j] ^ other.t0[i][j]
}
}
return (ret & subtle.ConstantTimeEq(int32(acc), 0)) == 1
}
Loading