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
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
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)
|
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
|
response.Response = &resp
|
||||||
case messages.RespError:
|
case messages.RespError:
|
||||||
var resp messages.ErrorResponse
|
var resp messages.ErrorResponse
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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.
|
// schema, reading CSR files from the filesystem and storing certificates in the file system.
|
||||||
package legacydb
|
package legacydb
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -172,6 +173,14 @@ SET warning = warning + 1
|
||||||
WHERE id = ?`
|
WHERE id = ?`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prefixOpenPGP = "gpg"
|
||||||
|
prefixPersonalClient = "client"
|
||||||
|
prefixPersonalServer = "server"
|
||||||
|
prefixOrganizationalClient = "orgclient"
|
||||||
|
prefixOrganizationalServer = "orgserver"
|
||||||
|
)
|
||||||
|
|
||||||
type pendingResponse struct {
|
type pendingResponse struct {
|
||||||
responseType responseType
|
responseType responseType
|
||||||
rowID int
|
rowID int
|
||||||
|
@ -315,6 +324,12 @@ func New(logger *logrus.Logger, config *config.Database, commands chan *protocol
|
||||||
respOrganizationalServerCertificate: "org_server",
|
respOrganizationalServerCertificate: "org_server",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportedHashAlgorithms := map[string]crypto.Hash{
|
||||||
|
"sha256": crypto.SHA256,
|
||||||
|
"sha384": crypto.SHA384,
|
||||||
|
"sha512": crypto.SHA512,
|
||||||
|
}
|
||||||
|
|
||||||
failureQuery := map[responseType]string{
|
failureQuery := map[responseType]string{
|
||||||
respGPG: sqlRecordFailedOpenPGP,
|
respGPG: sqlRecordFailedOpenPGP,
|
||||||
respPersonalClientCertificate: sqlRecordFailedPersonalClientCertificate,
|
respPersonalClientCertificate: sqlRecordFailedPersonalClientCertificate,
|
||||||
|
@ -332,6 +347,7 @@ func New(logger *logrus.Logger, config *config.Database, commands chan *protocol
|
||||||
logger: logger,
|
logger: logger,
|
||||||
commands: commands,
|
commands: commands,
|
||||||
emails: emails,
|
emails: emails,
|
||||||
|
algorithms: supportedHashAlgorithms,
|
||||||
issuerIDs: issuerIDs,
|
issuerIDs: issuerIDs,
|
||||||
profiles: profiles,
|
profiles: profiles,
|
||||||
pending: pending,
|
pending: pending,
|
||||||
|
@ -540,7 +556,7 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
||||||
var (
|
var (
|
||||||
csrID int
|
csrID int
|
||||||
csrFileName string
|
csrFileName string
|
||||||
certType int
|
certType sql.NullInt16
|
||||||
md string
|
md string
|
||||||
subject 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)
|
return fmt.Errorf("could not scan row: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csrBytes, err := os.ReadFile(csrFileName)
|
pemBytes, err := os.ReadFile(csrFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.WithFields(logrus.Fields{
|
d.logger.WithFields(logrus.Fields{
|
||||||
"id": csrID,
|
"id": csrID,
|
||||||
|
@ -561,6 +577,23 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
||||||
continue
|
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]
|
hashAlg, ok := d.algorithms[md]
|
||||||
if !ok {
|
if !ok {
|
||||||
d.logger.WithFields(logrus.Fields{}).Warn("unsupported hash algorithm")
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signCertCommand := buildSignCertificateCommand(issuerID, profileID, csrBlock.Bytes, subjParts, hashAlg)
|
||||||
|
|
||||||
command := &protocol.Command{
|
command := &protocol.Command{
|
||||||
Announce: messages.BuildCommandAnnounce(messages.CmdSignCertificate),
|
Announce: messages.BuildCommandAnnounce(messages.CmdSignCertificate),
|
||||||
Command: messages.SignCertificateCommand{
|
Command: signCertCommand,
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Lock()
|
d.Lock()
|
||||||
|
@ -617,6 +639,45 @@ func (d *LegacyDB) requestCerts(ctx context.Context, query string, rt responseTy
|
||||||
return nil
|
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 (
|
var (
|
||||||
errInvalidSANPart = errors.New("invalid SAN part, missing colon")
|
errInvalidSANPart = errors.New("invalid SAN part, missing colon")
|
||||||
errUnsupportedSubjectPart = errors.New("unsupported subject part")
|
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 {
|
func (d *LegacyDB) revokePersonalClientCerts(_ context.Context) error {
|
||||||
panic("not implemented")
|
logrus.Debug("not implemented")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LegacyDB) revokePersonalServerCerts(_ context.Context) error {
|
func (d *LegacyDB) revokePersonalServerCerts(_ context.Context) error {
|
||||||
panic("not implemented")
|
logrus.Debug("not implemented")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LegacyDB) revokeOrganizationClientCerts(_ context.Context) error {
|
func (d *LegacyDB) revokeOrganizationClientCerts(_ context.Context) error {
|
||||||
panic("not implemented")
|
logrus.Debug("not implemented")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LegacyDB) revokeOrganizationServerCerts(_ context.Context) error {
|
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) {
|
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
|
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(
|
panic(fmt.Sprintf(
|
||||||
"not implemented: record certificate with query %s for rowID %d (%d bytes)",
|
"not implemented: record certificate to prefix %s with query %s for rowID %d (%d bytes)",
|
||||||
query, rowID, len(certBytes),
|
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))
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not write OpenPGP result: %w", err)
|
return fmt.Errorf("could not write OpenPGP result: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -847,13 +912,13 @@ func (d *LegacyDB) handleSignedCertificate(
|
||||||
|
|
||||||
switch pending.responseType {
|
switch pending.responseType {
|
||||||
case respPersonalClientCertificate, respPersonalCodeSigningCertificate:
|
case respPersonalClientCertificate, respPersonalCodeSigningCertificate:
|
||||||
err = d.recordCertificate(ctx, sqlRecordPersonalClientCert, pending.rowID, r.CertificateData)
|
err = d.recordCertificate(ctx, prefixPersonalClient, sqlRecordPersonalClientCert, pending.rowID, r.CertificateData)
|
||||||
case respPersonalServerCertificate:
|
case respPersonalServerCertificate:
|
||||||
err = d.recordCertificate(ctx, sqlRecordPersonalServerCert, pending.rowID, r.CertificateData)
|
err = d.recordCertificate(ctx, prefixPersonalServer, sqlRecordPersonalServerCert, pending.rowID, r.CertificateData)
|
||||||
case respOrganizationalClientCertificate, respOrganizationalCodeSigningCertificate:
|
case respOrganizationalClientCertificate, respOrganizationalCodeSigningCertificate:
|
||||||
err = d.recordCertificate(ctx, sqlRecordOrganizationalClientCert, pending.rowID, r.CertificateData)
|
err = d.recordCertificate(ctx, prefixOrganizationalClient, sqlRecordOrganizationalClientCert, pending.rowID, r.CertificateData)
|
||||||
case respOrganizationalServerCertificate:
|
case respOrganizationalServerCertificate:
|
||||||
err = d.recordCertificate(ctx, sqlRecordOrganizationalServerCert, pending.rowID, r.CertificateData)
|
err = d.recordCertificate(ctx, prefixOrganizationalServer, sqlRecordOrganizationalServerCert, pending.rowID, r.CertificateData)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected response type for pending request %s", announce.ID)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create mail client: %w", err)
|
return fmt.Errorf("could not create mail client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue