github.com/bestchains/fabric-ca@v2.0.0-alpha+incompatible/test/integration/default/default_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package defserver
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/ecdsa"
    12  	"crypto/elliptic"
    13  	"crypto/rand"
    14  	"crypto/x509"
    15  	"crypto/x509/pkix"
    16  	"encoding/pem"
    17  	"fmt"
    18  	"io"
    19  	"math/big"
    20  	"os"
    21  	"path/filepath"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/cloudflare/cfssl/certdb"
    26  	"github.com/cloudflare/cfssl/config"
    27  	"github.com/cloudflare/cfssl/log"
    28  	"github.com/hyperledger/fabric-ca/cmd/fabric-ca-client/command"
    29  	"github.com/hyperledger/fabric-ca/lib"
    30  	"github.com/hyperledger/fabric-ca/lib/metadata"
    31  	"github.com/hyperledger/fabric-ca/lib/server/db"
    32  	"github.com/hyperledger/fabric-ca/util"
    33  	"github.com/pkg/errors"
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  const (
    38  	cmdName    = "fabric-ca-client"
    39  	clientHome = "clientHome"
    40  )
    41  
    42  var (
    43  	defaultServer          *lib.Server
    44  	defaultServerPort      = 7054
    45  	defaultServerEnrollURL = fmt.Sprintf("http://admin:adminpw@localhost:%d", defaultServerPort)
    46  	defaultServerHomeDir   = "defaultServerDir"
    47  	storeCertsDir          = "/tmp/testCerts"
    48  )
    49  
    50  func TestMain(m *testing.M) {
    51  	var err error
    52  
    53  	metadata.Version = "1.1.0"
    54  
    55  	os.RemoveAll(defaultServerHomeDir)
    56  	defaultServer, err = getDefaultServer()
    57  	if err != nil {
    58  		log.Errorf("Failed to get instance of server: %s", err)
    59  		os.Exit(1)
    60  	}
    61  
    62  	err = defaultServer.Start()
    63  	if err != nil {
    64  		log.Errorf("Failed to start server: %s", err)
    65  		os.Exit(1)
    66  	}
    67  
    68  	rc := m.Run()
    69  
    70  	err = defaultServer.Stop()
    71  	if err != nil {
    72  		log.Errorf("Failed to stop server: %s, integration test results: %d", err, rc)
    73  		os.Exit(1)
    74  	}
    75  
    76  	os.RemoveAll(defaultServerHomeDir)
    77  	os.RemoveAll(storeCertsDir)
    78  	os.Exit(rc)
    79  }
    80  
    81  func TestListCertificateCmdNegative(t *testing.T) {
    82  	var err error
    83  	// Remove default client home location to remove any existing enrollment information
    84  	os.RemoveAll(filepath.Dir(util.GetDefaultConfigFile("fabric-ca-client")))
    85  
    86  	// Command should fail if caller has not yet enrolled
    87  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d"})
    88  	util.ErrorContains(t, err, "Enrollment information does not exist", "Should have failed to call command, if caller has not yet enrolled")
    89  
    90  	// Enroll a user that will be used for subsequent certificate commands
    91  	err = command.RunMain([]string{cmdName, "enroll", "-u", defaultServerEnrollURL, "-d"})
    92  	util.FatalError(t, err, "Failed to enroll user")
    93  
    94  	// Test with --revocation flag
    95  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--revocation", "-30d:-15d"})
    96  	t.Log("Error: ", err)
    97  	assert.Error(t, err, "Should fail, only one ':' specified need to specify two '::'")
    98  
    99  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--revocation", "30d::-15d"})
   100  	t.Log("Error: ", err)
   101  	assert.Error(t, err, "Should fail, missing +/- on starting duration")
   102  
   103  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--revocation", "+30d::15d"})
   104  	t.Log("Error: ", err)
   105  	assert.Error(t, err, "Should fail, missing +/- on ending duration")
   106  
   107  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--revocation", "+30d::+15y"})
   108  	t.Log("Error: ", err)
   109  	assert.Error(t, err, "Should fail, invalid duration type (y)")
   110  
   111  	// Test with --expiration flag
   112  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--expiration", "-30d:-15d"})
   113  	t.Log("Error: ", err)
   114  	assert.Error(t, err, "Should fail, only one ':' specified need to specify two '::'")
   115  
   116  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--expiration", "30d::-15d"})
   117  	t.Log("Error: ", err)
   118  	assert.Error(t, err, "Should fail, missing +/- on starting duration")
   119  
   120  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--expiration", "+30d::15d"})
   121  	t.Log("Error: ", err)
   122  	assert.Error(t, err, "Should fail, missing +/- on ending duration")
   123  
   124  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--expiration", "1/30/18::2/14/2018"})
   125  	t.Log("Error: ", err)
   126  	assert.Error(t, err, "Should fail, using slashes instead of dashes in time format")
   127  }
   128  
   129  func TestListCertificateCmdPositive(t *testing.T) {
   130  	populateCertificatesTable(t, defaultServer)
   131  
   132  	var err error
   133  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--expiration", "+30d::+15d", "--notexpired"})
   134  	t.Log("Error: ", err)
   135  	assert.Error(t, err, "Should fail, --expiration and --notexpired together")
   136  
   137  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d", "--id", "admin", "--revocation", "-30d::-10d", "--notrevoked"})
   138  	t.Log("Error: ", err)
   139  	assert.Error(t, err, "Should fail, --revocation and --notrevoked together")
   140  
   141  	// Enroll a user that will be used for subsequent certificate commands
   142  	err = command.RunMain([]string{cmdName, "enroll", "-u", defaultServerEnrollURL, "-d"})
   143  	util.FatalError(t, err, "Failed to enroll user")
   144  
   145  	err = command.RunMain([]string{cmdName, "certificate", "list", "-d"})
   146  	assert.NoError(t, err, "Failed to get certificates")
   147  
   148  	result, err := captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--id", "expire1"})
   149  	assert.NoError(t, err, "Failed to get certificate for an id")
   150  	assert.Contains(t, result, "expire1")
   151  
   152  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--serial", "1111"})
   153  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation duration")
   154  	assert.Contains(t, result, "Serial Number: 1111")
   155  
   156  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--aki", "9876"})
   157  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation duration")
   158  	assert.Contains(t, result, "Serial Number: 1111")
   159  	assert.Contains(t, result, "Serial Number: 1112")
   160  
   161  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--serial", "1112", "--aki", "9876"})
   162  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation duration")
   163  	assert.Contains(t, result, "Serial Number: 1112")
   164  	assert.NotContains(t, result, "Serial Number: 1111")
   165  
   166  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--id", "testCertificate3", "--aki", "9876AB"})
   167  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation duration")
   168  	assert.Contains(t, result, "1113")
   169  	assert.Contains(t, result, "testCertificate3")
   170  	assert.NotContains(t, result, "testCertificate1")
   171  
   172  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--id", "expire1", "--expiration", "2018-01-01::2018-03-05"})
   173  	assert.NoError(t, err, "Failed to parse a correctly formatted expiration date range")
   174  	assert.Contains(t, result, "expire1")
   175  	assert.NotContains(t, result, "expire3")
   176  
   177  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--id", "revoked2", "--revocation", "2017-01-01::2017-12-31"})
   178  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation date range")
   179  	assert.Contains(t, result, "revoked2")
   180  	assert.NotContains(t, result, "revoked3")
   181  
   182  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--expiration", "2018-03-01T00:00:00Z::2018-03-03T23:00:00Z"})
   183  	assert.NoError(t, err, "Failed to parse a correctly formatted expiration date range")
   184  	assert.Contains(t, result, "Serial Number: 1121")
   185  	assert.NotContains(t, result, "Serial Number: 1123")
   186  
   187  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--revocation", "2017-02-01T01:00:00Z::2017-02-20T23:00:00Z"})
   188  	assert.NoError(t, err, "Failed to parse a correctly formatted revocation date range")
   189  	assert.Contains(t, result, "Serial Number: 1132")
   190  	assert.NotContains(t, result, "Serial Number: 1131")
   191  
   192  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--expiration", "now::+101h"})
   193  	assert.NoError(t, err, "Failed to parse a expiration date range using 'now'")
   194  	assert.Contains(t, result, "Serial Number: 1123")
   195  	assert.NotContains(t, result, "Serial Number: 1121")
   196  
   197  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--revocation", "-15d::now"})
   198  	assert.NoError(t, err, "Failed to parse a revocation date range using 'now'")
   199  	assert.Contains(t, result, "Serial Number: 1131")
   200  	assert.NotContains(t, result, "Serial Number: 1111")
   201  
   202  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--expiration", "now::"})
   203  	assert.NoError(t, err, "Failed to parse a expiration date range using 'now' and empty end date")
   204  	assert.NotContains(t, result, "Serial Number: 1121")
   205  
   206  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--expiration", "::now"})
   207  	assert.NoError(t, err, "Failed to parse a expiration date range using 'now' and empty start date")
   208  	assert.Contains(t, result, "Serial Number: 1121")
   209  	assert.Contains(t, result, "Serial Number: 1122")
   210  	assert.Contains(t, result, "Serial Number: 1124")
   211  
   212  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--expiration", "::now", "--notrevoked"})
   213  	assert.NoError(t, err, "Failed to parse a expiration date range using 'now' and empty start date")
   214  	assert.Contains(t, result, "Serial Number: 1121")
   215  	assert.Contains(t, result, "Serial Number: 1122")
   216  	assert.NotContains(t, result, "Serial Number: 1124")
   217  
   218  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--revocation", "2018-02-01T01:00:00Z::"})
   219  	assert.NoError(t, err, "Failed to parse a revocation date range using 'now' and empty end date")
   220  	assert.Contains(t, result, "1131")
   221  
   222  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--revocation", "::now"})
   223  	assert.NoError(t, err, "Failed to parse a revocation date range using 'now' and empty start date")
   224  	assert.Contains(t, result, "Serial Number: 1131")
   225  	assert.Contains(t, result, "Serial Number: 1132")
   226  	assert.Contains(t, result, "Serial Number: 1124")
   227  
   228  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--revocation", "::now", "--notexpired"})
   229  	assert.NoError(t, err, "Failed to parse a revocation date range using 'now' and empty start date")
   230  	assert.Contains(t, result, "Serial Number: 1131")
   231  	assert.Contains(t, result, "Serial Number: 1132")
   232  	assert.NotContains(t, result, "Serial Number: 1124")
   233  
   234  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--notrevoked", "--notexpired"})
   235  	assert.NoError(t, err, "Failed to parse a revocation date range using 'now' and empty start date")
   236  	assert.Contains(t, result, "Serial Number: 1111")
   237  	assert.Contains(t, result, "Serial Number: 1112")
   238  	assert.Contains(t, result, "Serial Number: 1113")
   239  	assert.Contains(t, result, "Serial Number: 1123")
   240  	assert.NotContains(t, result, "Serial Number: 1121")
   241  	assert.NotContains(t, result, "Serial Number: 1122")
   242  	assert.NotContains(t, result, "Serial Number: 1124")
   243  	assert.NotContains(t, result, "Serial Number: 1131")
   244  	assert.NotContains(t, result, "Serial Number: 1132")
   245  	assert.NotContains(t, result, "Serial Number: 1133")
   246  
   247  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "-d", "--id", "fakeID"})
   248  	assert.NoError(t, err, "Should not error if the ID does not exist")
   249  	assert.Contains(t, result, "No results returned")
   250  
   251  	result, err = captureCLICertificatesOutput(command.RunMain, []string{cmdName, "certificate", "list", "--id", "expire1", "--store", storeCertsDir})
   252  	assert.NoError(t, err, "Should not error if the ID does not exist")
   253  	assert.Equal(t, true, util.FileExists(filepath.Join(storeCertsDir, "expire1-1.pem")))
   254  	assert.Equal(t, true, util.FileExists(filepath.Join(storeCertsDir, "expire1-2.pem")))
   255  	assert.Contains(t, result, "Serial Number: 1121")
   256  	assert.Contains(t, result, "Serial Number: 1124")
   257  }
   258  
   259  func populateCertificatesTable(t *testing.T, srv *lib.Server) {
   260  	var err error
   261  
   262  	dur, err := time.ParseDuration("+100h")
   263  	util.FatalError(t, err, "Failed to parse duration '+100h'")
   264  	futureTime := time.Now().Add(dur).UTC()
   265  
   266  	dur, err = time.ParseDuration("-72h")
   267  	util.FatalError(t, err, "Failed to parse duration '-72h'")
   268  	pastTime := time.Now().Add(dur).UTC()
   269  
   270  	// Active Certs
   271  	err = testInsertCertificate(&certdb.CertificateRecord{
   272  		Serial: "1111",
   273  		AKI:    "9876",
   274  		Expiry: futureTime,
   275  	}, "testCertificate1", srv)
   276  	util.FatalError(t, err, "Failed to insert certificate with serial/AKI")
   277  
   278  	err = testInsertCertificate(&certdb.CertificateRecord{
   279  		Serial: "1112",
   280  		AKI:    "9876",
   281  		Expiry: futureTime,
   282  	}, "testCertificate2", srv)
   283  	util.FatalError(t, err, "Failed to insert certificate with serial/AKI")
   284  
   285  	err = testInsertCertificate(&certdb.CertificateRecord{
   286  		Serial: "1113",
   287  		AKI:    "9876ab",
   288  		Expiry: futureTime,
   289  	}, "testCertificate3", srv)
   290  	util.FatalError(t, err, "Failed to insert certificate with serial/AKI")
   291  
   292  	// Expired
   293  	err = testInsertCertificate(&certdb.CertificateRecord{
   294  		Serial: "1121",
   295  		AKI:    "98765",
   296  		Expiry: time.Date(2018, time.March, 1, 0, 0, 0, 0, time.UTC),
   297  	}, "expire1", srv)
   298  	util.FatalError(t, err, "Failed to insert certificate with expiration date")
   299  
   300  	err = testInsertCertificate(&certdb.CertificateRecord{
   301  		Serial: "1122",
   302  		AKI:    "98765",
   303  		Expiry: time.Date(2018, time.March, 1, 0, 0, 0, 0, time.UTC),
   304  	}, "expire3", srv)
   305  	util.FatalError(t, err, "Failed to insert certificate with expiration date")
   306  
   307  	// Not Expired
   308  	err = testInsertCertificate(&certdb.CertificateRecord{
   309  		Serial: "1123",
   310  		AKI:    "98765",
   311  		Expiry: futureTime,
   312  	}, "expire2", srv)
   313  	util.FatalError(t, err, "Failed to insert certificate with expiration date")
   314  
   315  	// Expired and Revoked
   316  	err = testInsertCertificate(&certdb.CertificateRecord{
   317  		Serial:    "1124",
   318  		AKI:       "98765",
   319  		Expiry:    time.Date(2018, time.March, 1, 0, 0, 0, 0, time.UTC),
   320  		RevokedAt: pastTime,
   321  	}, "expire1", srv)
   322  	util.FatalError(t, err, "Failed to insert certificate with expiration date")
   323  
   324  	// Revoked
   325  	err = testInsertCertificate(&certdb.CertificateRecord{
   326  		Serial:    "1131",
   327  		AKI:       "98765",
   328  		Expiry:    futureTime,
   329  		RevokedAt: pastTime,
   330  	}, "revoked1", srv)
   331  	util.FatalError(t, err, "Failed to insert certificate with revocation date")
   332  
   333  	err = testInsertCertificate(&certdb.CertificateRecord{
   334  		Serial:    "1132",
   335  		AKI:       "98765",
   336  		Expiry:    futureTime,
   337  		RevokedAt: time.Date(2017, time.February, 15, 0, 0, 0, 0, time.UTC),
   338  	}, "revoked2", srv)
   339  	util.FatalError(t, err, "Failed to insert certificate with revocation date")
   340  
   341  	err = testInsertCertificate(&certdb.CertificateRecord{
   342  		Serial:    "1133",
   343  		AKI:       "98765",
   344  		Expiry:    futureTime,
   345  		RevokedAt: time.Date(2017, time.February, 15, 0, 0, 0, 0, time.UTC),
   346  	}, "revoked3", srv)
   347  	util.FatalError(t, err, "Failed to insert certificate with revocation date")
   348  }
   349  
   350  func captureCLICertificatesOutput(f func(args []string) error, args []string) (string, error) {
   351  	old := os.Stdout
   352  	r, w, err := os.Pipe()
   353  	if err != nil {
   354  		panic(err)
   355  	}
   356  	os.Stdout = w
   357  	err = f(args)
   358  	if err != nil {
   359  		return "", err
   360  	}
   361  	w.Close()
   362  	os.Stdout = old
   363  	var buf bytes.Buffer
   364  	io.Copy(&buf, r)
   365  	return buf.String(), nil
   366  }
   367  
   368  func TestRevokeWithColons(t *testing.T) {
   369  	var err error
   370  
   371  	err = testInsertCertificate(&certdb.CertificateRecord{
   372  		Serial: "11aa22bb",
   373  		AKI:    "33cc44dd",
   374  	}, "testingRevoke", defaultServer)
   375  	util.FatalError(t, err, "Failed to insert certificate with serial/AKI")
   376  
   377  	// Enroll a user that will be used for subsequent revoke commands
   378  	err = command.RunMain([]string{cmdName, "enroll", "-u", defaultServerEnrollURL, "-d"})
   379  	util.FatalError(t, err, "Failed to enroll user")
   380  
   381  	err = command.RunMain([]string{cmdName, "register", "-u", defaultServerEnrollURL, "--id.name", "testingRevoke", "-d"})
   382  	util.FatalError(t, err, "Failed to enroll user")
   383  
   384  	err = command.RunMain([]string{cmdName, "revoke", "-s", "11:AA:22:bb", "-a", "33:Cc:44:DD", "-d"})
   385  	assert.NoError(t, err, "Failed to revoke certificate, when serial number and AKI contained colons")
   386  }
   387  
   388  func getDefaultServer() (*lib.Server, error) {
   389  	affiliations := map[string]interface{}{
   390  		"hyperledger": map[string]interface{}{
   391  			"fabric":    []string{"ledger", "orderer", "security"},
   392  			"fabric-ca": nil,
   393  			"sdk":       nil,
   394  		},
   395  		"org2":      []string{"dept1"},
   396  		"org1":      nil,
   397  		"org2dept1": nil,
   398  	}
   399  	profiles := map[string]*config.SigningProfile{
   400  		"tls": &config.SigningProfile{
   401  			Usage:        []string{"signing", "key encipherment", "server auth", "client auth", "key agreement"},
   402  			ExpiryString: "8760h",
   403  		},
   404  		"ca": &config.SigningProfile{
   405  			Usage:        []string{"cert sign", "crl sign"},
   406  			ExpiryString: "8760h",
   407  			CAConstraint: config.CAConstraint{
   408  				IsCA:       true,
   409  				MaxPathLen: 0,
   410  			},
   411  		},
   412  	}
   413  	defaultProfile := &config.SigningProfile{
   414  		Usage:        []string{"cert sign"},
   415  		ExpiryString: "8760h",
   416  	}
   417  	srv := &lib.Server{
   418  		Config: &lib.ServerConfig{
   419  			Port:  defaultServerPort,
   420  			Debug: true,
   421  		},
   422  		CA: lib.CA{
   423  			Config: &lib.CAConfig{
   424  				Intermediate: lib.IntermediateCA{
   425  					ParentServer: lib.ParentServer{
   426  						URL: "",
   427  					},
   428  				},
   429  				Affiliations: affiliations,
   430  				Registry: lib.CAConfigRegistry{
   431  					MaxEnrollments: -1,
   432  				},
   433  				Signing: &config.Signing{
   434  					Profiles: profiles,
   435  					Default:  defaultProfile,
   436  				},
   437  				Version: "1.1.0", // The default test server/ca should use the latest version
   438  			},
   439  		},
   440  		HomeDir: defaultServerHomeDir,
   441  	}
   442  	// The bootstrap user's affiliation is the empty string, which
   443  	// means the user is at the affiliation root
   444  	err := srv.RegisterBootstrapUser("admin", "adminpw", "")
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	return srv, nil
   449  }
   450  
   451  func testInsertCertificate(req *certdb.CertificateRecord, id string, srv *lib.Server) error {
   452  	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   453  	if err != nil {
   454  		return errors.Errorf("Failed to generate private key: %s", err)
   455  	}
   456  
   457  	serial := new(big.Int)
   458  	serial.SetString(req.Serial, 10) //base 10
   459  	template := x509.Certificate{
   460  		Subject: pkix.Name{
   461  			CommonName: id,
   462  		},
   463  		SerialNumber:   serial,
   464  		AuthorityKeyId: []byte(req.AKI),
   465  		KeyUsage:       x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   466  	}
   467  
   468  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
   469  	if err != nil {
   470  		return err
   471  	}
   472  
   473  	cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   474  	record := &db.CertRecord{
   475  		ID: id,
   476  		CertificateRecord: certdb.CertificateRecord{
   477  			Serial:    req.Serial,
   478  			AKI:       req.AKI,
   479  			CALabel:   req.CALabel,
   480  			Status:    req.Status,
   481  			Reason:    req.Reason,
   482  			Expiry:    req.Expiry.UTC(),
   483  			RevokedAt: req.RevokedAt.UTC(),
   484  			PEM:       string(cert),
   485  		},
   486  	}
   487  
   488  	db := srv.CA.GetDB()
   489  	res, err := db.NamedExec("", `INSERT INTO certificates (id, serial_number, authority_key_identifier, ca_label, status, reason, expiry, revoked_at, pem, level)
   490  	VALUES (:id, :serial_number, :authority_key_identifier, :ca_label, :status, :reason, :expiry, :revoked_at, :pem, :level);`, record)
   491  
   492  	if err != nil {
   493  		return errors.Wrap(err, "Failed to insert record into database")
   494  	}
   495  
   496  	numRowsAffected, err := res.RowsAffected()
   497  
   498  	if numRowsAffected == 0 {
   499  		return errors.New("Failed to insert the certificate record; no rows affected")
   500  	}
   501  
   502  	if numRowsAffected != 1 {
   503  		return errors.Errorf("Expected to affect 1 entry in certificate database but affected %d",
   504  			numRowsAffected)
   505  	}
   506  
   507  	return err
   508  }