Continue certificate signing implementation
- parse PEM CSRs to DER before sending them to the signer - handle SignCertificate and SignOpenPGP response messages - add file system prefix constants for writing certificate files - define supported hash algorithms - fix handling of signing requests without organization, organizational unit, or location information - use a different SMTP port for debugging with mailpit
This commit is contained in:
parent
a6317c82c5
commit
73aad9d74e
2 changed files with 111 additions and 31 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022-2023 CAcert Inc.
|
||||
Copyright CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -132,6 +132,20 @@ func (s *SignerClientHandler) ResponseData(ctx context.Context, in <-chan []byte
|
|||
return fmt.Errorf("could not unmarshal fetch CRL response data: %w", err)
|
||||
}
|
||||
|
||||
response.Response = &resp
|
||||
case messages.RespSignCertificate:
|
||||
var resp messages.SignCertificateResponse
|
||||
if err := msgpack.Unmarshal(frame, &resp); err != nil {
|
||||
return fmt.Errorf("could not unmarshal sign certificate response data: %w", err)
|
||||
}
|
||||
|
||||
response.Response = &resp
|
||||
case messages.RespSignOpenPGP:
|
||||
var resp messages.SignOpenPGPResponse
|
||||
if err := msgpack.Unmarshal(frame, &resp); err != nil {
|
||||
return fmt.Errorf("could not unmarshal sign OpenPGP response data: %w", err)
|
||||
}
|
||||
|
||||
response.Response = &resp
|
||||
case messages.RespError:
|
||||
var resp messages.ErrorResponse
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package legacydb is emulates the behavior of the old signer client by polling from a MySQL database using the old
|
||||
// Package legacydb emulates the behavior of the old signer client by polling from a MySQL database using the old
|
||||
// schema, reading CSR files from the filesystem and storing certificates in the file system.
|
||||
package legacydb
|
||||
|
||||
|
@ -26,6 +26,7 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -172,6 +173,14 @@ SET warning = warning + 1
|
|||
WHERE id = ?`
|
||||
)
|
||||
|
||||
const (
|
||||
prefixOpenPGP = "gpg"
|
||||
prefixPersonalClient = "client"
|
||||
prefixPersonalServer = "server"
|
||||
prefixOrganizationalClient = "orgclient"
|
||||
prefixOrganizationalServer = "orgserver"
|
||||
)
|
||||
|
||||
type pendingResponse struct {
|
||||
responseType responseType
|
||||
rowID int
|
||||
|
@ -315,6 +324,12 @@ func New(logger *logrus.Logger, config *config.Database, commands chan *protocol
|
|||
respOrganizationalServerCertificate: "org_server",
|
||||
}
|
||||
|
||||
supportedHashAlgorithms := map[string]crypto.Hash{
|
||||
"sha256": crypto.SHA256,
|
||||
"sha384": crypto.SHA384,
|
||||
"sha512": crypto.SHA512,
|
||||
}
|
||||
|
||||
failureQuery := map[responseType]string{
|
||||
respGPG: sqlRecordFailedOpenPGP,
|
||||
respPersonalClientCertificate: sqlRecordFailedPersonalClientCertificate,
|
||||
|
@ -332,6 +347,7 @@ func New(logger *logrus.Logger, config *config.Database, commands chan *protocol
|
|||
logger: logger,
|
||||
commands: commands,
|
||||
emails: emails,
|
||||
algorithms: supportedHashAlgorithms,
|
||||
issuerIDs: issuerIDs,
|
||||
profiles: profiles,
|
||||
pending: pending,
|
||||
|
@ -540,7 +556,7 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
|||
var (
|
||||
csrID int
|
||||
csrFileName string
|
||||
certType int
|
||||
certType sql.NullInt16
|
||||
md string
|
||||
subject string
|
||||
)
|
||||
|
@ -549,7 +565,7 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
|||
return fmt.Errorf("could not scan row: %w", err)
|
||||
}
|
||||
|
||||
csrBytes, err := os.ReadFile(csrFileName)
|
||||
pemBytes, err := os.ReadFile(csrFileName)
|
||||
if err != nil {
|
||||
d.logger.WithFields(logrus.Fields{
|
||||
"id": csrID,
|
||||
|
@ -561,6 +577,23 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
|||
continue
|
||||
}
|
||||
|
||||
csrBlock, remaining := pem.Decode(pemBytes)
|
||||
if len(remaining) > 0 {
|
||||
d.logger.WithFields(logrus.Fields{"id": csrID, "file_name": csrFileName}).Warn("unhandled CSR bytes")
|
||||
|
||||
idsWithIssues = append(idsWithIssues, csrID)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if csrBlock.Type != "CERTIFICATE REQUEST" {
|
||||
d.logger.WithFields(logrus.Fields{"id": csrID, "file_name": csrFileName, "pem_block_type": csrBlock.Type}).Warn("unhandled PEM block type")
|
||||
|
||||
idsWithIssues = append(idsWithIssues, csrID)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
hashAlg, ok := d.algorithms[md]
|
||||
if !ok {
|
||||
d.logger.WithFields(logrus.Fields{}).Warn("unsupported hash algorithm")
|
||||
|
@ -582,22 +615,11 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
|||
continue
|
||||
}
|
||||
|
||||
signCertCommand := buildSignCertificateCommand(issuerID, profileID, csrBlock.Bytes, subjParts, hashAlg)
|
||||
|
||||
command := &protocol.Command{
|
||||
Announce: messages.BuildCommandAnnounce(messages.CmdSignCertificate),
|
||||
Command: messages.SignCertificateCommand{
|
||||
IssuerID: issuerID,
|
||||
ProfileName: profileID,
|
||||
CSRData: csrBytes,
|
||||
CommonName: subjParts.Subject.CommonName,
|
||||
Organization: subjParts.Subject.Organization[0],
|
||||
OrganizationalUnit: subjParts.Subject.OrganizationalUnit[0],
|
||||
Locality: subjParts.Subject.Locality[0],
|
||||
Province: subjParts.Subject.Province[0],
|
||||
Country: subjParts.Subject.Country[0],
|
||||
Hostnames: subjParts.DNSNames,
|
||||
EmailAddresses: subjParts.EmailAddresses,
|
||||
PreferredHash: hashAlg,
|
||||
},
|
||||
Command: signCertCommand,
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
|
@ -617,6 +639,45 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
|||
return nil
|
||||
}
|
||||
|
||||
func buildSignCertificateCommand(
|
||||
issuerID string,
|
||||
profileID string,
|
||||
csrBytes []byte,
|
||||
subjParts *x509.Certificate,
|
||||
hashAlg crypto.Hash,
|
||||
) messages.SignCertificateCommand {
|
||||
signCertCommand := messages.SignCertificateCommand{
|
||||
IssuerID: issuerID,
|
||||
ProfileName: profileID,
|
||||
CSRData: csrBytes,
|
||||
CommonName: subjParts.Subject.CommonName,
|
||||
Hostnames: subjParts.DNSNames,
|
||||
EmailAddresses: subjParts.EmailAddresses,
|
||||
PreferredHash: hashAlg,
|
||||
}
|
||||
|
||||
if len(subjParts.Subject.Organization) > 0 {
|
||||
signCertCommand.Organization = subjParts.Subject.Organization[0]
|
||||
}
|
||||
|
||||
if len(subjParts.Subject.OrganizationalUnit) > 0 {
|
||||
signCertCommand.OrganizationalUnit = subjParts.Subject.OrganizationalUnit[0]
|
||||
}
|
||||
|
||||
if len(subjParts.Subject.Locality) > 0 {
|
||||
signCertCommand.Locality = subjParts.Subject.Locality[0]
|
||||
}
|
||||
|
||||
if len(subjParts.Subject.Province) > 0 {
|
||||
signCertCommand.Province = subjParts.Subject.Province[0]
|
||||
}
|
||||
|
||||
if len(subjParts.Subject.Country) > 0 {
|
||||
signCertCommand.Country = subjParts.Subject.Country[0]
|
||||
}
|
||||
return signCertCommand
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidSANPart = errors.New("invalid SAN part, missing colon")
|
||||
errUnsupportedSubjectPart = errors.New("unsupported subject part")
|
||||
|
@ -702,19 +763,23 @@ func parseSubjectStringComponent(identifier, value string, res *x509.Certificate
|
|||
}
|
||||
|
||||
func (d *LegacyDB) revokePersonalClientCerts(_ context.Context) error {
|
||||
panic("not implemented")
|
||||
logrus.Debug("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LegacyDB) revokePersonalServerCerts(_ context.Context) error {
|
||||
panic("not implemented")
|
||||
logrus.Debug("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LegacyDB) revokeOrganizationClientCerts(_ context.Context) error {
|
||||
panic("not implemented")
|
||||
logrus.Debug("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LegacyDB) revokeOrganizationServerCerts(_ context.Context) error {
|
||||
panic("not implemented")
|
||||
logrus.Debug("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LegacyDB) writeCertificate(prefix string, rowID int, signatureData []byte) (string, error) {
|
||||
|
@ -728,10 +793,10 @@ func (d *LegacyDB) writeCertificate(prefix string, rowID int, signatureData []by
|
|||
return crtFileName, nil
|
||||
}
|
||||
|
||||
func (d *LegacyDB) recordCertificate(_ context.Context, query string, rowID int, certBytes []byte) error {
|
||||
func (d *LegacyDB) recordCertificate(_ context.Context, prefix, query string, rowID int, certBytes []byte) error {
|
||||
panic(fmt.Sprintf(
|
||||
"not implemented: record certificate with query %s for rowID %d (%d bytes)",
|
||||
query, rowID, len(certBytes),
|
||||
"not implemented: record certificate to prefix %s with query %s for rowID %d (%d bytes)",
|
||||
prefix, query, rowID, len(certBytes),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -778,7 +843,7 @@ func (d *LegacyDB) recordSignedOpenPGPKey(ctx context.Context, rowID int, signat
|
|||
|
||||
gpgExpiry := entity.Subkeys[0].Sig.CreationTime.Add(time.Second * time.Duration(*lifeTimeSecs))
|
||||
|
||||
crtFileName, err := d.writeCertificate("gpg", rowID, signatureData)
|
||||
crtFileName, err := d.writeCertificate(prefixOpenPGP, rowID, signatureData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write OpenPGP result: %w", err)
|
||||
}
|
||||
|
@ -847,13 +912,13 @@ func (d *LegacyDB) handleSignedCertificate(
|
|||
|
||||
switch pending.responseType {
|
||||
case respPersonalClientCertificate, respPersonalCodeSigningCertificate:
|
||||
err = d.recordCertificate(ctx, sqlRecordPersonalClientCert, pending.rowID, r.CertificateData)
|
||||
err = d.recordCertificate(ctx, prefixPersonalClient, sqlRecordPersonalClientCert, pending.rowID, r.CertificateData)
|
||||
case respPersonalServerCertificate:
|
||||
err = d.recordCertificate(ctx, sqlRecordPersonalServerCert, pending.rowID, r.CertificateData)
|
||||
err = d.recordCertificate(ctx, prefixPersonalServer, sqlRecordPersonalServerCert, pending.rowID, r.CertificateData)
|
||||
case respOrganizationalClientCertificate, respOrganizationalCodeSigningCertificate:
|
||||
err = d.recordCertificate(ctx, sqlRecordOrganizationalClientCert, pending.rowID, r.CertificateData)
|
||||
err = d.recordCertificate(ctx, prefixOrganizationalClient, sqlRecordOrganizationalClientCert, pending.rowID, r.CertificateData)
|
||||
case respOrganizationalServerCertificate:
|
||||
err = d.recordCertificate(ctx, sqlRecordOrganizationalServerCert, pending.rowID, r.CertificateData)
|
||||
err = d.recordCertificate(ctx, prefixOrganizationalServer, sqlRecordOrganizationalServerCert, pending.rowID, r.CertificateData)
|
||||
default:
|
||||
return fmt.Errorf("unexpected response type for pending request %s", announce.ID)
|
||||
}
|
||||
|
@ -936,7 +1001,8 @@ func (d *LegacyDB) sendNotificationEmail(ctx context.Context, e emailData) error
|
|||
return fmt.Errorf("could not set text template for email body: %w", err)
|
||||
}
|
||||
|
||||
c, err := mail.NewClient("localhost")
|
||||
const smtpPort = 1025
|
||||
c, err := mail.NewClient("localhost", mail.WithPort(smtpPort))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create mail client: %w", err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue