Jan Dittberner
ad6b987c91
- decouple config and messages - cainfo maps from config.Profile to messages.CAProfile - config parses profile usage - validity can be configured per certificate profile, defaults are defined in a defaultValidity method of the profile usage - the client simulator emits certificate signing requests at random intervals - add implementation of SingCertificateCommand to MsgPackHandler - remove indirection signing.RequestSignature
430 lines
11 KiB
Go
430 lines
11 KiB
Go
/*
|
|
Copyright 2022 CAcert Inc.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
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 messages
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
|
)
|
|
|
|
func TestCommandCode_String(t *testing.T) {
|
|
goodVariants := []struct {
|
|
Name string
|
|
Value CommandCode
|
|
}{
|
|
{"undefined", CmdUndef},
|
|
{"health", CmdHealth},
|
|
{"fetch-crl", CmdFetchCRL},
|
|
}
|
|
|
|
for _, v := range goodVariants {
|
|
t.Run(v.Name, func(t *testing.T) {
|
|
str := v.Value.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
})
|
|
}
|
|
|
|
t.Run("unknown", func(t *testing.T) {
|
|
var unknown CommandCode = -1
|
|
|
|
str := unknown.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Equal(t, "unknown -1", str)
|
|
})
|
|
}
|
|
|
|
func TestResponseCode_String(t *testing.T) {
|
|
goodVariants := []struct {
|
|
Name string
|
|
Value ResponseCode
|
|
}{
|
|
{"undefined", RespUndef},
|
|
{"error", RespError},
|
|
{"health", RespHealth},
|
|
{"fetch-crl", RespFetchCRL},
|
|
}
|
|
|
|
for _, v := range goodVariants {
|
|
t.Run(v.Name, func(t *testing.T) {
|
|
str := v.Value.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
})
|
|
}
|
|
|
|
t.Run("unknown", func(t *testing.T) {
|
|
var unknown ResponseCode = -2
|
|
|
|
str := unknown.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Equal(t, "unknown -2", str)
|
|
})
|
|
}
|
|
|
|
func TestCommandAnnounce_String(t *testing.T) {
|
|
announce := BuildCommandAnnounce(CmdUndef)
|
|
|
|
require.NotNil(t, announce)
|
|
|
|
str := announce.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "code")
|
|
assert.Contains(t, str, "id")
|
|
assert.Contains(t, str, "created")
|
|
}
|
|
|
|
func TestResponseAnnounce_String(t *testing.T) {
|
|
announce := BuildResponseAnnounce(RespUndef, uuid.NewString())
|
|
|
|
require.NotNil(t, announce)
|
|
|
|
str := announce.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "code")
|
|
assert.Contains(t, str, "id")
|
|
assert.Contains(t, str, "created")
|
|
}
|
|
|
|
func TestHealthCommand_String(t *testing.T) {
|
|
command := &HealthCommand{}
|
|
|
|
str := command.String()
|
|
|
|
assert.Equal(t, "", str)
|
|
}
|
|
|
|
func TestFetchCRLCommand_String(t *testing.T) {
|
|
t.Run("no-last-known", func(t *testing.T) {
|
|
cmd := &FetchCRLCommand{IssuerID: "issuer"}
|
|
|
|
str := cmd.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuerId")
|
|
assert.NotContains(t, str, "lastKnownId")
|
|
assert.NotContains(t, str, "0x")
|
|
})
|
|
|
|
t.Run("with-last-known", func(t *testing.T) {
|
|
cmd := &FetchCRLCommand{
|
|
IssuerID: "issuer",
|
|
LastKnownID: big.NewInt(255).Bytes(),
|
|
}
|
|
|
|
str := cmd.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuerId")
|
|
assert.Contains(t, str, "lastKnownId")
|
|
assert.Contains(t, str, "0xff")
|
|
})
|
|
}
|
|
|
|
func TestCAProfile_String(t *testing.T) {
|
|
profiles := []CAProfile{
|
|
{Name: "test-client", UseFor: signing.UsageClient},
|
|
{Name: "test-code", UseFor: signing.UsageCode},
|
|
{Name: "test-ocsp", UseFor: signing.UsageOCSP},
|
|
{Name: "test-org-client", UseFor: signing.UsageOrgClient},
|
|
{Name: "test-org-code", UseFor: signing.UsageOrgCode},
|
|
{Name: "test-org-email", UseFor: signing.UsageOrgEmail},
|
|
{Name: "test-org-person", UseFor: signing.UsageOrgPerson},
|
|
{Name: "test-org-server", UseFor: signing.UsageOrgServer},
|
|
{Name: "test-org-server-client", UseFor: signing.UsageOrgServerClient},
|
|
{Name: "test-person", UseFor: signing.UsagePerson},
|
|
{Name: "test-server", UseFor: signing.UsageServer},
|
|
{Name: "test-server-client", UseFor: signing.UsageServerClient},
|
|
}
|
|
|
|
for _, p := range profiles {
|
|
t.Run(p.Name, func(t *testing.T) {
|
|
str := p.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.True(t, strings.HasPrefix(str, "profile['"))
|
|
assert.True(t, strings.HasSuffix(str, "']"))
|
|
assert.Contains(t, str, "': '")
|
|
assert.Contains(t, str, p.Name)
|
|
assert.Contains(t, str, p.UseFor.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCAInfoCommand_String(t *testing.T) {
|
|
c := CAInfoCommand{Name: "test"}
|
|
|
|
str := c.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "test")
|
|
assert.Contains(t, str, "name")
|
|
}
|
|
|
|
func TestCAInfoResponse_String(t *testing.T) {
|
|
_, certBytes := createTestCertificate(t)
|
|
|
|
info := CAInfoResponse{
|
|
Name: "testca",
|
|
Signing: true,
|
|
Profiles: []CAProfile{{
|
|
Name: "test",
|
|
UseFor: signing.UsageServer,
|
|
}},
|
|
Certificate: certBytes,
|
|
}
|
|
|
|
str := info.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, fmt.Sprintf("signing=%t", info.Signing))
|
|
assert.Contains(t, str, "certificate name")
|
|
assert.Contains(t, str, "profiles")
|
|
|
|
if len(info.Profiles) == 0 {
|
|
assert.Contains(t, str, "[]")
|
|
}
|
|
}
|
|
|
|
func TestHealthInfo_String(t *testing.T) {
|
|
t.Run("no-more-info", func(t *testing.T) {
|
|
i := &HealthInfo{Source: "test1", Healthy: true}
|
|
|
|
str := i.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "source")
|
|
assert.Contains(t, str, "healthy")
|
|
assert.Contains(t, str, "true")
|
|
assert.NotContains(t, str, "[")
|
|
assert.NotContains(t, str, "]")
|
|
})
|
|
|
|
t.Run("with-more-info", func(t *testing.T) {
|
|
i := &HealthInfo{Source: "test2", Healthy: false, MoreInfo: map[string]string{"foo": "bar", "baz": "boo"}}
|
|
|
|
str := i.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "source")
|
|
assert.Contains(t, str, "healthy")
|
|
assert.Contains(t, str, "false")
|
|
assert.Contains(t, str, "['baz': 'boo', 'foo': 'bar']")
|
|
})
|
|
}
|
|
|
|
func TestHealthResponse_String(t *testing.T) {
|
|
t.Run("no-data", func(t *testing.T) {
|
|
r := &HealthResponse{Version: "foo", Healthy: true}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "signer version")
|
|
assert.Contains(t, str, "foo")
|
|
assert.Contains(t, str, "healthy")
|
|
assert.Contains(t, str, "true")
|
|
assert.Contains(t, str, "[]")
|
|
})
|
|
|
|
t.Run("with-data", func(t *testing.T) {
|
|
r := &HealthResponse{
|
|
Version: "foo",
|
|
Healthy: false,
|
|
Info: []*HealthInfo{
|
|
{Source: "bar", Healthy: true},
|
|
{Source: "baz", Healthy: false, MoreInfo: map[string]string{
|
|
"alice": "good",
|
|
"malory": "bad",
|
|
}},
|
|
},
|
|
}
|
|
|
|
str := r.String()
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "signer version")
|
|
assert.Contains(t, str, "foo")
|
|
assert.Contains(t, str, "healthy")
|
|
assert.Contains(t, str, "false")
|
|
assert.NotContains(t, str, "[]")
|
|
assert.Contains(t, str, "bar")
|
|
assert.Contains(t, str, "baz")
|
|
assert.Contains(t, str, "alice")
|
|
assert.Contains(t, str, "good")
|
|
assert.Contains(t, str, "malory")
|
|
assert.Contains(t, str, "bad")
|
|
})
|
|
}
|
|
|
|
func TestFetchCRLResponse_String(t *testing.T) {
|
|
t.Run("unchanged", func(t *testing.T) {
|
|
r := &FetchCRLResponse{
|
|
IssuerID: "deep-thought",
|
|
IsDelta: false,
|
|
UnChanged: true,
|
|
CRLNumber: big.NewInt(0x42).Bytes(),
|
|
}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuer id")
|
|
assert.Contains(t, str, "delta=false")
|
|
assert.Contains(t, str, "unchanged=true")
|
|
assert.Contains(t, str, "CRL number=0x42")
|
|
assert.NotContains(t, str, "delta CRL data")
|
|
assert.NotContains(t, str, "CERTIFICATE REVOCATION LIST")
|
|
})
|
|
|
|
t.Run("delta", func(t *testing.T) {
|
|
randomBytes := make([]byte, 100)
|
|
_, _ = rand.Read(randomBytes)
|
|
|
|
r := &FetchCRLResponse{
|
|
IssuerID: "deep-thought",
|
|
IsDelta: true,
|
|
UnChanged: false,
|
|
CRLData: randomBytes,
|
|
CRLNumber: big.NewInt(0x42).Bytes(),
|
|
}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuer id")
|
|
assert.Contains(t, str, "delta=true")
|
|
assert.Contains(t, str, "unchanged=false")
|
|
assert.Contains(t, str, "CRL number=0x42")
|
|
assert.Contains(t, str, "delta CRL data")
|
|
assert.Contains(t, str, "100 bytes")
|
|
assert.NotContains(t, str, "CERTIFICATE REVOCATION LIST")
|
|
})
|
|
|
|
t.Run("bad-crl", func(t *testing.T) {
|
|
randomBytes := make([]byte, 100)
|
|
_, _ = rand.Read(randomBytes)
|
|
|
|
r := &FetchCRLResponse{
|
|
IssuerID: "deep-thought",
|
|
IsDelta: false,
|
|
UnChanged: false,
|
|
CRLData: randomBytes,
|
|
CRLNumber: big.NewInt(0x42).Bytes(),
|
|
}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuer id")
|
|
assert.Contains(t, str, "delta=false")
|
|
assert.Contains(t, str, "unchanged=false")
|
|
assert.Contains(t, str, "CRL number=0x42")
|
|
assert.NotContains(t, str, "delta CRL data")
|
|
assert.NotContains(t, str, "CERTIFICATE REVOCATION LIST")
|
|
assert.Contains(t, str, "could not parse CRL")
|
|
})
|
|
|
|
t.Run("good-crl", func(t *testing.T) {
|
|
keyPair, certBytes := createTestCertificate(t)
|
|
|
|
cert, _ := x509.ParseCertificate(certBytes)
|
|
|
|
list, _ := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
Number: big.NewInt(0x42),
|
|
Issuer: cert.Subject,
|
|
ThisUpdate: time.Now(),
|
|
NextUpdate: time.Now().Add(1 * time.Hour),
|
|
}, cert, keyPair)
|
|
|
|
r := &FetchCRLResponse{
|
|
IssuerID: "deep-thought",
|
|
IsDelta: false,
|
|
UnChanged: false,
|
|
CRLData: list,
|
|
CRLNumber: big.NewInt(0x42).Bytes(),
|
|
}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "issuer id")
|
|
assert.Contains(t, str, "delta=false")
|
|
assert.Contains(t, str, "unchanged=false")
|
|
assert.Contains(t, str, "CRL number=0x42")
|
|
assert.NotContains(t, str, "delta CRL data")
|
|
assert.Contains(t, str, "CERTIFICATE REVOCATION LIST")
|
|
assert.NotContains(t, str, "could not parse CRL")
|
|
})
|
|
}
|
|
|
|
func createTestCertificate(t *testing.T) (crypto.Signer, []byte) {
|
|
t.Helper()
|
|
|
|
keyPair, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
require.NoError(t, err)
|
|
|
|
certTemplate := &x509.Certificate{
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
Issuer: pkix.Name{Country: []string{"un"}, Organization: []string{"Acme Ltd."}, CommonName: "foo CA"},
|
|
KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
|
|
MaxPathLen: 0,
|
|
MaxPathLenZero: false,
|
|
NotAfter: time.Now().AddDate(5, 0, 0),
|
|
NotBefore: time.Now(),
|
|
SerialNumber: big.NewInt(1),
|
|
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
|
Subject: pkix.Name{Country: []string{"un"}, Organization: []string{"Acme Ltd."}, CommonName: "foo CA"},
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, keyPair.Public(), keyPair)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return keyPair, certBytes
|
|
}
|
|
|
|
func TestErrorResponse_String(t *testing.T) {
|
|
r := &ErrorResponse{Message: "kaput"}
|
|
|
|
str := r.String()
|
|
|
|
assert.NotEmpty(t, str)
|
|
assert.Contains(t, str, "message")
|
|
assert.Contains(t, str, "kaput")
|
|
}
|