Add legacydb package to support existing MySQL DB
- add new legacydb package - fix warnings
This commit is contained in:
parent
eb92755ef6
commit
a6317c82c5
8 changed files with 1437 additions and 67 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
Copyright CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -31,6 +31,10 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
||||
|
||||
"git.cacert.org/cacert-gosignerclient/internal/legacydb"
|
||||
|
||||
"git.cacert.org/cacert-gosignerclient/internal/client"
|
||||
"git.cacert.org/cacert-gosignerclient/internal/config"
|
||||
"git.cacert.org/cacert-gosignerclient/internal/handler"
|
||||
|
@ -103,6 +107,8 @@ func generateDefaultConfig() error {
|
|||
serial:
|
||||
device: /dev/ttyUSB0
|
||||
baud: 112500
|
||||
database:
|
||||
dsn: "user:password@/dbname"
|
||||
`
|
||||
|
||||
cfg, err := config.LoadConfiguration(strings.NewReader(defaultBaseConfiguration))
|
||||
|
@ -141,6 +147,18 @@ func startClient(configFile string, logger *logrus.Logger) error {
|
|||
|
||||
defer func() { _ = signerClient.Close() }()
|
||||
|
||||
commands := make(chan *protocol.Command, clientConfig.CommandChannelCapacity)
|
||||
|
||||
legacyDB, err := legacydb.New(logger, &clientConfig.Database, commands)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize legacy database: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = legacyDB.Close() }()
|
||||
|
||||
signerClient.RegisterCommandSource(legacyDB)
|
||||
signerClient.RegisterResponseSink(legacyDB)
|
||||
|
||||
logger.Info("setup complete, starting client operation")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
@ -157,14 +175,14 @@ func startClient(configFile string, logger *logrus.Logger) error {
|
|||
cancel()
|
||||
}()
|
||||
|
||||
callbacks := make(chan interface{}, client.CallBackBufferSize)
|
||||
callbacks := make(chan any, client.CallBackBufferSize)
|
||||
|
||||
clientHandler, err := handler.New(clientConfig, logger, callbacks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not setup client handler: %w", err)
|
||||
}
|
||||
|
||||
if err = signerClient.Run(ctx, callbacks, clientHandler); err != nil {
|
||||
if err = signerClient.Run(ctx, callbacks, clientHandler, commands); err != nil {
|
||||
return fmt.Errorf("error in client: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
Copyright CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -82,21 +82,24 @@ type Client struct {
|
|||
config *config.ClientConfig
|
||||
signerInfo *SignerInfo
|
||||
knownCACertificates map[string]*CACertificateInfo
|
||||
commandSources []CommandSource
|
||||
responseSinks map[messages.ResponseCode]ResponseSink
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Client) Run(
|
||||
ctx context.Context, callback <-chan interface{}, handler protocol.ClientHandler,
|
||||
ctx context.Context, callback <-chan any, handler protocol.ClientHandler,
|
||||
commands chan *protocol.Command,
|
||||
) error {
|
||||
const componentCount = 4
|
||||
|
||||
protocolErrors, framerErrors := make(chan error), make(chan error)
|
||||
protocolErrors, framerErrors, sourceErrors := make(chan error), make(chan error), make(chan error)
|
||||
|
||||
subCtx, cancel := context.WithCancel(ctx)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(componentCount)
|
||||
wg.Add(len(c.commandSources))
|
||||
|
||||
commands := make(chan *protocol.Command, c.config.CommandChannelCapacity)
|
||||
fromSigner := make(chan []byte)
|
||||
toSigner := make(chan []byte)
|
||||
|
||||
|
@ -107,6 +110,8 @@ func (c *Client) Run(
|
|||
c.logger.Info("shutdown complete")
|
||||
}()
|
||||
|
||||
c.RunSources(subCtx, &wg, sourceErrors)
|
||||
|
||||
go func(f protocol.Framer) {
|
||||
defer wg.Done()
|
||||
|
||||
|
@ -171,6 +176,12 @@ func (c *Client) Run(
|
|||
return fmt.Errorf("error from protocol: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
case err := <-sourceErrors:
|
||||
if err != nil {
|
||||
return fmt.Errorf("error from command source: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -205,10 +216,15 @@ func (c *Client) Close() error {
|
|||
|
||||
type commandGenerator func(context.Context, chan<- *protocol.Command) error
|
||||
|
||||
func (c *Client) commandLoop(ctx context.Context, commands chan *protocol.Command, callback <-chan interface{}) {
|
||||
func (c *Client) commandLoop(ctx context.Context, commands chan *protocol.Command, callback <-chan any) {
|
||||
healthTimer := time.NewTimer(c.config.HealthStart)
|
||||
fetchCRLTimer := time.NewTimer(c.config.FetchCRLStart)
|
||||
nextCommands := make(chan *protocol.Command)
|
||||
|
||||
defer func() {
|
||||
close(commands)
|
||||
|
||||
c.logger.Info("command loop stopped")
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
@ -216,57 +232,54 @@ func (c *Client) commandLoop(ctx context.Context, commands chan *protocol.Comman
|
|||
return
|
||||
case callbackData := <-callback:
|
||||
go func() {
|
||||
err := c.handleCallback(ctx, nextCommands, callbackData)
|
||||
err := c.handleCallback(ctx, commands, callbackData)
|
||||
if err != nil {
|
||||
c.logger.WithError(err).Error("callback handling failed")
|
||||
}
|
||||
}()
|
||||
case <-fetchCRLTimer.C:
|
||||
go c.scheduleRequiredCRLFetches(ctx, nextCommands)
|
||||
go c.scheduleRequiredCRLFetches(ctx, commands)
|
||||
|
||||
fetchCRLTimer.Reset(c.config.FetchCRLInterval)
|
||||
case <-healthTimer.C:
|
||||
go c.scheduleHealthCheck(ctx, nextCommands)
|
||||
go c.scheduleHealthCheck(ctx, commands)
|
||||
|
||||
healthTimer.Reset(c.config.HealthInterval)
|
||||
case nextCommand, ok := <-nextCommands:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands <- nextCommand
|
||||
type ErrNoResponseSink struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
c.logger.WithFields(map[string]interface{}{
|
||||
"command": nextCommand.Announce,
|
||||
"buffer length": len(commands),
|
||||
}).Trace("sent command")
|
||||
}
|
||||
}
|
||||
func (e ErrNoResponseSink) Error() string {
|
||||
return fmt.Sprintf("no response sink for %s response found", e.msg)
|
||||
}
|
||||
|
||||
func (c *Client) handleCallback(
|
||||
ctx context.Context,
|
||||
newCommands chan<- *protocol.Command,
|
||||
data interface{},
|
||||
data any,
|
||||
) error {
|
||||
var handler commandGenerator
|
||||
var (
|
||||
handler commandGenerator
|
||||
err error
|
||||
)
|
||||
|
||||
switch d := data.(type) {
|
||||
case SignerInfo:
|
||||
handler = c.updateSignerInfo(d)
|
||||
case *messages.CAInfoResponse:
|
||||
handler = c.updateCAInformation(d)
|
||||
case *messages.FetchCRLResponse:
|
||||
handler = c.updateCRL(d)
|
||||
default:
|
||||
return fmt.Errorf("unknown callback data of type %T", data)
|
||||
}
|
||||
|
||||
if err := handler(ctx, newCommands); err != nil {
|
||||
case *protocol.Response:
|
||||
handler, err = c.handleResponse(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown callback data of type %T", d)
|
||||
}
|
||||
|
||||
return nil
|
||||
return handler(ctx, newCommands)
|
||||
}
|
||||
|
||||
func (c *Client) updateSignerInfo(
|
||||
|
@ -609,6 +622,94 @@ func (c *Client) setLastKnownCRL(caName string, number *big.Int) {
|
|||
caInfo.LastKnownCRL = number
|
||||
}
|
||||
|
||||
type CommandSource interface {
|
||||
Run(context.Context) error
|
||||
}
|
||||
|
||||
type ResponseSink interface {
|
||||
SupportedResponses() []messages.ResponseCode
|
||||
HandleResponse(context.Context, *messages.ResponseAnnounce, any) error
|
||||
NotifyError(ctx context.Context, requestID, message string) error
|
||||
}
|
||||
|
||||
func (c *Client) RegisterCommandSource(source CommandSource) {
|
||||
c.commandSources = append(c.commandSources, source)
|
||||
}
|
||||
|
||||
func (c *Client) RegisterResponseSink(sink ResponseSink) {
|
||||
for _, code := range sink.SupportedResponses() {
|
||||
c.responseSinks[code] = sink
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleResponse(r *protocol.Response) (commandGenerator, error) {
|
||||
var handler commandGenerator
|
||||
|
||||
switch payload := r.Response.(type) {
|
||||
case *messages.CAInfoResponse:
|
||||
handler = c.updateCAInformation(payload)
|
||||
case *messages.FetchCRLResponse:
|
||||
handler = c.updateCRL(payload)
|
||||
case *messages.ErrorResponse:
|
||||
handler = func(ctx context.Context, _ chan<- *protocol.Command) error {
|
||||
for _, sink := range c.responseSinks {
|
||||
if err := sink.NotifyError(ctx, r.Announce.ID, payload.Message); err != nil {
|
||||
return fmt.Errorf("error from response sink: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case *messages.SignCertificateResponse:
|
||||
sink, ok := c.responseSinks[messages.RespSignCertificate]
|
||||
if !ok {
|
||||
return nil, ErrNoResponseSink{"sign certificate"}
|
||||
}
|
||||
|
||||
handler = func(ctx context.Context, _ chan<- *protocol.Command) error {
|
||||
if err := sink.HandleResponse(ctx, r.Announce, payload); err != nil {
|
||||
return fmt.Errorf("error from response sink: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
case *messages.SignOpenPGPResponse:
|
||||
sink, ok := c.responseSinks[messages.RespSignOpenPGP]
|
||||
if !ok {
|
||||
return nil, ErrNoResponseSink{"sign openpgp"}
|
||||
}
|
||||
|
||||
handler = func(ctx context.Context, _ chan<- *protocol.Command) error {
|
||||
if err := sink.HandleResponse(ctx, r.Announce, payload); err != nil {
|
||||
return fmt.Errorf("error from response sink: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled response %s", payload)
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func (c *Client) RunSources(ctx context.Context, wg *sync.WaitGroup, errorChan chan error) {
|
||||
for _, source := range c.commandSources {
|
||||
go func(s CommandSource) {
|
||||
defer wg.Done()
|
||||
|
||||
err := s.Run(ctx)
|
||||
if err != nil {
|
||||
c.logger.WithError(err).Error("command source failed")
|
||||
|
||||
errorChan <- err
|
||||
}
|
||||
|
||||
c.logger.Info("command source stopped")
|
||||
}(source)
|
||||
}
|
||||
}
|
||||
|
||||
func New(
|
||||
cfg *config.ClientConfig,
|
||||
logger *logrus.Logger,
|
||||
|
@ -623,6 +724,8 @@ func New(
|
|||
framer: cobsFramer,
|
||||
config: cfg,
|
||||
knownCACertificates: make(map[string]*CACertificateInfo),
|
||||
responseSinks: make(map[messages.ResponseCode]ResponseSink),
|
||||
commandSources: make([]CommandSource, 0),
|
||||
}
|
||||
|
||||
err = client.setupConnection(&serial.Config{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
Copyright 2022-2023 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -36,6 +36,9 @@ const (
|
|||
defaultResponseDataTimeout = 2 * time.Second
|
||||
defaultFilesDirectory = "public"
|
||||
defaultCommandChannelCapacity = 100
|
||||
defaultDatabaseConnMaxLiveTime = 3 * time.Minute
|
||||
defaultDatabaseMaxOpenConns = 10
|
||||
defaultDatabaseMaxIdleConns = 10
|
||||
)
|
||||
|
||||
type SettingsError struct {
|
||||
|
@ -52,6 +55,13 @@ type Serial struct {
|
|||
Timeout time.Duration `yaml:"timeout"`
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
DSN string `yaml:"dsn"`
|
||||
ConnMaxLiveTime time.Duration `yaml:"conn-max-live-time"`
|
||||
MaxOpenConns int `yaml:"max-open-conns"`
|
||||
MaxIdleConns int `yaml:"max-idle-conns"`
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
Serial Serial `yaml:"serial"`
|
||||
HealthInterval time.Duration `yaml:"health-interval"`
|
||||
|
@ -63,6 +73,7 @@ type ClientConfig struct {
|
|||
PublicCRLDirectory string `yaml:"public-crl-directory"`
|
||||
PublicCertificateDirectory string `yaml:"public-certificate-directory"`
|
||||
CommandChannelCapacity int `yaml:"command-channel-capacity"`
|
||||
Database Database `yaml:"database"`
|
||||
}
|
||||
|
||||
func (c *ClientConfig) UnmarshalYAML(n *yaml.Node) error {
|
||||
|
@ -77,6 +88,7 @@ func (c *ClientConfig) UnmarshalYAML(n *yaml.Node) error {
|
|||
PublicCRLDirectory string `yaml:"public-crl-directory"`
|
||||
PublicCertificateDirectory string `yaml:"public-certificate-directory"`
|
||||
CommandChannelCapacity int `yaml:"command-channel-capacity"`
|
||||
Database Database `yaml:"database"`
|
||||
}{}
|
||||
|
||||
err := n.Decode(&data)
|
||||
|
@ -84,16 +96,8 @@ func (c *ClientConfig) UnmarshalYAML(n *yaml.Node) error {
|
|||
return fmt.Errorf("could not decode YAML: %w", err)
|
||||
}
|
||||
|
||||
if data.Serial.Device == "" {
|
||||
return SettingsError{"you must specify a serial 'device'"}
|
||||
}
|
||||
|
||||
if data.Serial.Baud == 0 {
|
||||
data.Serial.Baud = 115200
|
||||
}
|
||||
|
||||
if data.Serial.Timeout == 0 {
|
||||
data.Serial.Timeout = defaultSerialTimeout
|
||||
if err := checkSerialConfig(&data.Serial); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Serial = data.Serial
|
||||
|
@ -152,6 +156,50 @@ func (c *ClientConfig) UnmarshalYAML(n *yaml.Node) error {
|
|||
|
||||
c.PublicCertificateDirectory = data.PublicCRLDirectory
|
||||
|
||||
if err := checkDatabaseConfig(&data.Database); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Database = data.Database
|
||||
|
||||
c.Database = data.Database
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDatabaseConfig(d *Database) error {
|
||||
if d.DSN == "" {
|
||||
return SettingsError{"you must specify a database 'dsn'"}
|
||||
}
|
||||
|
||||
if d.ConnMaxLiveTime == 0 {
|
||||
d.ConnMaxLiveTime = defaultDatabaseConnMaxLiveTime
|
||||
}
|
||||
|
||||
if d.MaxOpenConns == 0 {
|
||||
d.MaxOpenConns = defaultDatabaseMaxOpenConns
|
||||
}
|
||||
|
||||
if d.MaxIdleConns == 0 {
|
||||
d.MaxIdleConns = defaultDatabaseMaxIdleConns
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSerialConfig(s *Serial) error {
|
||||
if s.Device == "" {
|
||||
return SettingsError{"you must specify a serial 'device'"}
|
||||
}
|
||||
|
||||
if s.Baud == 0 {
|
||||
s.Baud = 115200
|
||||
}
|
||||
|
||||
if s.Timeout == 0 {
|
||||
s.Timeout = defaultSerialTimeout
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
Copyright 2022-2023 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -37,7 +37,7 @@ import (
|
|||
type SignerClientHandler struct {
|
||||
logger *logrus.Logger
|
||||
config *config.ClientConfig
|
||||
clientCallback chan<- interface{}
|
||||
clientCallback chan<- any
|
||||
}
|
||||
|
||||
var errInputClosed = errors.New("input channel has been closed")
|
||||
|
@ -155,16 +155,13 @@ func (s *SignerClientHandler) HandleResponse(ctx context.Context, response *prot
|
|||
s.logger.WithField("response", response).Debug("full response")
|
||||
|
||||
switch r := response.Response.(type) {
|
||||
case *messages.ErrorResponse:
|
||||
s.logger.WithField("message", r.Message).Error("error from signer")
|
||||
case *messages.HealthResponse:
|
||||
s.handleHealthResponse(ctx, r)
|
||||
case *messages.CAInfoResponse:
|
||||
s.handleCAInfoResponse(ctx, r)
|
||||
case *messages.FetchCRLResponse:
|
||||
s.handleFetchCRLResponse(ctx, r)
|
||||
default:
|
||||
s.logger.WithField("response", response).Warnf("unhandled response of type %T", response.Response)
|
||||
s.logger.WithField("response", response).Tracef(
|
||||
"delegate response handling of type %T", response.Response,
|
||||
)
|
||||
s.handleGenericResponse(ctx, response)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -215,20 +212,11 @@ func (s *SignerClientHandler) handleHealthResponse(ctx context.Context, r *messa
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SignerClientHandler) handleCAInfoResponse(ctx context.Context, r *messages.CAInfoResponse) {
|
||||
func (s *SignerClientHandler) handleGenericResponse(ctx context.Context, response *protocol.Response) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case s.clientCallback <- r:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignerClientHandler) handleFetchCRLResponse(ctx context.Context, r *messages.FetchCRLResponse) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case s.clientCallback <- r:
|
||||
case s.clientCallback <- response:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +224,7 @@ func (s *SignerClientHandler) handleFetchCRLResponse(ctx context.Context, r *mes
|
|||
func New(
|
||||
config *config.ClientConfig,
|
||||
logger *logrus.Logger,
|
||||
clientCallback chan interface{},
|
||||
clientCallback chan any,
|
||||
) (protocol.ClientHandler, error) {
|
||||
return &SignerClientHandler{
|
||||
logger: logger,
|
||||
|
|
1014
internal/legacydb/legacydb.go
Normal file
1014
internal/legacydb/legacydb.go
Normal file
File diff suppressed because it is too large
Load diff
186
internal/legacydb/legacydb_test.go
Normal file
186
internal/legacydb/legacydb_test.go
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright 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 legacydb
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_extractSubjectParts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
subject string
|
||||
want *x509.Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"personal user subject",
|
||||
"/CN=John Doe/emailAddress=john.doe@example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "John Doe"},
|
||||
EmailAddresses: []string{"john.doe@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"subject with supported and unsupported alt names",
|
||||
"/CN=a.example.com/subjectAltName=DNS:a.example.com/" +
|
||||
"subjectAltName=otherName:1.3.6.1.5.5.7.8.5;UTF8:a.example.com",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "a.example.com"},
|
||||
DNSNames: []string{"a.example.com"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"subject with ISO-8859-1 special characters",
|
||||
"/CN=D\xf6ner Kebap/emailAddress=doener@example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "Döner Kebap"},
|
||||
EmailAddresses: []string{"doener@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"subject with Windows1252 special characters",
|
||||
"/CN=J\xe1no\x9a Test\x9c/emailAddress=janos.testoe@example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "Jánoš Testœ"},
|
||||
EmailAddresses: []string{"janos.testoe@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"WoT User subject",
|
||||
"/CN=CAcert WoT User/emailAddress=test@example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "CAcert WoT User"},
|
||||
EmailAddresses: []string{"test@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Keep address order",
|
||||
"/CN=CAcert WoT User/emailAddress=wot.user@example.com/emailAddress=wu@example.com",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "CAcert WoT User"},
|
||||
EmailAddresses: []string{"wot.user@example.com", "wu@example.com"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Keep DNS name order",
|
||||
"/CN=Test User/subjectAltName=DNS:www.example.com/subjectAltName=DNS:example.com",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "Test User"},
|
||||
DNSNames: []string{"www.example.com", "example.com"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Organization user without OU",
|
||||
"/CN=Test User/emailAddress=test@example.org/organizationName=Acme Inc./" +
|
||||
"localityName=Example town/stateOrProvinceName=BW/countryName=DE",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test User",
|
||||
Organization: []string{"Acme Inc."},
|
||||
Locality: []string{"Example town"},
|
||||
Province: []string{"BW"},
|
||||
Country: []string{"DE"},
|
||||
},
|
||||
EmailAddresses: []string{"test@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Organization user with OU",
|
||||
"/CN=Test User/emailAddress=test@example.org/organizationalUnitName=IT/" +
|
||||
"organizationName=Acme Inc./localityName=Example town/countryName=DE",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Test User",
|
||||
Organization: []string{"Acme Inc."},
|
||||
OrganizationalUnit: []string{"IT"},
|
||||
Locality: []string{"Example town"},
|
||||
Country: []string{"DE"},
|
||||
},
|
||||
EmailAddresses: []string{"test@example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Organization domain without OU",
|
||||
"/organizationName=Acme Inc./localityName=Example Town/stateOrProvinceName=BW/countryName=DE/" +
|
||||
"commonName=www.example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "www.example.org",
|
||||
Organization: []string{"Acme Inc."},
|
||||
Locality: []string{"Example Town"},
|
||||
Province: []string{"BW"},
|
||||
Country: []string{"DE"},
|
||||
},
|
||||
DNSNames: []string{"www.example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Organization domain with OU",
|
||||
"/organizationalUnitName=IT/organizationName=Acme Inc./localityName=Example Town/" +
|
||||
"stateOrProvinceName=BW/countryName=DE/commonName=example.org",
|
||||
&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "example.org",
|
||||
Organization: []string{"Acme Inc."},
|
||||
OrganizationalUnit: []string{"IT"},
|
||||
Locality: []string{"Example Town"},
|
||||
Province: []string{"BW"},
|
||||
Country: []string{"DE"},
|
||||
},
|
||||
DNSNames: []string{"example.org"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Empty subject",
|
||||
"",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"No = in part",
|
||||
"/CNexample",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := extractSubjectParts(tt.subject)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("extractSubjectParts() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got, "extractSubjectParts() got = %v, want %v", got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
12
templates/mail/en/gpg_body.txt
Normal file
12
templates/mail/en/gpg_body.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
Your CAcert signed key for {{ .Email }} is available online at:
|
||||
|
||||
https://www.cacert.org/gpg.php?id=3&cert={{ .RowID }}
|
||||
|
||||
To help improve the trust of CAcert in general, it's appreciated if you could also sign our key and upload it to a key
|
||||
server. Below is a copy of our primary key details:
|
||||
|
||||
pub 1024D/65D0FD58 2003-07-11 CA Cert Signing Authority (Root CA) <gpg@cacert.org>
|
||||
Key fingerprint = A31D 4F81 EF4E BD07 B456 FA04 D2BB 0D01 65D0 FD58
|
||||
|
||||
Best regards
|
||||
CAcert.org Support!
|
1
templates/mail/en/gpg_subject.txt
Normal file
1
templates/mail/en/gpg_subject.txt
Normal file
|
@ -0,0 +1 @@
|
|||
[CAcert.org] Your GPG/PGP Key
|
Loading…
Reference in a new issue