Skip to content
Snippets Groups Projects
Commit 7cec960c authored by Dan Lorenc's avatar Dan Lorenc
Browse files

Add x509 support and tests.

Still need to add some more e2e-style tests that go through the ArtifactFactory flow.
parent db0409e9
No related branches found
No related tags found
No related merge requests found
FROM registry.access.redhat.com/ubi8/go-toolset AS builder
FROM golang:1.15 AS builder
ENV APP_ROOT=/opt/app-root
ENV GOPATH=$APP_ROOT
WORKDIR $APP_ROOT/src/
......
......@@ -167,6 +167,8 @@ func CreateRekordFromPFlags() (models.ProposedEntry, error) {
re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatPgp
case "minisign":
re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatMinisign
case "x509":
re.RekordObj.Signature.Format = models.RekordV001SchemaSignatureFormatX509
}
signature := viper.GetString("signature")
sigURL, err := url.Parse(signature)
......@@ -254,12 +256,13 @@ func (f *sigFormatFlag) Set(s string) error {
set := map[string]struct{}{
"pgp": {},
"minisign": {},
"x509": {},
}
if _, ok := set[s]; ok {
f.value = s
return nil
}
return fmt.Errorf("value specified is invalid: [%s] supported values are: [pgp, minisign]", s)
return fmt.Errorf("value specified is invalid: [%s] supported values are: [pgp, minisign, x509]", s)
}
type shaFlag struct {
......
......@@ -394,7 +394,7 @@ type RekordV001SchemaSignature struct {
Content strfmt.Base64 `json:"content,omitempty"`
// Specifies the format of the signature
// Enum: [pgp minisign]
// Enum: [pgp minisign x509]
Format string `json:"format,omitempty"`
// public key
......@@ -431,7 +431,7 @@ var rekordV001SchemaSignatureTypeFormatPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["pgp","minisign"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["pgp","minisign","x509"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
......@@ -446,6 +446,9 @@ const (
// RekordV001SchemaSignatureFormatMinisign captures enum value "minisign"
RekordV001SchemaSignatureFormatMinisign string = "minisign"
// RekordV001SchemaSignatureFormatX509 captures enum value "x509"
RekordV001SchemaSignatureFormatX509 string = "x509"
)
// prop value enum
......
......@@ -1151,7 +1151,8 @@ func init() {
"type": "string",
"enum": [
"pgp",
"minisign"
"minisign",
"x509"
]
},
"publicKey": {
......@@ -1374,7 +1375,8 @@ func init() {
"type": "string",
"enum": [
"pgp",
"minisign"
"minisign",
"x509"
]
},
"publicKey": {
......
......@@ -22,6 +22,8 @@ import (
"strings"
"github.com/projectrekor/rekor/pkg/pki/minisign"
"github.com/projectrekor/rekor/pkg/pki/x509"
"github.com/projectrekor/rekor/pkg/pki/pgp"
)
......@@ -52,6 +54,8 @@ func (a ArtifactFactory) NewPublicKey(r io.Reader) (PublicKey, error) {
return pgp.NewPublicKey(r)
case "minisign":
return minisign.NewPublicKey(r)
case "x509":
return x509.NewPublicKey(r)
}
return nil, fmt.Errorf("unknown key format '%v'", a.format)
}
......@@ -62,6 +66,8 @@ func (a ArtifactFactory) NewSignature(r io.Reader) (Signature, error) {
return pgp.NewSignature(r)
case "minisign":
return minisign.NewSignature(r)
case "x509":
return x509.NewSignature(r)
}
return nil, fmt.Errorf("unknown key format '%v'", a.format)
}
......@@ -48,6 +48,12 @@ func TestFactoryNewKey(t *testing.T) {
sigFile: "minisign/testdata/hello_world.txt.minisig",
expectSuccess: true,
},
{
format: "x509",
keyFile: "x509/testdata/ec.pub",
sigFile: "x509/testdata/hello_world.txt.sig",
expectSuccess: true,
},
{
format: "bogus",
expectSuccess: false,
......
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIxaAc+TaxYoQU0X9NNUJgWffbn6h
juoEDPQQn80nX/Eus9I/t00ccNpcSrUw1+IPyGp1p9fgmL0DlBf05BNFNQ==
-----END PUBLIC KEY-----
Hello, World!
File added
/*
Copyright © 2021 Dan Lorenc <lorenc.d@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package x509
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
)
type Signature struct {
signature []byte
}
// NewSignature creates and validates an x509 signature object
func NewSignature(r io.Reader) (*Signature, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return &Signature{
signature: b,
}, nil
}
// CanonicalValue implements the pki.Signature interface
func (s Signature) CanonicalValue() ([]byte, error) {
return s.signature, nil
}
// Verify implements the pki.Signature interface
func (s Signature) Verify(r io.Reader, k interface{}) error {
if len(s.signature) == 0 {
return fmt.Errorf("X509 signature has not been initialized")
}
message, err := ioutil.ReadAll(r)
if err != nil {
return err
}
hashed := sha256.Sum256(message)
key, ok := k.(*PublicKey)
if !ok {
return fmt.Errorf("Invalid public key type for: %v", k)
}
switch pub := key.key.(type) {
case *rsa.PublicKey:
return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], s.signature)
case ed25519.PublicKey:
if ed25519.Verify(pub, message, s.signature) {
return nil
}
return fmt.Errorf("signature mismatch for %s", string(s.signature))
case *ecdsa.PublicKey:
if ecdsa.VerifyASN1(pub, hashed[:], s.signature) {
return nil
}
return fmt.Errorf("signature mismatch for %s", string(s.signature))
default:
return fmt.Errorf("invalid public key type: %T", pub)
}
}
// PublicKey Public Key that follows the x509 standard
type PublicKey struct {
key interface{}
}
// NewPublicKey implements the pki.PublicKey interface
func NewPublicKey(r io.Reader) (*PublicKey, error) {
rawPub, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
block, _ := pem.Decode(rawPub)
if block == nil {
return nil, fmt.Errorf("invalid public key: %s", string(rawPub))
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
return &PublicKey{key: key}, nil
}
// CanonicalValue implements the pki.PublicKey interface
func (k PublicKey) CanonicalValue() ([]byte, error) {
if k.key == nil {
return nil, fmt.Errorf("x509 public key has not been initialized")
}
b, err := x509.MarshalPKIXPublicKey(k.key)
if err != nil {
return nil, err
}
p := pem.Block{
Type: "PUBLIC KEY",
Bytes: b,
}
var buf bytes.Buffer
if err := pem.Encode(&buf, &p); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
/*
Copyright © 2021 Dan Lorenc <lorenc.d@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package x509
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"strings"
"testing"
)
// Generated with:
// openssl genrsa -out myprivate.pem 512
// openssl pkcs8 -topk8 -in myprivate.pem -nocrypt'
const pkcs1v15Priv = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAoLEL57Kd5w8b5LCl
SM+5mJbVYj4GoFXP/Gynfk6mDj7aANYWAkU74xkjz0BX2Nq0IT9DyxWI8aXZ8B6R
YtbsPwIDAQABAkA2WgwTz5eXKsYdgR421YQKN6JvO1mUa9IQqFOy5jlGgbR+W5HG
JfQVJKhCGMYYmByHgR0QDk/6gvJjhuszTHuJAiEA0siY/vE20zC1UHpPgDXXVSNN
dKtM6YKBKSo47oTKQHsCIQDDKZgal50Cd3W+lOWpNO23QGZgBhJrJ70TpcPWGEsS
DQIhAIDIMLnq1G1Z4B2IbRRPUP3icMtscbRlmNZ2xovsM8oLAiBluZh+w+gjEQFe
hV3wBJajnf2+r2uKTvxO8WhSf/chQQIhAKzYjX2chfvPN6hRqeGeoPpRLXS8cdxC
A4hZJRvZgkO3
-----END PRIVATE KEY-----`
// Extracted from above with:
// openssl rsa -in myprivate.pem -pubout
const pkcs1v15Pub = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKCxC+eynecPG+SwpUjPuZiW1WI+BqBV
z/xsp35Opg4+2gDWFgJFO+MZI89AV9jatCE/Q8sViPGl2fAekWLW7D8CAwEAAQ==
-----END PUBLIC KEY-----`
// Generated with:
// openssl ecparam -genkey -name prime256v1 > ec_private.pem
// openssl pkcs8 -topk8 -in ec_private.pem -nocrypt
const ecdsaPriv = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmrLtCpBdXgXLUr7o
nSUPfo3oXMjmvuwTOjpTulIBKlKhRANCAATH6KSpTFe6uXFmW1qNEFXaO7fWPfZt
pPZrHZ1cFykidZoURKoYXfkohJ+U/USYy8Sd8b4DMd5xDRZCnlDM0h37
-----END PRIVATE KEY-----`
// Extracted from above with:
// openssl ec -in ec_private.pem -pubout
const ecdsaPub = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+ikqUxXurlxZltajRBV2ju31j32
baT2ax2dXBcpInWaFESqGF35KISflP1EmMvEnfG+AzHecQ0WQp5QzNId+w==
-----END PUBLIC KEY-----`
// Generated with:
// openssl genpkey -algorithm ED25519 -out edprivate.pem
const ed25519Priv = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIKjlXfR/VFvO9qM9+CG2qbuSM54k8ciKWHhgNwKTgqpG
-----END PRIVATE KEY-----`
// Extracted from above with:
// openssl pkey -in edprivate.pem -pubout
const ed25519Pub = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAizWek2gKgMM+bad4rVJ5nc9NsbNOba0A0BNfzOgklRs=
-----END PUBLIC KEY-----`
func signData(t *testing.T, b []byte, pkey string) []byte {
// Get a private key object
p, _ := pem.Decode([]byte(pkey))
if p.Type != "PRIVATE KEY" {
t.Fatalf("expected private key, found object of type %s", p.Type)
}
pk, err := x509.ParsePKCS8PrivateKey(p.Bytes)
if err != nil {
t.Fatal(err)
}
h := sha256.Sum256(b)
var signature []byte
switch k := pk.(type) {
case *rsa.PrivateKey:
signature, err = rsa.SignPKCS1v15(rand.Reader, k, crypto.SHA256, h[:])
case *ecdsa.PrivateKey:
signature, err = ecdsa.SignASN1(rand.Reader, k, h[:])
case ed25519.PrivateKey:
signature = ed25519.Sign(k, b)
}
if err != nil {
t.Fatal(err)
}
return signature
}
func TestSignature_Verify(t *testing.T) {
tests := []struct {
name string
priv string
pub string
}{
{
name: "rsa",
priv: pkcs1v15Priv,
pub: pkcs1v15Pub,
},
{
name: "ec",
priv: ecdsaPriv,
pub: ecdsaPub,
},
{
name: "ed25519",
priv: ed25519Priv,
pub: ed25519Pub,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := []byte("hey! this is my test data")
sigBytes := signData(t, data, tt.priv)
s, err := NewSignature(bytes.NewReader(sigBytes))
if err != nil {
t.Fatal(err)
}
pub, err := NewPublicKey(strings.NewReader(tt.pub))
if err != nil {
t.Fatal(err)
}
if err := s.Verify(bytes.NewReader(data), pub); err != nil {
t.Errorf("Signature.Verify() error = %v", err)
}
// Now try with the canonical value
cb, err := s.CanonicalValue()
if err != nil {
t.Error(err)
}
canonicalSig, err := NewSignature(bytes.NewReader(cb))
if err != nil {
t.Error(err)
}
if err := canonicalSig.Verify(bytes.NewReader(data), pub); err != nil {
t.Errorf("Signature.Verify() error = %v", err)
}
})
}
}
func TestSignature_VerifyFail(t *testing.T) {
tests := []struct {
name string
priv string
pub string
}{
{
name: "rsa",
priv: pkcs1v15Priv,
pub: pkcs1v15Pub,
},
{
name: "ec",
priv: ecdsaPriv,
pub: ecdsaPub,
},
{
name: "ed25519",
priv: ed25519Priv,
pub: ed25519Pub,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Make some fake data, and tamper with the signature
data := []byte("hey! this is my test data")
sigBytes := signData(t, data, tt.priv)
sigBytes[0] = sigBytes[0] - 1
s, err := NewSignature(bytes.NewReader(sigBytes))
if err != nil {
t.Fatal(err)
}
pub, err := NewPublicKey(strings.NewReader(tt.pub))
if err != nil {
t.Fatal(err)
}
if err := s.Verify(bytes.NewReader(data), pub); err == nil {
t.Error("Signature.Verify() expected error!")
}
})
}
}
......@@ -12,7 +12,7 @@
"format": {
"description": "Specifies the format of the signature",
"type": "string",
"enum": [ "pgp", "minisign" ]
"enum": [ "pgp", "minisign", "x509" ]
},
"url": {
"description": "Specifies the location of the signature",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment